extends Node3D ## Swap only when the real Moon is about to leave the far plane. ## At handover the disc matches the real angular size. It shrinks with distance. ## A late boost only kicks in when the disc would become a speck (Earth). @export var real_moon_path: NodePath = ^".." @export var surface_high_path: NodePath = ^"../SurfaceHigh" @export var impostor_mesh_path: NodePath = ^"ImposterSphere" @export var switch_distance: float = 850000.0 @export var visual_distance: float = 280000.0 @export var real_radius: float = 173700.0 @export var far_boost_start: float = 2500000.0 @export var far_boost_end: float = 20000000.0 @export var far_boost: float = 5.0 var _real_moon: Node3D var _surface_high: Node3D var _surface_low: Node3D var _mesh: MeshInstance3D var _base_radius: float = 0.5 func _ready() -> void: _real_moon = get_node_or_null(real_moon_path) as Node3D _surface_high = get_node_or_null(surface_high_path) as Node3D if _surface_high == null and _real_moon: _surface_high = _real_moon.get_node_or_null("SurfaceHigh") as Node3D if _real_moon: _surface_low = _real_moon.get_node_or_null("SurfaceLow") as Node3D _mesh = get_node_or_null(impostor_mesh_path) as MeshInstance3D if _mesh == null: _mesh = find_child("ImposterSphere", true, false) as MeshInstance3D if _mesh: _mesh.top_level = true _mesh.visible = false _mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF if _mesh.mesh is SphereMesh: var sm := _mesh.mesh as SphereMesh _base_radius = maxf(sm.radius, 0.5) sm.radial_segments = maxi(sm.radial_segments, 32) sm.rings = maxi(sm.rings, 16) var mat := _mesh.get_active_material(0) if mat == null: mat = _mesh.material_override if mat is StandardMaterial3D: var copy := (mat as StandardMaterial3D).duplicate() as StandardMaterial3D copy.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED _mesh.material_override = copy print("MoonImposter ready switch=", switch_distance, " mesh=", _mesh) set_process(true) func _hide_real(hide: bool) -> void: if _surface_high: _surface_high.visible = not hide if _surface_low: _surface_low.visible = not hide func _process(_delta: float) -> void: if _real_moon == null or _mesh == null: return var viewer: Node3D = FloatingOrigin.focus if viewer == null or not is_instance_valid(viewer): _mesh.visible = false return if _real_moon.has_meta("hide_crust") and bool(_real_moon.get_meta("hide_crust")): _mesh.visible = false _hide_real(true) return var to_moon: Vector3 = _real_moon.global_position - viewer.global_position var dist: float = to_moon.length() if dist < switch_distance: _mesh.visible = false _hide_real(false) return _hide_real(true) var dir: Vector3 = to_moon / maxf(dist, 1.0) _mesh.global_position = viewer.global_position + dir * visual_distance var exact: float = real_radius * (visual_distance / dist) var t: float = 0.0 if dist > far_boost_start: t = clampf((dist - far_boost_start) / maxf(far_boost_end - far_boost_start, 1.0), 0.0, 1.0) var visual_radius: float = exact * lerpf(1.0, far_boost, t) visual_radius = clampf(visual_radius, 200.0, visual_distance * 0.85) _mesh.scale = Vector3.ONE * (visual_radius / _base_radius) _mesh.visible = true