@tool extends MeshInstance3D ## Procedural dolphin mesh — built from elliptic cross-sections along the body axis. ## Dos : #4a6d82 Ventre : #d8e2ea ## Aucun asset externe — CC0 absolu. const NUM_SECTIONS: int = 14 # sections along body axis (Z) const NUM_RING_VERTS: int = 12 # vertices per ring ellipse const BODY_LENGTH: float = 2.0 # total body length (metres) # Body profile: [z_norm (0..1), rx, ry] — normalised radii # z_norm 0 = snout tip, 1 = tail root const PROFILE: Array = [ [0.00, 0.04, 0.03], # snout tip [0.06, 0.12, 0.09], # rostrum [0.14, 0.22, 0.17], # head front [0.22, 0.34, 0.28], # melon / forehead [0.32, 0.40, 0.33], # max girth front [0.42, 0.38, 0.30], # chest (pecs here) [0.52, 0.32, 0.25], # mid body [0.62, 0.26, 0.20], # post-dorsal [0.70, 0.20, 0.15], # waist start [0.78, 0.14, 0.11], # peduncle [0.85, 0.09, 0.07], # narrow peduncle [0.91, 0.06, 0.04], # tail stock [0.96, 0.04, 0.03], # tail root [1.00, 0.02, 0.015], # tail tip ] const COLOR_BACK: Color = Color(0.290, 0.427, 0.510, 1.0) # #4a6d82 const COLOR_BELLY: Color = Color(0.847, 0.886, 0.918, 1.0) # #d8e2ea var _tail_pivot: Node3D # animated separately var _dorsal_pivot: Node3D var _pec_left: Node3D var _pec_right: Node3D func _ready() -> void: _build_body() _build_dorsal_fin() _build_pectoral_fins() _build_tail() _build_caudal_fluke() # --------------------------------------------------------------------------- # Body # --------------------------------------------------------------------------- func _build_body() -> void: var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) st.set_smooth_group(0) var rings: Array = [] for i in range(NUM_SECTIONS): var prof = PROFILE[i] var z: float = (prof[0] as float) * BODY_LENGTH - BODY_LENGTH * 0.5 var rx: float = prof[1] as float var ry: float = prof[2] as float var ring: Array = [] for j in range(NUM_RING_VERTS): var angle: float = (float(j) / float(NUM_RING_VERTS)) * TAU var lx: float = cos(angle) * rx var ly: float = sin(angle) * ry # Colour: belly on the bottom (ly < 0), back on top var t: float = clamp((ly / ry + 1.0) * 0.5, 0.0, 1.0) # 0=belly, 1=back var col: Color = COLOR_BELLY.lerp(COLOR_BACK, t) ring.append({"pos": Vector3(lx, ly, z), "col": col, "angle": angle}) rings.append(ring) # Stitch rings for i in range(NUM_SECTIONS - 1): var r0: Array = rings[i] var r1: Array = rings[i + 1] for j in range(NUM_RING_VERTS): var jn: int = (j + 1) % NUM_RING_VERTS var v00 = r0[j] var v01 = r0[jn] var v10 = r1[j] var v11 = r1[jn] # Tri 1 st.set_color(v00["col"]); st.add_vertex(v00["pos"]) st.set_color(v10["col"]); st.add_vertex(v10["pos"]) st.set_color(v11["col"]); st.add_vertex(v11["pos"]) # Tri 2 st.set_color(v00["col"]); st.add_vertex(v00["pos"]) st.set_color(v11["col"]); st.add_vertex(v11["pos"]) st.set_color(v01["col"]); st.add_vertex(v01["pos"]) # Cap snout var snout_center := Vector3(0.0, 0.0, PROFILE[0][0] * BODY_LENGTH - BODY_LENGTH * 0.5) for j in range(NUM_RING_VERTS): var jn: int = (j + 1) % NUM_RING_VERTS var v0 = rings[0][j] var v1 = rings[0][jn] st.set_color(COLOR_BACK); st.add_vertex(snout_center) st.set_color(v1["col"]); st.add_vertex(v1["pos"]) st.set_color(v0["col"]); st.add_vertex(v0["pos"]) st.generate_normals() var mat := StandardMaterial3D.new() mat.vertex_color_use_as_albedo = true mat.roughness = 0.6 mat.metallic = 0.1 mesh = st.commit() set_surface_override_material(0, mat) # --------------------------------------------------------------------------- # Dorsal fin # --------------------------------------------------------------------------- func _build_dorsal_fin() -> void: var pivot := Node3D.new() pivot.name = "DorsalFin" # Position at ~55% along body, on top pivot.position = Vector3(0.0, 0.28, -BODY_LENGTH * 0.5 + BODY_LENGTH * 0.52) add_child(pivot) _dorsal_pivot = pivot var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) # Dorsal fin: curved triangle, base front-back, tip up var pts: Array[Vector3] = [ Vector3(-0.05, 0.0, 0.22), # base front Vector3( 0.05, 0.0, 0.22), Vector3(-0.03, 0.0, -0.18), # base rear Vector3( 0.03, 0.0, -0.18), Vector3( 0.00, 0.38, 0.05), # tip (slightly forward) ] var col := COLOR_BACK # Front face _add_tri(st, pts[0], pts[4], pts[2], col) # Back face (reverse winding for double-sided appearance) _add_tri(st, pts[1], pts[3], pts[4], col) # Bridge between left and right at base _add_quad(st, pts[0], pts[1], pts[4], pts[4], col) _add_quad(st, pts[2], pts[4], pts[3], pts[4], col) st.generate_normals() var mi := MeshInstance3D.new() mi.name = "DorsalFinMesh" var mat := StandardMaterial3D.new() mat.albedo_color = COLOR_BACK mat.roughness = 0.7 mat.cull_mode = BaseMaterial3D.CULL_DISABLED mi.mesh = st.commit() mi.set_surface_override_material(0, mat) pivot.add_child(mi) # --------------------------------------------------------------------------- # Pectoral fins # --------------------------------------------------------------------------- func _build_pectoral_fins() -> void: _build_one_pec_fin(false) # left _build_one_pec_fin(true) # right func _build_one_pec_fin(right_side: bool) -> void: var side: float = 1.0 if right_side else -1.0 var pivot := Node3D.new() pivot.name = "PecFinRight" if right_side else "PecFinLeft" # At ~42% along body, sides of thorax var base_x: float = 0.36 * side pivot.position = Vector3(base_x, -0.05, -BODY_LENGTH * 0.5 + BODY_LENGTH * 0.42) add_child(pivot) if right_side: _pec_right = pivot else: _pec_left = pivot var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) # Fin sweeps outward and slightly backward, flattened paddle shape var tip := Vector3(side * 0.30, -0.12, -0.12) var base_front := Vector3(0.0, 0.0, 0.12) var base_rear := Vector3(0.0, 0.0, -0.12) var col := COLOR_BACK _add_tri(st, base_front, tip, base_rear, col) _add_tri(st, base_rear, tip, base_front, col) # double-sided st.generate_normals() var mi := MeshInstance3D.new() mi.name = "PecFinMesh" var mat := StandardMaterial3D.new() mat.albedo_color = COLOR_BACK mat.roughness = 0.7 mat.cull_mode = BaseMaterial3D.CULL_DISABLED mi.mesh = st.commit() mi.set_surface_override_material(0, mat) pivot.add_child(mi) # --------------------------------------------------------------------------- # Tail peduncle pivot (for animation) # --------------------------------------------------------------------------- func _build_tail() -> void: var pivot := Node3D.new() pivot.name = "TailPivot" # Place pivot at ~85% along body pivot.position = Vector3(0.0, 0.0, -BODY_LENGTH * 0.5 + BODY_LENGTH * 0.85) add_child(pivot) _tail_pivot = pivot # --------------------------------------------------------------------------- # Caudal fluke (horizontal — dolphin/whale style) # --------------------------------------------------------------------------- func _build_caudal_fluke() -> void: if _tail_pivot == null: return var st := SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) # Horizontal bilobed fluke — symmetric left/right around X axis # Offset from tail pivot (which is at 85%); fluke extends to 100% var z_root: float = 0.0 # relative to pivot var z_tip: float = -0.22 # tail tip relative to pivot var pts: Array[Vector3] = [ Vector3( 0.0, 0.0, z_root), # centre root Vector3( 0.45, 0.0, z_root - 0.06), # right lobe root Vector3( 0.42, 0.0, z_tip), # right lobe tip Vector3( 0.0, 0.0, z_tip + 0.08), # centre notch Vector3(-0.42, 0.0, z_tip), # left lobe tip Vector3(-0.45, 0.0, z_root - 0.06), # left lobe root ] var col := COLOR_BACK # Right lobe _add_tri(st, pts[0], pts[1], pts[2], col) _add_tri(st, pts[0], pts[2], pts[3], col) # Left lobe _add_tri(st, pts[0], pts[3], pts[4], col) _add_tri(st, pts[0], pts[4], pts[5], col) # Double-sided _add_tri(st, pts[2], pts[1], pts[0], col) _add_tri(st, pts[3], pts[2], pts[0], col) _add_tri(st, pts[4], pts[3], pts[0], col) _add_tri(st, pts[5], pts[4], pts[0], col) st.generate_normals() var mi := MeshInstance3D.new() mi.name = "CaudalFluke" var mat := StandardMaterial3D.new() mat.albedo_color = COLOR_BACK mat.roughness = 0.7 mat.cull_mode = BaseMaterial3D.CULL_DISABLED mi.mesh = st.commit() mi.set_surface_override_material(0, mat) _tail_pivot.add_child(mi) # --------------------------------------------------------------------------- # Animation — called from DolphinController every _process frame # --------------------------------------------------------------------------- func animate(time: float, speed_factor: float, is_boosting: bool, turn_input: float) -> void: if _tail_pivot == null: return # Tail fluke oscillation (pitch = rotation X for horizontal fluke) var freq: float = 8.0 if is_boosting else 4.5 freq = lerp(freq * 0.4, freq, clamp(speed_factor, 0.0, 1.0)) var amplitude: float = 0.55 if is_boosting else 0.38 _tail_pivot.rotation.x = sin(time * freq) * amplitude # Secondary body wave — slight counter-rotation on body rotation.x = sin(time * freq + 0.8) * amplitude * 0.18 # Turn lean — roll body when turning (Z axis = forward, lean on X or Z) rotation.z = lerp(rotation.z, turn_input * 0.35, 0.12) # Pectoral fins flap gently if _pec_left != null: _pec_left.rotation.z = sin(time * freq * 0.5) * 0.12 if _pec_right != null: _pec_right.rotation.z = -sin(time * freq * 0.5) * 0.12 # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- func _add_tri(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, col: Color) -> void: st.set_color(col); st.add_vertex(a) st.set_color(col); st.add_vertex(b) st.set_color(col); st.add_vertex(c) func _add_quad(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, d: Vector3, col: Color) -> void: _add_tri(st, a, b, c, col) _add_tri(st, a, c, d, col)