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