diff --git a/library/objects.json b/library/objects.json index 44c8208..9be9547 100644 --- a/library/objects.json +++ b/library/objects.json @@ -13,6 +13,9 @@ "boulder_small": { "poly": 96, "collision": { "shape": "sphere", "radius": 0.8 } }, "tree_pine": { "poly": 180, "collision": { "shape": "cylinder", "radius": 0.4, "height": 12.0 } }, "bush_small": { "poly": 64, "collision": { "shape": "sphere", "radius": 0.8 } }, - "fass": { "poly": 128, "collision": { "shape": "cylinder", "radius": 0.31, "height": 0.88 } } + "fass": { "poly": 128, "collision": { "shape": "cylinder", "radius": 0.31, "height": 0.88 } }, + "bruecke_holz": { "poly": 228, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [0, 0.85, 0], "size": [2, 0.3, 3] }, { "shape": "box", "pos": [0, 0.361, -2.806], "size": [2, 0.3, 2.89], "rot_deg": [21.8, 0, 0] }, { "shape": "box", "pos": [0, 0.361, 2.806], "size": [2, 0.3, 2.89], "rot_deg": [-21.8, 0, 0] }, { "shape": "box", "pos": [-0.93, 1.5, 0], "size": [0.14, 0.12, 3] }, { "shape": "box", "pos": [-0.93, 0.964, -2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [21.8, 0, 0] }, { "shape": "box", "pos": [-0.93, 0.964, 2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [-21.8, 0, 0] }, { "shape": "box", "pos": [0.93, 1.5, 0], "size": [0.14, 0.12, 3] }, { "shape": "box", "pos": [0.93, 0.964, -2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [21.8, 0, 0] }, { "shape": "box", "pos": [0.93, 0.964, 2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [-21.8, 0, 0] }] } }, + "hoehleneingang": { "poly": 204, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [-1.8, 1.6, 0], "size": [1.4, 3.2, 4] }, { "shape": "box", "pos": [1.8, 1.6, 0], "size": [1.4, 3.2, 4] }, { "shape": "box", "pos": [0, 2.8, 0], "size": [5, 0.8, 4] }, { "shape": "box", "pos": [0, 1.2, 1.8], "size": [2.2, 2.4, 0.4] }] } }, + "felsbogen": { "poly": 156, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [-1.9, 1.3, 0], "size": [1.2, 2.6, 2.4] }, { "shape": "box", "pos": [1.9, 1.3, 0], "size": [1.2, 2.6, 2.4] }, { "shape": "box", "pos": [0, 3.1, 0], "size": [5, 1, 2.4] }] } } } } diff --git a/library/thumbs/bruecke_holz.png b/library/thumbs/bruecke_holz.png new file mode 100644 index 0000000..05e259a Binary files /dev/null and b/library/thumbs/bruecke_holz.png differ diff --git a/library/thumbs/felsbogen.png b/library/thumbs/felsbogen.png new file mode 100644 index 0000000..4011678 Binary files /dev/null and b/library/thumbs/felsbogen.png differ diff --git a/library/thumbs/hoehleneingang.png b/library/thumbs/hoehleneingang.png new file mode 100644 index 0000000..a3dde38 Binary files /dev/null and b/library/thumbs/hoehleneingang.png differ diff --git a/src/editor/library_browser.gd b/src/editor/library_browser.gd index 3da8372..ffbf9bd 100644 --- a/src/editor/library_browser.gd +++ b/src/editor/library_browser.gd @@ -22,6 +22,9 @@ const NAMES := { "boulder_small": "Kleiner Felsen", "tree_pine": "Nadelbaum", "bush_small": "Strauch", + "bruecke_holz": "Brücke (Holz)", + "hoehleneingang": "Höhleneingang", + "felsbogen": "Felsbogen", } ## Gruppen für den Bibliothek-Browser (Anzeige-Reihenfolge = Reihenfolge hier). @@ -34,7 +37,8 @@ const GROUP_LABELS := { } const GROUP_IDS := { "kisten": ["crate_wood", "crate_metal", "barrier_sandbag", "wall_concrete"], - "bauwerke": ["bunker_concrete", "watchtower_wood", "fence_metal"], + "bauwerke": ["bunker_concrete", "watchtower_wood", "fence_metal", + "bruecke_holz", "hoehleneingang", "felsbogen"], "natur": ["tree_pine", "bush_small", "rock_large", "boulder_small"], } diff --git a/src/viewport/map_view_builder.gd b/src/viewport/map_view_builder.gd index 28acf42..2320253 100644 --- a/src/viewport/map_view_builder.gd +++ b/src/viewport/map_view_builder.gd @@ -195,7 +195,7 @@ func _build_objects(objects: Array, manifest: Dictionary) -> void: marker.add_child(mi) var half := (mesh.get_aabb().size * 0.5) as Vector3 marker.set_meta("pick_radius", half.length() + maxf(0.6, half.y * 0.25)) - marker.set_meta("half_height", MeshShapes.half_height(mesh)) + marker.set_meta("half_height", MeshShapes.center_height(mesh)) object_markers.append(marker) add_child(marker) diff --git a/src/viewport/mesh_shapes.gd b/src/viewport/mesh_shapes.gd index f5cdeb5..32085f0 100644 --- a/src/viewport/mesh_shapes.gd +++ b/src/viewport/mesh_shapes.gd @@ -7,6 +7,7 @@ class_name MeshShapes const SHAPE_BOX := "box" const SHAPE_SPHERE := "sphere" const SHAPE_CYLINDER := "cylinder" +const SHAPE_COMPOUND := "compound" static func make_placeholder(shape: String, col: Dictionary, scale: float) -> Mesh: @@ -23,6 +24,33 @@ static func make_placeholder(shape: String, col: Dictionary, scale: float) -> Me 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]) @@ -32,6 +60,7 @@ static func make_placeholder(shape: String, col: Dictionary, scale: float) -> Me ## 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 @@ -39,4 +68,12 @@ static func half_height(mesh: Mesh) -> float: return (mesh as CylinderMesh).height * 0.5 if mesh is SphereMesh: return (mesh as SphereMesh).radius - return 0.0 \ No newline at end of file + 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 \ No newline at end of file diff --git a/tests/test_library.gd b/tests/test_library.gd index aff6bdd..c046b91 100644 --- a/tests/test_library.gd +++ b/tests/test_library.gd @@ -52,7 +52,7 @@ func _test_manifest_load() -> void: var manifest := _load_manifest() _check(not manifest.is_empty(), "Manifest lädt und ist ein Objekt") var lib: Dictionary = manifest.get("objects", {}) - _check(lib.size() == 12, "Katalog hat die 12 etablierten Objekte (ist %d)" % lib.size()) + _check(lib.size() == 15, "Katalog hat die 15 etablierten Objekte (ist %d)" % lib.size()) var all_ok := true for oid in lib.keys(): var entry: Dictionary = lib[oid] @@ -254,7 +254,7 @@ func _test_crud_undo_all_objects() -> void: all_ok = false if first: first = false - _check(all_ok, "alle 11 Objekte: setzen/drehen/verschieben/löschen je mit Undo+Redo sauber") + _check(all_ok, "alle %d Objekte: setzen/drehen/verschieben/löschen je mit Undo+Redo sauber" % lib.size()) # -------------------------------------------------------- svmap-Roundtrip ----