feat(plugin): add terrain3d plugin installation
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
# Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
#
|
||||
# This is an example of using a particle shader with Terrain3D.
|
||||
# To use it, add `Terrain3DParticles.tscn` to your scene and assign the terrain.
|
||||
# Then customize the settings, materials and shader to extend it and make it your own.
|
||||
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
|
||||
#region settings
|
||||
## Auto set if attached as a child of a Terrain3D node
|
||||
@export var terrain: Terrain3D:
|
||||
set(value):
|
||||
terrain = value
|
||||
_create_grid()
|
||||
|
||||
|
||||
## Distance between instances
|
||||
@export_range(0.125, 2.0, 0.015625) var instance_spacing: float = 0.5:
|
||||
set(value):
|
||||
instance_spacing = clamp(round(value * 64.0) * 0.015625, 0.125, 2.0)
|
||||
rows = maxi(int(cell_width / instance_spacing), 1)
|
||||
amount = rows * rows
|
||||
_set_offsets()
|
||||
|
||||
|
||||
## Width of an individual cell of the grid
|
||||
@export_range(8.0, 256.0, 1.0) var cell_width: float = 32.0:
|
||||
set(value):
|
||||
cell_width = clamp(value, 8.0, 256.0)
|
||||
rows = maxi(int(cell_width / instance_spacing), 1)
|
||||
amount = rows * rows
|
||||
min_draw_distance = 1.0
|
||||
# Have to update aabb
|
||||
if terrain and terrain.data:
|
||||
var height_range: Vector2 = terrain.data.get_height_range()
|
||||
var height: float = height_range[0] - height_range[1]
|
||||
var aabb: AABB = AABB()
|
||||
aabb.size = Vector3(cell_width, height, cell_width)
|
||||
aabb.position = aabb.size * -0.5
|
||||
aabb.position.y = height_range[1]
|
||||
for p in particle_nodes:
|
||||
p.custom_aabb = aabb
|
||||
_set_offsets()
|
||||
|
||||
|
||||
## Grid width. Must be odd.
|
||||
## Higher values cull slightly better, draw further out.
|
||||
@export_range(1, 15, 2) var grid_width: int = 9:
|
||||
set(value):
|
||||
grid_width = value
|
||||
particle_count = 1
|
||||
min_draw_distance = 1.0
|
||||
_create_grid()
|
||||
|
||||
|
||||
@export_storage var rows: int = 1
|
||||
|
||||
@export_storage var amount: int = 1:
|
||||
set(value):
|
||||
amount = value
|
||||
particle_count = value
|
||||
last_pos = Vector3.ZERO
|
||||
for p in particle_nodes:
|
||||
p.amount = amount
|
||||
|
||||
|
||||
@export_range(1, 256, 1) var process_fixed_fps: int = 30:
|
||||
set(value):
|
||||
process_fixed_fps = maxi(value, 1)
|
||||
for p in particle_nodes:
|
||||
p.fixed_fps = process_fixed_fps
|
||||
p.preprocess = 1.0 / float(process_fixed_fps)
|
||||
|
||||
|
||||
## Access to process material parameters
|
||||
@export var process_material: ShaderMaterial
|
||||
|
||||
## The mesh that each particle will render
|
||||
@export var mesh: Mesh
|
||||
|
||||
@export var shadow_mode: GeometryInstance3D.ShadowCastingSetting = (
|
||||
GeometryInstance3D.ShadowCastingSetting.SHADOW_CASTING_SETTING_ON):
|
||||
set(value):
|
||||
shadow_mode = value
|
||||
for p in particle_nodes:
|
||||
p.cast_shadow = value
|
||||
|
||||
|
||||
## Override material for the particle mesh
|
||||
@export_custom(
|
||||
PROPERTY_HINT_RESOURCE_TYPE,
|
||||
"BaseMaterial3D,ShaderMaterial") var mesh_material_override: Material:
|
||||
set(value):
|
||||
mesh_material_override = value
|
||||
for p in particle_nodes:
|
||||
p.material_override = mesh_material_override
|
||||
|
||||
|
||||
@export_group("Info")
|
||||
## The minimum distance that particles will be drawn upto
|
||||
## If using fade out effects like pixel alpha this is the limit to use.
|
||||
@export var min_draw_distance: float = 1.0:
|
||||
set(value):
|
||||
min_draw_distance = float(cell_width * grid_width) * 0.5
|
||||
|
||||
|
||||
## Displays current total particle count based on Cell Width and Instance Spacing
|
||||
@export var particle_count: int = 1:
|
||||
set(value):
|
||||
particle_count = amount * grid_width * grid_width
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
var offsets: Array[Vector3]
|
||||
var last_pos: Vector3 = Vector3.ZERO
|
||||
var particle_nodes: Array[GPUParticles3D]
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not terrain:
|
||||
var parent: Node = get_parent()
|
||||
if parent is Terrain3D:
|
||||
terrain = parent
|
||||
_create_grid()
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
_destroy_grid()
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if terrain:
|
||||
var camera: Camera3D = terrain.get_camera()
|
||||
if camera:
|
||||
if last_pos.distance_squared_to(camera.global_position) > 1.0:
|
||||
var pos: Vector3 = camera.global_position.snapped(Vector3.ONE)
|
||||
_position_grid(pos)
|
||||
RenderingServer.material_set_param(process_material.get_rid(), "camera_position", pos )
|
||||
last_pos = camera.global_position
|
||||
_update_process_parameters()
|
||||
else:
|
||||
set_physics_process(false)
|
||||
|
||||
|
||||
func _create_grid() -> void:
|
||||
_destroy_grid()
|
||||
if not terrain:
|
||||
return
|
||||
set_physics_process(true)
|
||||
_set_offsets()
|
||||
var hr: Vector2 = terrain.data.get_height_range()
|
||||
var height: float = hr.x - hr.y
|
||||
var aabb: AABB = AABB()
|
||||
aabb.size = Vector3(cell_width, height, cell_width)
|
||||
aabb.position = aabb.size * -0.5
|
||||
aabb.position.y = hr.y
|
||||
var half_grid: int = grid_width / 2
|
||||
# Iterating the array like this allows identifying grid position, in case setting
|
||||
# different mesh or materials is desired for LODs etc.
|
||||
for x in range(-half_grid, half_grid + 1):
|
||||
for z in range(-half_grid, half_grid + 1):
|
||||
#var ring: int = maxi(maxi(absi(x), absi(z)), 0)
|
||||
var particle_node = GPUParticles3D.new()
|
||||
particle_node.lifetime = 600.0
|
||||
particle_node.amount = amount
|
||||
particle_node.explosiveness = 1.0
|
||||
particle_node.amount_ratio = 1.0
|
||||
particle_node.process_material = process_material
|
||||
particle_node.draw_pass_1 = mesh
|
||||
particle_node.speed_scale = 1.0
|
||||
particle_node.custom_aabb = aabb
|
||||
particle_node.cast_shadow = shadow_mode
|
||||
particle_node.fixed_fps = process_fixed_fps
|
||||
# This prevent minor grid alignment errors when the camera is moving very fast
|
||||
particle_node.preprocess = 1.0 / float(process_fixed_fps)
|
||||
if mesh_material_override:
|
||||
particle_node.material_override = mesh_material_override
|
||||
particle_node.use_fixed_seed = true
|
||||
if (x > -half_grid and z > -half_grid): # Use the same seed across all nodes
|
||||
particle_node.seed = particle_nodes[0].seed
|
||||
self.add_child(particle_node)
|
||||
particle_node.emitting = true
|
||||
particle_nodes.push_back(particle_node)
|
||||
last_pos = Vector3.ZERO
|
||||
|
||||
|
||||
func _set_offsets() -> void:
|
||||
var half_grid: int = grid_width / 2
|
||||
offsets.clear()
|
||||
for x in range(-half_grid, half_grid + 1):
|
||||
for z in range(-half_grid, half_grid + 1):
|
||||
var offset := Vector3(
|
||||
float(x * rows) * instance_spacing,
|
||||
0.0,
|
||||
float(z * rows) * instance_spacing
|
||||
)
|
||||
offsets.append(offset)
|
||||
|
||||
|
||||
func _destroy_grid() -> void:
|
||||
for node: GPUParticles3D in particle_nodes:
|
||||
if is_instance_valid(node):
|
||||
node.queue_free()
|
||||
particle_nodes.clear()
|
||||
|
||||
|
||||
func _position_grid(pos: Vector3) -> void:
|
||||
for i in particle_nodes.size():
|
||||
var node: GPUParticles3D = particle_nodes[i]
|
||||
var snap = Vector3(pos.x, 0, pos.z).snapped(Vector3.ONE) + offsets[i]
|
||||
node.global_position = (snap / instance_spacing).round() * instance_spacing
|
||||
node.reset_physics_interpolation()
|
||||
node.restart(true) # keep the same seed.
|
||||
|
||||
|
||||
func _update_process_parameters() -> void:
|
||||
if process_material:
|
||||
var process_rid: RID = process_material.get_rid()
|
||||
if terrain and process_rid.is_valid():
|
||||
RenderingServer.material_set_param(process_rid, "_background_mode", terrain.material.world_background)
|
||||
RenderingServer.material_set_param(process_rid, "_vertex_spacing", terrain.vertex_spacing)
|
||||
RenderingServer.material_set_param(process_rid, "_vertex_density", 1.0 / terrain.vertex_spacing)
|
||||
RenderingServer.material_set_param(process_rid, "_region_size", terrain.region_size)
|
||||
RenderingServer.material_set_param(process_rid, "_region_texel_size", 1.0 / terrain.region_size)
|
||||
RenderingServer.material_set_param(process_rid, "_region_map_size", 32)
|
||||
RenderingServer.material_set_param(process_rid, "_region_map", terrain.data.get_region_map())
|
||||
RenderingServer.material_set_param(process_rid, "_region_locations", terrain.data.get_region_locations())
|
||||
RenderingServer.material_set_param(process_rid, "_height_maps", terrain.data.get_height_maps_rid())
|
||||
RenderingServer.material_set_param(process_rid, "_control_maps", terrain.data.get_control_maps_rid())
|
||||
RenderingServer.material_set_param(process_rid, "_color_maps", terrain.data.get_color_maps_rid())
|
||||
RenderingServer.material_set_param(process_rid, "instance_spacing", instance_spacing)
|
||||
RenderingServer.material_set_param(process_rid, "instance_rows", rows)
|
||||
RenderingServer.material_set_param(process_rid, "max_dist", min_draw_distance)
|
||||
Reference in New Issue
Block a user