scopedvr-map-core/viewport/mesh_shapes.gd
2026-11-01 19:31:33 +01:00

88 lines
No EOL
3.5 KiB
GDScript

extends RefCounted
class_name MeshShapes
## Platzhalter-Meshes für Bibliotheks-Objekte (Form aus dem Kollider-Eintrag,
## wie der Server-Sichtbarkeit) — geteilt von Viewport-Bauer und Ghost-Preview,
## damit Objekte im Editor überall identisch aussehen.
const SHAPE_BOX := "box"
const SHAPE_SPHERE := "sphere"
const SHAPE_CYLINDER := "cylinder"
const SHAPE_COMPOUND := "compound"
const SHAPE_NONE := "none"
static func make_placeholder(shape: String, col: Dictionary, scale: float) -> Mesh:
match shape:
SHAPE_NONE:
# Rein dekorativ (Natur-Update, t_7cf80244): kein Kollider — aber
# im Editor trotzdem sichtbar/wählbar: flache Fußpunkt-Scheibe.
var d := CylinderMesh.new()
d.top_radius = 0.4 * scale
d.bottom_radius = d.top_radius
d.height = maxf(0.02, 0.02 * scale)
return d
SHAPE_SPHERE:
var s := SphereMesh.new()
var r := maxf(0.01, float(col.get("radius", 1.0)) * scale)
s.radius = r
s.height = r * 2.0
return s
SHAPE_CYLINDER:
var c := CylinderMesh.new()
c.top_radius = maxf(0.01, float(col.get("radius", 1.0)) * scale)
c.bottom_radius = c.top_radius
c.height = maxf(0.01, float(col.get("height", 1.0)) * scale)
return c
SHAPE_COMPOUND:
# Struktur-Objekte (t_d8be9ed7): EIN Mesh aus allen Verbund-Teilen,
# gebacken in Fußpunkt-Koordinaten (Teil-pos = Versatz vom Fußpunkt,
# rot_deg = Euler-Grad) — Rampe/Durchgänge also sichtbar wie die
# echte Kollision. half_height() liefert dafür 0 (schon gebacken).
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
st.set_smooth_group(-1)
for teil_v in col.get("parts", []):
var teil: Dictionary = teil_v if teil_v is Dictionary else {}
var teil_mesh := make_placeholder(str(teil.get("shape", "box")), teil, scale)
if teil_mesh == null:
continue
var off: Array = teil.get("pos", [0.0, 0.0, 0.0])
var rot: Array = teil.get("rot_deg", [0.0, 0.0, 0.0])
var xf := Transform3D(
Basis.from_euler(Vector3(deg_to_rad(float(rot[0])), deg_to_rad(float(rot[1])), deg_to_rad(float(rot[2])))),
Vector3(float(off[0]), float(off[1]), float(off[2])) * scale)
# PrimitiveMesh → Roht_arrays (MeshDataTool bräuchte ArrayMesh).
var arrays: Array = teil_mesh.surface_get_arrays(0)
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
var norms: PackedVector3Array = arrays[Mesh.ARRAY_NORMAL]
var idx: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
for ii in idx:
st.set_normal(xf.basis * norms[ii])
st.add_vertex(xf * verts[ii])
return st.commit()
_:
var b := BoxMesh.new()
var size: Array = col.get("size", [1.0, 1.0, 1.0])
b.size = Vector3(maxf(0.01, float(size[0])), maxf(0.01, float(size[1])),
maxf(0.01, float(size[2]))) * scale
return b
## Halbe Höhe des Meshes über dem Fußpunkt (Mesh-Ursprung = Objekt-Fuß).
## Compound-ArrayMeshes sind bereits in Fußpunkt-Koordinaten gebacken → 0.
static func half_height(mesh: Mesh) -> float:
if mesh is BoxMesh:
return (mesh as BoxMesh).size.y * 0.5
if mesh is CylinderMesh:
return (mesh as CylinderMesh).height * 0.5
if mesh is SphereMesh:
return (mesh as SphereMesh).radius
return 0.0
## Höhe des Mesh-MITTELPUNKTS über dem Fußpunkt — für das Drehzentrum der
## Objekt-Auswahl. Bei Grundformen = half_height (Mesh ist zentriert); beim
## Compound-ArrayMesh = AABB-Mitte (schon in Fußpunkt-Koordinaten gebacken).
static func center_height(mesh: Mesh) -> float:
var aabb := mesh.get_aabb()
return half_height(mesh) + aabb.position.y + aabb.size.y * 0.5