@tool # Shows a cursor on top of the terrain to preview where the brush will paint # TODO Use an actual decal node, it wasn't available in Godot 3 const HT_DirectMeshInstance = preload("../../util/direct_mesh_instance.gd") const HTerrain = preload("../../hterrain.gd") const HTerrainData = preload("../../hterrain_data.gd") const HT_Util = preload("../../util/util.gd") var _mesh_instance : HT_DirectMeshInstance var _mesh : PlaneMesh var _material := ShaderMaterial.new() #var _debug_mesh := BoxMesh.new() #var _debug_mesh_instance : HT_DirectMeshInstance = null var _terrain : HTerrain = null func _init(): _material.shader = load("res://addons/zylann.hterrain/tools/brush/decal.gdshader") _mesh_instance = HT_DirectMeshInstance.new() _mesh_instance.set_material(_material) _mesh = PlaneMesh.new() _mesh_instance.set_mesh(_mesh) # _debug_mesh_instance = HT_DirectMeshInstance.new() # _debug_mesh_instance.set_mesh(_debug_mesh) func set_size(size: float): _mesh.size = Vector2(size, size) # Must line up to terrain vertex policy, so must apply an off-by-one. # If I don't do that, the brush will appear to wobble above the ground var ss := size - 1 # Adding extra subdivisions, notably for small brush sizes ss *= 4 # Don't subdivide too much while ss > 50: ss /= 2 _mesh.subdivide_width = ss _mesh.subdivide_depth = ss # Move decal closer to ground at small sizes, otherwise it looks off. # At larger sizes it needs to be further away because of Z-fighting and LOD imprecision. var distance := clampf(size / 50.0, 0.1, 1.0) _material.set_shader_parameter(&"u_distance_from_ground", distance) #func set_shape(shape_image): # set_size(shape_image.get_width()) func _on_terrain_transform_changed(terrain_global_trans: Transform3D): var inv = terrain_global_trans.affine_inverse() _material.set_shader_parameter("u_terrain_inverse_transform", inv) var normal_basis = terrain_global_trans.basis.inverse().transposed() _material.set_shader_parameter("u_terrain_normal_basis", normal_basis) func set_terrain(terrain: HTerrain): if _terrain == terrain: return if _terrain != null: _terrain.transform_changed.disconnect(_on_terrain_transform_changed) _mesh_instance.exit_world() # _debug_mesh_instance.exit_world() _terrain = terrain if _terrain != null: _terrain.transform_changed.connect(_on_terrain_transform_changed) _on_terrain_transform_changed(_terrain.get_internal_transform()) _mesh_instance.enter_world(terrain.get_world_3d()) # _debug_mesh_instance.enter_world(terrain.get_world_3d()) update_visibility() func set_position(p_local_pos: Vector3): assert(_terrain != null) assert(typeof(p_local_pos) == TYPE_VECTOR3) # Set custom AABB (in local cells) because the decal is displaced by shader var data := _terrain.get_data() if data != null: var r := _mesh.size / 2 var aabb := data.get_region_aabb( \ int(p_local_pos.x - r.x), \ int(p_local_pos.z - r.y), \ int(2 * r.x), \ int(2 * r.y)) aabb.position = Vector3(-r.x, aabb.position.y, -r.y) _mesh.custom_aabb = aabb #_debug_mesh.size = aabb.size var trans := Transform3D(Basis(), p_local_pos) var terrain_gt := _terrain.get_internal_transform() trans = terrain_gt * trans _mesh_instance.set_transform(trans) # _debug_mesh_instance.set_transform(trans) # This is called very often so it should be cheap func update_visibility(): var heightmap = _get_heightmap(_terrain) if heightmap == null: # I do this for refcounting because heightmaps are large resources _material.set_shader_parameter("u_terrain_heightmap", null) _mesh_instance.set_visible(false) # _debug_mesh_instance.set_visible(false) else: _material.set_shader_parameter("u_terrain_heightmap", heightmap) _mesh_instance.set_visible(true) # _debug_mesh_instance.set_visible(true) func _get_heightmap(terrain): if terrain == null: return null var data = terrain.get_data() if data == null: return null return data.get_texture(HTerrainData.CHANNEL_HEIGHT)