Files
Brachistochrone/Scenes/Scripts/planet_shell.gd
T
scorpion21211-cmd 72ca74c67f Initial commit
2026-10-03 23:29:41 -04:00

135 lines
4.4 KiB
GDScript

@tool
extends Node3D
## Hollow planet collision. Attach to Moon or Earth.
## SphereShape3D is a SOLID ball — anything inside the radius gets ejected.
## This builds a concave surface (triangles only) with an optional station hole.
## Visual mesh stays on SurfaceHigh. Collision matches that radius.
@export var surface_mesh_path: NodePath = ^"SurfaceHigh"
@export var old_body_path: NodePath = ^"MoonBody"
@export var hole_anchor_path: NodePath = ^"LovellAnchor"
@export var hole_radius_m: float = 205.0
@export var segments: int = 48
@export var rings: int = 32
@export var rebuild_shell: bool = false:
set(v):
rebuild_shell = false
if v:
build()
func _ready() -> void:
build()
func build() -> void:
var radius: float = _visual_radius()
if radius < 10.0:
push_warning("PlanetShell: no radius from SurfaceHigh")
return
_disable_solid_body()
var hole_dir := Vector3.ZERO
var hole_cos := 1.0
var anchor := get_node_or_null(hole_anchor_path) as Node3D
if anchor and hole_radius_m > 1.0:
var to_a: Vector3 = anchor.global_position - global_position
if to_a.length() > 1.0:
hole_dir = global_transform.basis.inverse() * to_a.normalized()
hole_dir = hole_dir.normalized()
var ang: float = atan(hole_radius_m / radius)
hole_cos = cos(ang)
var faces := _make_faces(radius, hole_dir, hole_cos)
var body := get_node_or_null("ShellBody") as StaticBody3D
if body == null:
body = StaticBody3D.new()
body.name = "ShellBody"
add_child(body)
body.owner = _scene_root()
body.transform = Transform3D()
body.collision_layer = 1
body.collision_mask = 1
var col := body.get_node_or_null("CollisionShape3D") as CollisionShape3D
if col == null:
col = CollisionShape3D.new()
col.name = "CollisionShape3D"
body.add_child(col)
col.owner = body.owner
var shape := ConcavePolygonShape3D.new()
shape.set_faces(faces)
col.shape = shape
col.transform = Transform3D()
print("PlanetShell ", name, " radius=", radius, " faces=", faces.size() / 3, " hole=", hole_dir != Vector3.ZERO, " anchor=", anchor)
func _visual_radius() -> float:
var high := get_node_or_null(surface_mesh_path) as MeshInstance3D
if high and high.mesh is SphereMesh:
var sm := high.mesh as SphereMesh
var rad: float = sm.radius
if sm.height > rad * 1.5:
rad = sm.height * 0.5
var scl: float = maxf(high.scale.x, maxf(high.scale.y, high.scale.z))
rad *= scl
if rad > 100.0:
return rad
return 173700.0
func _disable_solid_body() -> void:
var old := get_node_or_null(old_body_path)
if old == null:
return
var col := old.get_node_or_null("CollisionShape3D") as CollisionShape3D
if col:
col.disabled = true
if old is CollisionObject3D:
(old as CollisionObject3D).collision_layer = 0
(old as CollisionObject3D).collision_mask = 0
print("PlanetShell: MoonBody DISABLED (solid sphere ejects)")
func _make_faces(radius: float, hole_dir: Vector3, hole_cos: float) -> PackedVector3Array:
var faces := PackedVector3Array()
var segs: int = maxi(12, segments)
var rgs: int = maxi(8, rings)
var use_hole: bool = hole_dir.length_squared() > 0.5
for lat in range(rgs):
var t0: float = float(lat) / float(rgs) * PI - PI * 0.5
var t1: float = float(lat + 1) / float(rgs) * PI - PI * 0.5
var y0: float = sin(t0)
var y1: float = sin(t1)
var r0: float = cos(t0)
var r1: float = cos(t1)
for lon in range(segs):
var a0: float = float(lon) / float(segs) * TAU
var a1: float = float(lon + 1) / float(segs) * TAU
var p00 := Vector3(r0 * cos(a0), y0, r0 * sin(a0)) * radius
var p10 := Vector3(r0 * cos(a1), y0, r0 * sin(a1)) * radius
var p01 := Vector3(r1 * cos(a0), y1, r1 * sin(a0)) * radius
var p11 := Vector3(r1 * cos(a1), y1, r1 * sin(a1)) * radius
_add_tri(faces, p00, p01, p11, hole_dir, hole_cos, use_hole)
_add_tri(faces, p00, p11, p10, hole_dir, hole_cos, use_hole)
return faces
func _add_tri(faces: PackedVector3Array, a: Vector3, b: Vector3, c: Vector3, hole_dir: Vector3, hole_cos: float, use_hole: bool) -> void:
if use_hole:
var mid: Vector3 = (a + b + c) / 3.0
if mid.length_squared() < 0.001:
return
if mid.normalized().dot(hole_dir) >= hole_cos:
return
# Both windings. Concave shells are one-sided; a flipped face is a hole you fall through.
faces.append(a)
faces.append(b)
faces.append(c)
faces.append(a)
faces.append(c)
faces.append(b)
func _scene_root() -> Node:
if Engine.is_editor_hint() and get_tree() and get_tree().edited_scene_root:
return get_tree().edited_scene_root
return owner if owner else self