Files
dauphincraft/scripts/dolphin/DolphinMeshBuilder.gd
Floppyrj45 e1fda4d393 feat(dolphin): proper 3D mesh + swim animations (procedural)
- DolphinMeshBuilder.gd: ArrayMesh procédural, 14 sections elliptiques,
  nageoire dorsale, pectorales, queue horizontale (caudale), gradient
  dos #4a6d82 / ventre #d8e2ea
- Animations: battement caudale (sin), lean virage, flap pectorales,
  bob corps — fréquence x2 en boost
- Dolphin.tscn: remplace 6 capsules CSG par DolphinMesh + DolphinMeshBuilder
- DolphinController: corrige double-déclaration speed, pilote animate()
- CREDITS.md: mesh procédural CC0 original

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 18:11:03 +02:00

299 lines
9.9 KiB
GDScript

@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)