From 5dd37e32d7da6625c13fc32fe1f47de8d85ba2a7 Mon Sep 17 00:00:00 2001 From: Slyphax Date: Thu, 15 Oct 2026 19:21:25 +0000 Subject: [PATCH] =?UTF-8?q?M2-Karte=20t=5Fd8be9ed7:=20Struktur-Objekte=20i?= =?UTF-8?q?m=20Editor.=20Bibliothek=20+15=20Objekte:=20bruecke=5Fholz=20(B?= =?UTF-8?q?ruecke=20Holz),=20hoehleneingang,=20felsbogen=20=E2=80=94=20deu?= =?UTF-8?q?tsche=20Namen,=20Gruppe=20'bauwerke',=20Thumbnails=20gerendert.?= =?UTF-8?q?=20mesh=5Fshapes:=20SHAPE=5FCOMPOUND-Platzhalter=20=E2=80=94=20?= =?UTF-8?q?EIN=20Mesh=20aus=20allen=20Verbund-Teilen,=20in=20Fusspunkt-Koo?= =?UTF-8?q?rdinaten=20gebacken=20(pos-Versatz=20+=20rot=5Fdeg),=20so=20sin?= =?UTF-8?q?d=20Rampe/Durchgaenge=20im=20Editor=20sichtbar=20wie=20die=20ec?= =?UTF-8?q?hte=20Kollision;=20neu=20center=5Fheight()=20fuer=20das=20Drehz?= =?UTF-8?q?entrum=20der=20Auswahl=20(map=5Fview=5Fbuilder=20nutzt=20es).?= =?UTF-8?q?=20test=5Flibrary:=2015=20Objekte,=20Undo/Redo-Zaehler=20dynami?= =?UTF-8?q?siert=20->=20Suite=20334=20Checks=20gruen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/objects.json | 5 +++- library/thumbs/bruecke_holz.png | Bin 0 -> 1063 bytes library/thumbs/felsbogen.png | Bin 0 -> 941 bytes library/thumbs/hoehleneingang.png | Bin 0 -> 1001 bytes src/editor/library_browser.gd | 6 ++++- src/viewport/map_view_builder.gd | 2 +- src/viewport/mesh_shapes.gd | 39 +++++++++++++++++++++++++++++- tests/test_library.gd | 4 +-- 8 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 library/thumbs/bruecke_holz.png create mode 100644 library/thumbs/felsbogen.png create mode 100644 library/thumbs/hoehleneingang.png 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 0000000000000000000000000000000000000000..05e259a314f8c0bb322720a0a1f70538f00d1dfc GIT binary patch literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>V1DT7;uumf z=k4A7S+@gZj(&U`#4*o?#iqLG#ifh>6AisjNbp43m;?k%JvykSx}tfZ?5%}&Z%scb zeIj_8jwDm$oQkaW0@=S?bxvQK`p$Cx*^g_re$mf-}w53iD8;E`z3b!ug5;r z*8i-Vr(I<>`{D8R_Os__cxo`6FfLjA;=}H{dJR9$1(=>=6S#A$@l9jAzut$JpST=; zeGu5mF&f)(xUc3JF2gc*_%;tMhJyNfeukNfGfFGhmocooz3=_+3==8KbK9)euo_76e*E)C=J(I3-`8F!-0XCM zE9L3~#s|-yvF(k^|9OjNe_yzL!bi;qZ@w(~VEFC(cjxu<=gmvXzVMtG+O(ElzxN_SUvHR!Y~WNF7Kq;n{nRYvIkDNAKR{sXt#N`=E8Itb)hx#@Mht zrqewP0UWC%s<||*nH&^VHvW>3=mmTKDp!{+gowTxQme^JE>I@5Vf3Vu;)S^m0P^`bmr)m0HF8 r3S}A$MO+PYm=u;XoY15<^FN~pZv%6U%wGdw&S&s+^>bP0l+XkK8t}@s literal 0 HcmV?d00001 diff --git a/library/thumbs/felsbogen.png b/library/thumbs/felsbogen.png new file mode 100644 index 0000000000000000000000000000000000000000..4011678b04dc586686c2f5f00d04b2859c52d25d GIT binary patch literal 941 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>U{3dRaSW-L z^Y-rEyvqp^Z4cEuG}$l6Fmo%XA31S|Dd@Dz#DtfZ8oicE+pe$Z;a8A7{p68_->W0` z_kaJ~slQU_tw3be`#t|WL&e_TtFI8t+3Q)$;L*)+MzFz>$-tfA8OH$`#u>^C#ViSZ z)D^6r{fpt)hu6Qd-lxZ}TPG~zS<@Z!KOp7yg!2#O+Cd-8>-U6qS946y=E^^xoWiY=edzEavCcd%%uPld|erF)nsq-|on<+E3`2*ljRc&lfp z=(B=bGvBgh9Nv1!ZZ(5e%ZKshyO<|%900S8&pa(==wwh6efvjW@IZNT%#`6KTfw6gw4&6$PI8Fuos7t8NEqPbx6t0@7j20+tt o8qU4CouCI&`0WODlJIYK`^PNX3xxJL0`nh(r>mdKI;Vst03^POg8%>k literal 0 HcmV?d00001 diff --git a/library/thumbs/hoehleneingang.png b/library/thumbs/hoehleneingang.png new file mode 100644 index 0000000000000000000000000000000000000000..a3dde3869575bcbb015ef4251352e74a580f1195 GIT binary patch literal 1001 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>U|#I$;uumf z=k1-1eU}3SS|27K(N=ly;Cxcrk)2gUK~Ssdh**F_Bg+(J*5n-@ob?~D-v(Knu_uFM(1kbEc z`X9bAnf-6s=Dim^-*5>ZxR5B{|F~}Vd%K!hMKb1hSSRpTy)k$$qRO+G@lLL}E4xkW zaSb1q4?iFOzU00`@^zpH|AVuu#Tgo|W`%x|=-w=J?C~Xbk6FtY4+uNmzosqNI+J12 zvP{44|Grkm{xCUxKUPXFxFXGvP{OQ} z`XWrCXlr%Me}+nio%CI=WeB%F^H+& zXJBw5juU=cYGs+6M<%=l$5r^^rL zZD&Fl=DBbNgCf&5VxJ(31kZuP#Rp`bZ~VZ8H~C;+KjaZVBk}$`@>4EQ;U%U+dS;i*5WC+zcA>2Sdc# z`7L-DG=vYDhVQ@nkonv^#$R_H?OgC)^1x&JUlNCE84mmrEMcBuRP(yoKjt3Ot~Ive z6_R!WDa=ov-C+C4CQ<#hS_;hd%45zy%+AEda7O&#jMeuae`s95$Z$H3X+2QTj`@q^ z@0bhQ-Y!^u|MP>%8>AJ?%cM8($SZI%%=kV{AoT~sN! 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 ----