Compare commits

..

No commits in common. "main" and "server-tab-bots-restart" have entirely different histories.

1203 changed files with 48357 additions and 605 deletions

5
.gitmodules vendored
View file

@ -1,5 +0,0 @@
[submodule "core"]
path = core
url = ../scopedvr-map-core.git
branch = main
ignore = untracked

View file

@ -48,14 +48,14 @@ UX-Fundament auf dem App-Skelett (t_5badf371), nach dem Chef-Grundsatz gebaut:
Neue-Map-Dialog mit 4 Pflicht-Spawns pro Team, Orbit-Viewport mit Gitter/ Neue-Map-Dialog mit 4 Pflicht-Spawns pro Team, Orbit-Viewport mit Gitter/
Boden/Wetter-Stimmung — siehe Commit 84bc081 und `svmap_io.gd`.)* Boden/Wetter-Stimmung — siehe Commit 84bc081 und `svmap_io.gd`.)*
- **Undo/Redo (`core/editor/action_history.gd`):** zentrale ActionHistory über - **Undo/Redo (`src/editor/action_history.gd`):** zentrale ActionHistory über
die svmap-Ober-Blöcke (`objects`/`terrain`/`spawn_zones` — tiefe Kopien vor die svmap-Ober-Blöcke (`objects`/`terrain`/`spawn_zones` — tiefe Kopien vor
und nach jeder Änderung, merge_tags gegen Mausrad-Tick-Fluten, Max-Stufe 100). und nach jeder Änderung, merge_tags gegen Mausrad-Tick-Fluten, Max-Stufe 100).
Strg+Z / Strg+Y UND große Buttons „↩ Rückgängig / ↪ Wiederholen" oben. Strg+Z / Strg+Y UND große Buttons „↩ Rückgängig / ↪ Wiederholen" oben.
Spätere Werkzeuge melden sich mit `snapshot(keys)` vor und Spätere Werkzeuge melden sich mit `snapshot(keys)` vor und
`commit(desc, before, after)` nach der Änderung an — Terrain-Strokes und `commit(desc, before, after)` nach der Änderung an — Terrain-Strokes und
Spawn-Zonen landen damit automatisch in derselben History. Spawn-Zonen landen damit automatisch in derselben History.
- **Objekt-Platzierung mit Ghost-Preview (`core/editor/interaction.gd`):** - **Objekt-Platzierung mit Ghost-Preview (`src/editor/interaction.gd`):**
Bibliothek links (deutsche Namen + Poly-Zahl), Klick startet den Bibliothek links (deutsche Namen + Poly-Zahl), Klick startet den
Platzier-Modus — eine transparente Vorschau schwebt am Cursor auf dem Boden Platzier-Modus — eine transparente Vorschau schwebt am Cursor auf dem Boden
(Höhe kommt aus dem Terrain), Klick setzt, ESC bricht ab (Rechte (Höhe kommt aus dem Terrain), Klick setzt, ESC bricht ab (Rechte
@ -94,7 +94,7 @@ synchron ziehen (IDs sind der Vertrag, Entscheidung 20).
Auf dem Terrain-Werkzeug (t_9aba04f1) aufgebaut, nach dem Chef-Grundsatz: Auf dem Terrain-Werkzeug (t_9aba04f1) aufgebaut, nach dem Chef-Grundsatz:
- **Bibliothek-Browser mit Gruppen (`core/editor/library_browser.gd`):** - **Bibliothek-Browser mit Gruppen (`src/editor/library_browser.gd`):**
*(Stand 25.10.2026, Military-Pack-Umstellung)* Die Bibliothek ist das *(Stand 25.10.2026, Military-Pack-Umstellung)* Die Bibliothek ist das
komplette Military-Desert-Pack (418 Modelle + 5 Kombis = 423 Objekte aus komplette Military-Desert-Pack (418 Modelle + 5 Kombis = 423 Objekte aus
`library/objects.json`), sortiert in 19 deutsche Kategorien aus dem `library/objects.json`), sortiert in 19 deutsche Kategorien aus dem
@ -106,14 +106,14 @@ Auf dem Terrain-Werkzeug (t_9aba04f1) aufgebaut, nach dem Chef-Grundsatz:
(Panzer mit Turm und Kanone, Hummer mit Rädern, Hubschrauber mit Rotoren); (Panzer mit Turm und Kanone, Hummer mit Rädern, Hubschrauber mit Rotoren);
die Einzelteile stehen zusätzlich unter „Fahrzeugteile“. Unbekannte die Einzelteile stehen zusätzlich unter „Fahrzeugteile“. Unbekannte
Kategorien (z. B. spätere Bauteil-Module) bekommen automatisch eine eigene Kategorien (z. B. spätere Bauteil-Module) bekommen automatisch eine eigene
Gruppe. Jeder Button zeigt ein 128-px-Thumbnail (`core/thumbs/*.png` — Gruppe. Jeder Button zeigt ein 128-px-Thumbnail (`library/thumbs/*.png` —
erzeugt von `tools/sync_objects.sh`/`tools/gen_thumbs.gd` und committed; erzeugt von `tools/sync_objects.sh`/`tools/gen_thumbs.gd` und committed;
fehlt ein PNG, fällt der Button aufs Text-Layout zurück). Deutsche Namen fehlt ein PNG, fällt der Button aufs Text-Layout zurück). Deutsche Namen
kommen aus `name_de` im Manifest (ohne UI → headless testbar). Kollision: kommen aus `name_de` im Manifest (ohne UI → headless testbar). Kollision:
Gebäude/Zelte/Türme exakter Trimesh (betretbar), Fahrzeuge Verbund aus Gebäude/Zelte/Türme exakter Trimesh (betretbar), Fahrzeuge Verbund aus
Boxen, Bäume nur Stamm, Dekor ohne Kollision — erzeugt vom Match-Server-Repo Boxen, Bäume nur Stamm, Dekor ohne Kollision — erzeugt vom Match-Server-Repo
(`tools/pack_registry.py`). (`tools/pack_registry.py`).
- **Snap to Ground (`core/editor/object_ops.gd`):** Ein Klick auf - **Snap to Ground (`src/editor/object_ops.gd`):** Ein Klick auf
„⚡ Alles auf den Boden setzen" holt ALLE schwebenden Objekte auf die „⚡ Alles auf den Boden setzen" holt ALLE schwebenden Objekte auf die
Terrain-Höhe (z. B. nach dem Terrain-Formen) — EIN Undo-Eintrag für die Terrain-Höhe (z. B. nach dem Terrain-Formen) — EIN Undo-Eintrag für die
ganze Aktion. Objekte, die schon auf dem Boden stehen, bleiben unangetastet. ganze Aktion. Objekte, die schon auf dem Boden stehen, bleiben unangetastet.
@ -138,14 +138,14 @@ Export-Workflow.
Auf der Objekt-Bibliothek (t_3a998ae4) aufgebaut, nach dem Chef-Grundsatz: Auf der Objekt-Bibliothek (t_3a998ae4) aufgebaut, nach dem Chef-Grundsatz:
- **Spawn-Zonen malen (`core/editor/spawn_tool.gd`):** Werkzeug - **Spawn-Zonen malen (`src/editor/spawn_tool.gd`):** Werkzeug
„🚩 Spawn-Zonen malen“ — Team Blau 🔵 / Team Rot 🔴 (TDM-Konvention: „🚩 Spawn-Zonen malen“ — Team Blau 🔵 / Team Rot 🔴 (TDM-Konvention:
IDs A…/B…, mapformat § spawn_zones), Klick setzt eine farbige flache IDs A…/B…, mapformat § spawn_zones), Klick setzt eine farbige flache
Kreis-Zone auf dem Terrain (Vorschau-Kreis folgt der Maus), Radius Kreis-Zone auf dem Terrain (Vorschau-Kreis folgt der Maus), Radius
2–30 m per Regler. Zone anklicken → auswählen + ziehen (Terrain-Höhe 2–30 m per Regler. Zone anklicken → auswählen + ziehen (Terrain-Höhe
bleibt), Entf löscht, Strg+Z nimmt JEDE Operation zurück (jede Zone = bleibt), Entf löscht, Strg+Z nimmt JEDE Operation zurück (jede Zone =
EIN History-Eintrag, Radius-Slider-Ticks sind merge-gekoppelt). EIN History-Eintrag, Radius-Slider-Ticks sind merge-gekoppelt).
- **Live-Validierung (`core/editor/spawn_ops.gd`):** Block „Spawn-Check“ - **Live-Validierung (`src/editor/spawn_ops.gd`):** Block „Spawn-Check“
rechts bleibt STÄNDIG sichtbar und spiegelt exakt die Loader-Regeln rechts bleibt STÄNDIG sichtbar und spiegelt exakt die Loader-Regeln
(V11: ≥ 4 Spawn-Punkte pro Team · V7: Objekt-IDs in der Bibliothek) — (V11: ≥ 4 Spawn-Punkte pro Team · V7: Objekt-IDs in der Bibliothek) —
z. B. „✅ Spawns Team Blau: 4 · ❌ Spawns Team Rot: 2 (mindestens 4 z. B. „✅ Spawns Team Blau: 4 · ❌ Spawns Team Rot: 2 (mindestens 4
@ -238,13 +238,13 @@ Undo für alles):
Mehrfachauswahl + Gruppieren + Gruppen-Verschieben, auf dem UX-Fundament Mehrfachauswahl + Gruppieren + Gruppen-Verschieben, auf dem UX-Fundament
(t_40d855d0) aufgebaut, nach dem Chef-Grundsatz: (t_40d855d0) aufgebaut, nach dem Chef-Grundsatz:
- **Mehrfachauswahl (`core/editor/interaction.gd`):** Umschalt+Klick fügt - **Mehrfachauswahl (`src/editor/interaction.gd`):** Umschalt+Klick fügt
ein Objekt hinzu/entfernt es, Rahmen-Aufziehen (Rubberband) über leeren ein Objekt hinzu/entfernt es, Rahmen-Aufziehen (Rubberband) über leeren
Boden wählt ALLE Objekte im Rahmen (Umschalt + Rahmen = zusätzliche zur Boden wählt ALLE Objekte im Rahmen (Umschalt + Rahmen = zusätzliche zur
Auswahl). Loser Klick auf leere Fläche hebt die Auswahl auf. ALLE Auswahl). Loser Klick auf leere Fläche hebt die Auswahl auf. ALLE
markierten Objekte färben sich orange, das Auswahl-Panel rechts listet markierten Objekte färben sich orange, das Auswahl-Panel rechts listet
sie namentlich auf (ab 9: „… und N weitere“). Projektions-Mathe liegt sie namentlich auf (ab 9: „… und N weitere“). Projektions-Mathe liegt
in `core/editor/selection_math.gd` (headless getestet). in `src/editor/selection_math.gd` (headless getestet).
- **Gruppieren:** Buttons „🔗 Gruppieren / ⛓ Gruppe lösen“ im Auswahl-Panel - **Gruppieren:** Buttons „🔗 Gruppieren / ⛓ Gruppe lösen“ im Auswahl-Panel
+ Kürzel Strg+G / Strg+Umschalt+G. Eine Gruppe braucht ≥ 2 Objekte. + Kürzel Strg+G / Strg+Umschalt+G. Eine Gruppe braucht ≥ 2 Objekte.
Gruppen-Mitglieder ziehen und drehen GEMEINSAM — ein Griff an ein Gruppen-Mitglieder ziehen und drehen GEMEINSAM — ein Griff an ein
@ -287,7 +287,7 @@ auch ungespeicherte! — sofort als Spiel in einem EIGENEN Fenster. Der Editor
bleibt offen. Im Spiel: WASD laufen, Maus umsehen, Shift sprinten, Leertaste bleibt offen. Im Spiel: WASD laufen, Maus umsehen, Shift sprinten, Leertaste
springen, ESC beendet das Test-Fenster sauber (kein Prozess-Kill nötig). springen, ESC beendet das Test-Fenster sauber (kein Prozess-Kill nötig).
- **Temp-Export (`core/editor/test_run.gd`):** Die Map wird nach - **Temp-Export (`src/editor/test_run.gd`):** Die Map wird nach
`user://testrun/<name>.svmap` exportiert (deterministischer Name aus dem `user://testrun/<name>.svmap` exportiert (deterministischer Name aus dem
Map-Namen, erneuter Testlauf überschreibt) — über dieselbe Map-Namen, erneuter Testlauf überschreibt) — über dieselbe
`SvmapIO.save_to_file`-Validierung wie das echte Speichern. Ist die Map `SvmapIO.save_to_file`-Validierung wie das echte Speichern. Ist die Map
@ -317,7 +317,7 @@ Tastenkürzel-Karte 1 von 3 (Sly-Freigabe 16.10., finale Belegung).
Shortcut-Karten — Karten 2/3 ergänzen dort. Shortcut-Karten — Karten 2/3 ergänzen dort.
- **Konventionen (alle Karten):** Tastaturcode-basiert — InputMap-Actions mit - **Konventionen (alle Karten):** Tastaturcode-basiert — InputMap-Actions mit
PHYSISCHEN `KEY_*`-Konstanten (`core/editor/shortcut_keys.gd`, registriert in PHYSISCHEN `KEY_*`-Konstanten (`src/editor/shortcut_keys.gd`, registriert in
`main._ready`, idempotent). Physische Codes = deutsche (QWERTZ) und `main._ready`, idempotent). Physische Codes = deutsche (QWERTZ) und
Windows-Layouts identisch, keine OS-Hardcodes. Jede Objekt-Operation = Windows-Layouts identisch, keine OS-Hardcodes. Jede Objekt-Operation =
EIN ActionHistory-Eintrag (Ausnahme: reine Kamera-Aktionen). EIN ActionHistory-Eintrag (Ausnahme: reine Kamera-Aktionen).
@ -327,7 +327,7 @@ Shortcut-Karten — Karten 2/3 ergänzen dort.
Orbit bleibt Default — Orbit bleibt Default —
beim Flug-Exit werden Ziel/Winkel/Abstand aus Flugposition/Blickrichtung beim Flug-Exit werden Ziel/Winkel/Abstand aus Flugposition/Blickrichtung
zurückgerechnet (kein Kamera-Sprung, getestet). Mathe pur in zurückgerechnet (kein Kamera-Sprung, getestet). Mathe pur in
`core/editor/fly_math.gd`, Kamera dünn (`core/viewport/orbit_camera.gd`). `src/editor/fly_math.gd`, Kamera dünn (`src/viewport/orbit_camera.gd`).
Konflikt-Auflösung: RMB hat ABSOLUTEN Vorrang für den Flug — in JEDEM Konflikt-Auflösung: RMB hat ABSOLUTEN Vorrang für den Flug — in JEDEM
Werkzeugzustand (Terrain, Bauen, Spawn, Platzieren; Karte t_2470de89, Werkzeugzustand (Terrain, Bauen, Spawn, Platzieren; Karte t_2470de89,
kein `rmb_filter` mehr), Mausrad im Flug gehört dem Tempo (interaction kein `rmb_filter` mehr), Mausrad im Flug gehört dem Tempo (interaction
@ -337,7 +337,7 @@ Shortcut-Karten — Karten 2/3 ergänzen dort.
Flug dreht sich nur die Blickrichtung. Flug dreht sich nur die Blickrichtung.
- **Esc** = Prioritätskette Werkzeug verlassen → Ghost-Preview abbrechen → - **Esc** = Prioritätskette Werkzeug verlassen → Ghost-Preview abbrechen →
Auswahl aufheben, als pure, headless-getestete Funktion Auswahl aufheben, als pure, headless-getestete Funktion
(`core/editor/shortcut_policy.gd`). (`src/editor/shortcut_policy.gd`).
- **Entf (DELETE)** = Auswahl SOFORT löschen ohne Rückfrage (Sly-Yolo) — - **Entf (DELETE)** = Auswahl SOFORT löschen ohne Rückfrage (Sly-Yolo) —
wirkt auf Szene-Auswahl UND Objektbaum-Markierung (Baum und Szene sind wirkt auf Szene-Auswahl UND Objektbaum-Markierung (Baum und Szene sind
synchron), Strg+Z deckt es ab. War schon mehrfach-fähig, jetzt synchron), Strg+Z deckt es ab. War schon mehrfach-fähig, jetzt

View file

@ -80,6 +80,6 @@ Roh-Modelle und Texturen weiterzugeben. Das heißt konkret:
--- ---
*Technischer Hintergrund: die Modelle werden zur Laufzeit per GLTFDocument *Technischer Hintergrund: die Modelle werden zur Laufzeit per GLTFDocument
aus dem `models/`-Ordner geladen (siehe `core/viewport/glb_mesh_store.gd`, aus dem `models/`-Ordner geladen (siehe `src/viewport/glb_mesh_store.gd`,
Quellen-Reihenfolge: Einstellungen → Nachbar-Ordner `../scopedvr-client` → Quellen-Reihenfolge: Einstellungen → Nachbar-Ordner `../scopedvr-client` →
`models/`). Erster Treffer gewinnt.* `models/`). Erster Treffer gewinnt.*

View file

@ -2,7 +2,7 @@
extends EditorPlugin extends EditorPlugin
const PIECE_SCRIPT = preload("res://addons/modular_builder/modular_piece.gd") const PIECE_SCRIPT = preload("res://addons/modular_builder/modular_piece.gd")
const DETAIL_SCRIPT = preload("res://core/vendor/modular_builder/modular_building_detail.gd") const DETAIL_SCRIPT = preload("res://addons/modular_builder/modular_building_detail.gd")
const MESH_BAKER = preload("res://addons/modular_builder/modular_mesh_baker.gd") const MESH_BAKER = preload("res://addons/modular_builder/modular_mesh_baker.gd")
const TOOL_NONE := "" const TOOL_NONE := ""
const TOOL_FOUNDATION := "foundation" const TOOL_FOUNDATION := "foundation"

View file

@ -0,0 +1,284 @@
@tool
class_name ModularBuilderDetail
extends Node3D
enum DetailType { WINDOW, DOOR, HATCH, PILLAR, FENCE, PARAPET }
enum DetailMode { OPENING, SEPARATE_OBJECT }
enum WindowStyle { SINGLE_PANE, CROSS_PANE }
enum DoorStyle { FLAT, PANELED }
@export var detail_type: DetailType = DetailType.WINDOW:
set(value):
detail_type = value
notify_property_list_changed()
_rebuild()
@export var detail_mode: DetailMode = DetailMode.OPENING:
set(value):
detail_mode = value
notify_property_list_changed()
_rebuild()
@export var center_align: bool = false:
set(value):
center_align = value
_apply_pillar_alignment()
notify_property_list_changed()
@export_storage var pillar_direction: Vector3 = Vector3.UP
@export_storage var pillar_alignment_basis: Basis = Basis.IDENTITY
@export var window_style: WindowStyle = WindowStyle.SINGLE_PANE:
set(value):
window_style = value
_rebuild()
@export var door_style: DoorStyle = DoorStyle.FLAT:
set(value):
door_style = value
_rebuild()
@export_range(0.01, 5.0, 0.001, "suffix:m") var width: float = 1.0:
set(value):
width = value
_rebuild()
@export_range(0.2, 5.0, 0.001, "suffix:m") var height: float = 1.0:
set(value):
height = value
_rebuild()
@export_range(0.02, 0.5, 0.001, "suffix:m") var frame_depth: float = 0.08:
set(value):
frame_depth = value
_rebuild()
@export_range(0.1, 8.0, 0.001, "suffix:m") var fence_height: float = 0.7:
set(value):
fence_height = value
_rebuild()
@export_range(0, 100, 1, "suffix: posts") var fence_divisions: int = 1:
set(value):
fence_divisions = value
_rebuild()
@export_storage var path_points: PackedVector3Array = PackedVector3Array()
@export_range(0.01, 0.5, 0.001, "suffix:m") var frame_width: float = 0.08:
set(value):
frame_width = value
_rebuild()
@export var follow_host_thickness: bool = true:
set(value):
follow_host_thickness = value
_rebuild()
@export var surface_material: Material:
set(value):
surface_material = value
_rebuild()
@export var frame_material: Material:
set(value):
frame_material = value
_rebuild()
@export var panel_material: Material:
set(value):
panel_material = value
_rebuild()
func _validate_property(property: Dictionary) -> void:
var separate_object := detail_mode == DetailMode.SEPARATE_OBJECT
var is_window := detail_type == DetailType.WINDOW
var is_door := detail_type == DetailType.DOOR
var has_frame_and_panel := separate_object and detail_type != DetailType.PILLAR and detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET
var show_property := true
match StringName(property.name):
&"detail_mode":
show_property = detail_type != DetailType.PILLAR and detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET
&"center_align":
show_property = detail_type == DetailType.PILLAR
&"fence_height", &"fence_divisions":
show_property = detail_type == DetailType.FENCE or detail_type == DetailType.PARAPET
&"path_points":
show_property = false
&"width":
show_property = detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET
if detail_type == DetailType.PILLAR:
property.hint = PROPERTY_HINT_RANGE
property.hint_string = "0.01,5.0,0.001,suffix:m"
&"height":
show_property = detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET
&"window_style":
show_property = is_window and separate_object
&"door_style":
show_property = is_door and separate_object
&"frame_width", &"frame_material", &"panel_material":
show_property = has_frame_and_panel
&"follow_host_thickness":
show_property = has_frame_and_panel
&"frame_depth":
show_property = has_frame_and_panel and not follow_host_thickness
&"surface_material":
show_property = separate_object
if not show_property:
property.usage = PROPERTY_USAGE_NO_EDITOR
func _ready() -> void:
set_notify_transform(true)
_rebuild()
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED and is_inside_tree():
var host := get_parent()
if host != null and host.has_method("_rebuild"):
host.call("_rebuild")
func _on_host_thickness_changed() -> void:
_rebuild(false)
func _apply_pillar_alignment() -> void:
if detail_type != DetailType.PILLAR:
return
var direction := pillar_direction.normalized()
if direction.is_zero_approx():
direction = Vector3.UP
if center_align:
var object_axes := pillar_alignment_basis.orthonormalized()
var best_axis := direction
var best_dot := -1.0
for axis in [object_axes.x, object_axes.y, object_axes.z]:
var normalized_axis: Vector3 = axis.normalized()
var alignment := absf(direction.dot(normalized_axis))
if alignment > best_dot:
best_dot = alignment
best_axis = normalized_axis if direction.dot(normalized_axis) >= 0.0 else -normalized_axis
direction = best_axis
var reference_axis := Vector3.FORWARD if absf(direction.dot(Vector3.UP)) > 0.99 else Vector3.UP
var x_axis := reference_axis.cross(direction).normalized()
var z_axis := x_axis.cross(direction).normalized()
transform.basis = Basis(x_axis, direction, z_axis).orthonormalized()
if center_align and pillar_direction.length() > 0.0:
height = pillar_direction.length()
func _rebuild(notify_host: bool = true) -> void:
if not is_inside_tree():
return
for child in get_children():
if child.name == "DetailGeometry":
remove_child(child)
child.queue_free()
var host := get_parent()
if notify_host and host != null and host.has_method("_rebuild"):
host.call("_rebuild")
if detail_mode == DetailMode.OPENING and detail_type != DetailType.FENCE and detail_type != DetailType.PARAPET:
return
var holder := Node3D.new()
holder.name = "DetailGeometry"
add_child(holder)
var scene_root: Node = null
if Engine.is_editor_hint() and not has_meta("modular_builder_preview"):
scene_root = get_tree().edited_scene_root
if scene_root:
holder.owner = scene_root
var effective_depth := _effective_frame_depth()
var frame := minf(maxf(frame_width, 0.01), minf(width, height) * 0.45)
if detail_type == DetailType.FENCE or detail_type == DetailType.PARAPET:
_build_fence_geometry(holder)
return
if detail_type == DetailType.PILLAR:
_add_box(holder, "PillarBody", Vector3(width, height, width), Vector3.ZERO, surface_material)
return
if detail_type == DetailType.HATCH:
_add_box(holder, "LeftFrame", Vector3(frame, effective_depth, height), Vector3(-width * 0.5 + frame * 0.5, 0.0, 0.0), _frame_material())
_add_box(holder, "RightFrame", Vector3(frame, effective_depth, height), Vector3(width * 0.5 - frame * 0.5, 0.0, 0.0), _frame_material())
_add_box(holder, "NearFrame", Vector3(width, effective_depth, frame), Vector3(0.0, 0.0, -height * 0.5 + frame * 0.5), _frame_material())
_add_box(holder, "FarFrame", Vector3(width, effective_depth, frame), Vector3(0.0, 0.0, height * 0.5 - frame * 0.5), _frame_material())
_add_box(holder, "HatchPanel", Vector3(width - frame * 2.0, effective_depth * 0.35, height - frame * 2.0), Vector3(0.0, effective_depth * 0.4, 0.0), _panel_material())
return
_add_box(holder, "LeftFrame", Vector3(frame, height, effective_depth), Vector3(-width * 0.5 + frame * 0.5, 0.0, 0.0), _frame_material())
_add_box(holder, "RightFrame", Vector3(frame, height, effective_depth), Vector3(width * 0.5 - frame * 0.5, 0.0, 0.0), _frame_material())
_add_box(holder, "TopFrame", Vector3(width, frame, effective_depth), Vector3(0.0, height * 0.5 - frame * 0.5, 0.0), _frame_material())
if detail_type != DetailType.DOOR:
_add_box(holder, "BottomFrame", Vector3(width, frame, effective_depth), Vector3(0.0, -height * 0.5 + frame * 0.5, 0.0), _frame_material())
if detail_type == DetailType.WINDOW:
_add_box(holder, "WindowPanel", Vector3(maxf(width - frame * 2.0, 0.001), maxf(height - frame * 2.0, 0.001), effective_depth * 0.35), Vector3.ZERO, _panel_material())
if window_style == WindowStyle.CROSS_PANE:
_add_box(holder, "VerticalMullion", Vector3(frame * 0.55, height - frame * 2.0, effective_depth * 0.4), Vector3.ZERO, _frame_material())
_add_box(holder, "HorizontalMullion", Vector3(width - frame * 2.0, frame * 0.55, effective_depth * 0.4), Vector3.ZERO, _frame_material())
elif detail_type == DetailType.DOOR:
_add_box(holder, "DoorPanel", Vector3(width - frame * 2.0, height - frame, effective_depth * 0.45), Vector3(0.0, -frame * 0.5, 0.0), _panel_material())
if door_style == DoorStyle.PANELED:
var inset_depth := maxf(effective_depth * 0.04, 0.005)
var panel_width := (width - frame * 2.0) * 0.82
var panel_height := maxf((height - frame) * 0.28, 0.05)
for panel_index in range(2):
var panel_y := -frame * 0.5 + (panel_index - 0.5) * (panel_height + frame * 0.25)
_add_box(holder, "DoorInset_%d" % panel_index, Vector3(panel_width, panel_height, inset_depth), Vector3(0.0, panel_y, effective_depth * 0.24), _frame_material())
func _effective_frame_depth() -> float:
if follow_host_thickness:
var host := get_parent()
if host != null:
var host_thickness = host.get("thickness")
if host_thickness is float or host_thickness is int:
var depth := float(host_thickness)
if detail_type == DetailType.WINDOW or detail_type == DetailType.DOOR:
depth *= 1.25
return maxf(depth, 0.02)
return frame_depth
func _build_fence_geometry(holder: Node3D) -> void:
if path_points.size() < 2 or fence_height <= 0.0:
return
var start := path_points[0]
var finish := path_points[1]
var path := finish - start
var path_length := path.length()
if path_length < 0.05:
return
var path_axis := path / path_length
var up_axis := Vector3.UP - path_axis * path_axis.dot(Vector3.UP)
if up_axis.length_squared() < 0.001:
up_axis = Vector3.FORWARD - path_axis * path_axis.dot(Vector3.FORWARD)
up_axis = up_axis.normalized()
var side_axis := path_axis.cross(up_axis).normalized()
var post_width := 0.16 if detail_type == DetailType.PARAPET else 0.08
var post_count := fence_divisions + 2
var posts: Array[Vector3] = []
for i in range(post_count):
var base := start.lerp(finish, float(i) / float(post_count - 1))
posts.append(base)
_add_oriented_box(holder, "Post_%d" % i, base + up_axis * fence_height * 0.5, path_axis, up_axis, side_axis, Vector3(post_width, fence_height, post_width))
for i in range(posts.size() - 1):
var a := posts[i]
var b := posts[i + 1]
var segment := b - a
var segment_length := segment.length()
if detail_type == DetailType.PARAPET:
_add_oriented_box(holder, "Rail_%d" % i, (a + b) * 0.5 + up_axis * fence_height * 0.78, segment.normalized(), up_axis, side_axis, Vector3(segment_length, post_width * 0.8, post_width * 0.8))
else:
for height_ratio in [0.28, 0.78]:
_add_oriented_box(holder, "Rail_%d_%d" % [i, int(height_ratio * 100.0)], (a + b) * 0.5 + up_axis * fence_height * height_ratio, segment.normalized(), up_axis, side_axis, Vector3(segment_length, post_width, post_width))
func _add_oriented_box(holder: Node3D, node_name: String, center: Vector3, axis_x: Vector3, axis_y: Vector3, axis_z: Vector3, dimensions: Vector3) -> void:
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = node_name
var box := BoxMesh.new()
box.size = dimensions
mesh_instance.mesh = box
var basis := Basis(axis_x.normalized(), axis_y.normalized(), axis_z.normalized()).orthonormalized()
mesh_instance.transform = Transform3D(basis, center)
mesh_instance.material_override = surface_material
holder.add_child(mesh_instance)
if Engine.is_editor_hint() and not has_meta("modular_builder_preview"):
var scene_root := get_tree().edited_scene_root
if scene_root:
mesh_instance.owner = scene_root
func _frame_material() -> Material:
return frame_material if frame_material != null else surface_material
func _panel_material() -> Material:
return panel_material if panel_material != null else surface_material
func _add_box(holder: Node3D, node_name: String, box_size: Vector3, box_position: Vector3, material: Material = null) -> void:
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = node_name
var box := BoxMesh.new()
box.size = box_size
mesh_instance.mesh = box
mesh_instance.position = box_position
mesh_instance.material_override = material if material != null else surface_material
holder.add_child(mesh_instance)
if Engine.is_editor_hint() and not has_meta("modular_builder_preview"):
var scene_root := get_tree().edited_scene_root
if scene_root:
mesh_instance.owner = scene_root

View file

@ -0,0 +1 @@
uid://dsttda167kuhi

View file

@ -1,6 +1,6 @@
@tool @tool
class_name ModularBuilderPiece class_name ModularBuilderPiece
extends "res://core/vendor/modular_builder/modular_piece_geometry.gd" extends "res://addons/modular_builder/modular_piece_geometry.gd"
func _ready() -> void: func _ready() -> void:
_rebuild() _rebuild()

View file

@ -0,0 +1,748 @@
@tool
class_name ModularBuilderPieceGeometry
extends Node3D
const DETAIL_SCRIPT_PATH := "res://addons/modular_builder/modular_building_detail.gd"
const DETAIL_TYPE_HATCH := 2
const DETAIL_TYPE_PILLAR := 3
enum PieceType { FOUNDATION_SQUARE, FOUNDATION_TRIANGLE, FLOOR, WALL, BUILDING, GABLE_WALL, ROOF_GABLE, ROOF_SHED, GABLE_WALL_RIGHT, ROOF_PYRAMID, STAIRS, FENCE }
@export var piece_type: PieceType = PieceType.FOUNDATION_SQUARE:
set(value):
piece_type = value
notify_property_list_changed()
_rebuild()
@export_range(0.5, 8.0, 0.001, "suffix:m") var unit_size: float = 2.0:
set(value):
unit_size = value
_rebuild()
@export_range(0.01, 1.0, 0.001, "suffix:m") var thickness: float = 0.2:
set(value):
thickness = value
_rebuild()
for child in get_children():
if child.has_method("_on_host_thickness_changed"):
child.call("_on_host_thickness_changed")
@export_range(0.1, 8.0, 0.001, "suffix:m") var rise: float = 1.0:
set(value):
rise = value
_rebuild()
@export_range(0.01, 32.0, 0.001, "suffix:m") var roof_width: float = 2.0:
set(value):
roof_width = value
_rebuild()
@export_range(1, 100, 1, "suffix: steps") var stair_count: int = 10:
set(value):
stair_count = value
_rebuild()
@export_range(0.01, 8.0, 0.001, "suffix:m") var stair_width: float = 1.0:
set(value):
stair_width = value
_rebuild()
@export_range(0, 100, 1, "suffix: posts") var fence_divisions: int = 1:
set(value):
fence_divisions = value
_rebuild()
@export_range(0.1, 8.0, 0.001, "suffix:m") var fence_height: float = 1.0:
set(value):
fence_height = value
_rebuild()
@export var symmetric_dimensions: bool = true:
set(value):
if symmetric_dimensions == value:
return
if not value:
_capture_mesh_dimensions()
symmetric_dimensions = value
notify_property_list_changed()
if value:
scale = Vector3.ONE
else:
_apply_dimension_scale()
@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_width: float = 2.0:
set(value):
dimension_width = value
_apply_dimension_scale()
@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_height: float = 2.0:
set(value):
dimension_height = value
_apply_dimension_scale()
@export_range(0.01, 100.0, 0.001, "suffix:m") var dimension_depth: float = 2.0:
set(value):
dimension_depth = value
_apply_dimension_scale()
@export_storage var _dimension_reference: Vector3 = Vector3.ZERO
@export var center_align: bool = false:
set(value):
center_align = value
_rebuild()
@export var surface_material: Material:
set(value):
surface_material = value
_rebuild()
@export var custom_points: PackedVector3Array = PackedVector3Array():
set(value):
custom_points = value
_rebuild()
@export var centered_origin: bool = false:
set(value):
centered_origin = value
_rebuild()
func _rebuild() -> void:
pass
func _capture_mesh_dimensions() -> void:
var geometry := get_node_or_null("Geometry") as MeshInstance3D
if geometry == null or geometry.mesh == null:
return
var dimensions := geometry.mesh.get_aabb().size
if dimensions.x > 0.001:
dimension_width = dimensions.x
if dimensions.y > 0.001:
dimension_height = dimensions.y
if dimensions.z > 0.001:
dimension_depth = dimensions.z
_dimension_reference = dimensions
func _apply_dimension_scale() -> void:
if symmetric_dimensions or _dimension_reference.x <= 0.001 or _dimension_reference.y <= 0.001 or _dimension_reference.z <= 0.001:
return
var target := Vector3(dimension_width, dimension_height, dimension_depth)
if piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE or piece_type == PieceType.FLOOR:
target.y = _dimension_reference.y
if piece_type == PieceType.WALL:
target.z = _dimension_reference.z
scale = Vector3(target.x / _dimension_reference.x, target.y / _dimension_reference.y, target.z / _dimension_reference.z)
func _make_mesh() -> ArrayMesh:
if piece_type == PieceType.GABLE_WALL and custom_points.size() >= 2:
return _make_gable_wall_mesh()
if piece_type == PieceType.GABLE_WALL_RIGHT and custom_points.size() >= 2:
return _make_right_gable_wall_mesh()
if (piece_type == PieceType.ROOF_GABLE or piece_type == PieceType.ROOF_SHED or piece_type == PieceType.ROOF_PYRAMID) and custom_points.size() >= 2:
return _make_roof_mesh()
if piece_type == PieceType.STAIRS and custom_points.size() >= 2:
return _make_stair_mesh()
if piece_type == PieceType.FENCE and custom_points.size() >= 2:
return _make_fence_mesh()
if custom_points.size() >= 2 and piece_type == PieceType.WALL:
return _make_custom_wall_mesh()
if custom_points.size() >= 3 and piece_type == PieceType.FLOOR:
return _make_custom_floor_mesh()
if custom_points.size() >= 3 and (piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE):
return _make_custom_foundation_mesh()
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var s := unit_size
var polygon: Array[Vector2]
match piece_type:
PieceType.FOUNDATION_TRIANGLE:
polygon = [Vector2(-s * 0.5, -s * 0.5), Vector2(s * 0.5, -s * 0.5), Vector2(-s * 0.5, s * 0.5)]
_:
polygon = [Vector2(-s * 0.5, -s * 0.5), Vector2(s * 0.5, -s * 0.5), Vector2(s * 0.5, s * 0.5), Vector2(-s * 0.5, s * 0.5)]
var y0 := -thickness * 0.5 if centered_origin else 0.0
var y1 := thickness * 0.5 if centered_origin else thickness
if piece_type == PieceType.WALL:
polygon = [Vector2(-s * 0.5, -thickness * 0.5), Vector2(s * 0.5, -thickness * 0.5), Vector2(s * 0.5, thickness * 0.5), Vector2(-s * 0.5, thickness * 0.5)]
y0 = -s * 0.5 if centered_origin else 0.0
y1 = s * 0.5 if centered_origin else s
if piece_type == PieceType.FOUNDATION_SQUARE or piece_type == PieceType.FOUNDATION_TRIANGLE:
var hatch_mesh := _make_hatch_surface_mesh(polygon, y1, y0)
if hatch_mesh != null:
return hatch_mesh
for i in range(1, polygon.size() - 1):
_add_tri(vertices, normals, Vector3(polygon[0].x, y1, polygon[0].y), Vector3(polygon[i + 1].x, y1, polygon[i + 1].y), Vector3(polygon[i].x, y1, polygon[i].y))
_add_tri(vertices, normals, Vector3(polygon[0].x, y0, polygon[0].y), Vector3(polygon[i].x, y0, polygon[i].y), Vector3(polygon[i + 1].x, y0, polygon[i + 1].y))
for i in range(polygon.size()):
var j := (i + 1) % polygon.size()
var a := polygon[i]
var b := polygon[j]
_add_tri(vertices, normals, Vector3(a.x, y0, a.y), Vector3(b.x, y0, b.y), Vector3(b.x, y1, b.y))
_add_tri(vertices, normals, Vector3(a.x, y0, a.y), Vector3(b.x, y1, b.y), Vector3(a.x, y1, a.y))
var arrays: Array = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_NORMAL] = normals
var result := ArrayMesh.new()
result.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
return result
func _make_gable_wall_mesh() -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var base_points := _get_centered_gable_base() if center_align else PackedVector3Array([custom_points[0], custom_points[1]])
var a: Vector3 = base_points[0]
var b: Vector3 = base_points[1]
var direction := (b - a).normalized()
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * thickness * 0.5
var peak := (a + b) * 0.5 + Vector3.UP * rise
var front_a := a + side
var front_b := b + side
var front_peak := peak + side
var back_a := a - side
var back_b := b - side
var back_peak := peak - side
_add_tri(vertices, normals, front_a, front_b, front_peak)
_add_tri(vertices, normals, back_b, back_a, back_peak)
_add_quad(vertices, normals, front_a, back_a, back_b, front_b)
_add_quad(vertices, normals, front_b, back_b, back_peak, front_peak)
_add_quad(vertices, normals, front_peak, back_peak, back_a, front_a)
return _array_mesh(vertices, normals)
func _make_right_gable_wall_mesh() -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var base_points := _get_centered_gable_base() if center_align else PackedVector3Array([custom_points[0], custom_points[1]])
var a: Vector3 = base_points[0]
var b: Vector3 = base_points[1]
var direction := (b - a).normalized()
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * thickness * 0.5
var peak := b + Vector3.UP * rise
var front_a := a + side
var front_b := b + side
var front_peak := peak + side
var back_a := a - side
var back_b := b - side
var back_peak := peak - side
_add_tri(vertices, normals, front_a, front_b, front_peak)
_add_tri(vertices, normals, back_peak, back_b, back_a)
_add_quad(vertices, normals, front_a, back_a, back_b, front_b)
_add_quad(vertices, normals, front_b, back_b, back_peak, front_peak)
_add_quad(vertices, normals, front_peak, back_peak, back_a, front_a)
return _array_mesh(vertices, normals)
func _make_roof_mesh() -> ArrayMesh:
if piece_type == PieceType.ROOF_SHED and custom_points.size() == 2:
return _make_shed_roof_mesh()
if custom_points.size() >= 4:
return _make_oriented_roof_mesh()
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var first: Vector3 = custom_points[0]
var second: Vector3 = custom_points[1]
var min_x := minf(first.x, second.x)
var max_x := maxf(first.x, second.x)
var min_z := minf(first.z, second.z)
var max_z := maxf(first.z, second.z)
var span_x := max_x - min_x
var span_z := max_z - min_z
if span_x < 0.02 or span_z < 0.02:
return _array_mesh(vertices, normals)
var panels: Array = []
if piece_type == PieceType.ROOF_GABLE:
if span_x <= span_z:
var ridge_x := (min_x + max_x) * 0.5
panels.append([Vector3(min_x, 0.0, min_z), Vector3(min_x, 0.0, max_z), Vector3(ridge_x, rise, max_z), Vector3(ridge_x, rise, min_z)])
panels.append([Vector3(ridge_x, rise, min_z), Vector3(ridge_x, rise, max_z), Vector3(max_x, 0.0, max_z), Vector3(max_x, 0.0, min_z)])
else:
var ridge_z := (min_z + max_z) * 0.5
panels.append([Vector3(min_x, 0.0, min_z), Vector3(max_x, 0.0, min_z), Vector3(max_x, rise, ridge_z), Vector3(min_x, rise, ridge_z)])
panels.append([Vector3(min_x, rise, ridge_z), Vector3(max_x, rise, ridge_z), Vector3(max_x, 0.0, max_z), Vector3(min_x, 0.0, max_z)])
else:
if span_x <= span_z:
var low_x := first.x
var high_x := second.x
panels.append([Vector3(low_x, 0.0, min_z), Vector3(low_x, 0.0, max_z), Vector3(high_x, rise, max_z), Vector3(high_x, rise, min_z)])
else:
var low_z := first.z
var high_z := second.z
panels.append([Vector3(min_x, 0.0, low_z), Vector3(max_x, 0.0, low_z), Vector3(max_x, rise, high_z), Vector3(min_x, rise, high_z)])
for panel in panels:
_append_solid_panel(vertices, normals, panel)
return _array_mesh(vertices, normals)
func _make_shed_roof_mesh() -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var low: Vector3 = custom_points[0]
var high: Vector3 = custom_points[1]
var slope := high - low
var horizontal := Vector3(slope.x, 0.0, slope.z)
if horizontal.length_squared() < 0.0001 or roof_width <= 0.0:
return _array_mesh(vertices, normals)
var across := Vector3(horizontal.z, 0.0, -horizontal.x).normalized() * roof_width * 0.5
var panel := [low - across, low + across, high + across, high - across]
_append_solid_panel(vertices, normals, panel)
return _array_mesh(vertices, normals)
func _make_oriented_roof_mesh() -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var p0: Vector3 = custom_points[0]
var p1: Vector3 = custom_points[1]
var p2: Vector3 = custom_points[2]
var p3: Vector3 = custom_points[3]
var edge_01 := Vector2(p1.x - p0.x, p1.z - p0.z).length()
var edge_12 := Vector2(p2.x - p1.x, p2.z - p1.z).length()
var panels: Array = []
if piece_type == PieceType.ROOF_GABLE:
if edge_01 <= edge_12:
var ridge_01 := (p0 + p1) * 0.5 + Vector3.UP * rise
var ridge_23 := (p2 + p3) * 0.5 + Vector3.UP * rise
panels.append([p0, p3, ridge_23, ridge_01])
panels.append([ridge_01, ridge_23, p2, p1])
else:
var ridge_12 := (p1 + p2) * 0.5 + Vector3.UP * rise
var ridge_30 := (p3 + p0) * 0.5 + Vector3.UP * rise
panels.append([p0, p1, ridge_12, ridge_30])
panels.append([ridge_30, ridge_12, p2, p3])
elif piece_type == PieceType.ROOF_PYRAMID:
var peak := (p0 + p1 + p2 + p3) * 0.25 + Vector3.UP * rise
panels.append([p0, p1, peak])
panels.append([p1, p2, peak])
panels.append([p2, p3, peak])
panels.append([p3, p0, peak])
else:
# The four points define the low and high edges. Keep their centers
# fixed when adjusting both edge widths in the Inspector.
var base_width := Vector2(p1.x - p0.x, p1.z - p0.z).length()
if base_width > 0.001 and roof_width > 0.0:
var direction := Vector3(p1.x - p0.x, 0.0, p1.z - p0.z).normalized()
var low_center := (p0 + p1) * 0.5
var high_center := (p2 + p3) * 0.5
var half_width := roof_width * 0.5
panels.append([
low_center - direction * half_width,
low_center + direction * half_width,
high_center + direction * half_width,
high_center - direction * half_width,
])
else:
panels.append([p0, p1, p2, p3])
for panel in panels:
_append_solid_panel(vertices, normals, panel)
return _array_mesh(vertices, normals)
func _make_stair_mesh() -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var start: Vector3 = custom_points[0]
var finish: Vector3 = custom_points[1]
var horizontal := Vector3(finish.x - start.x, 0.0, finish.z - start.z)
var run := horizontal.length()
var rise_amount := finish.y - start.y
if run < 0.05 or rise_amount <= 0.05 or stair_count < 1 or stair_width <= 0.0:
return _array_mesh(vertices, normals)
var forward := horizontal / run
var width_axis := Vector3(-forward.z, 0.0, forward.x) * stair_width * 0.5
var profile := PackedVector2Array([Vector2(0.0, 0.0), Vector2(run, 0.0), Vector2(run, rise_amount)])
for step_index in range(stair_count - 1, -1, -1):
var tread_start := run * float(step_index) / stair_count
var tread_end := run * float(step_index + 1) / stair_count
var tread_height := rise_amount * float(step_index + 1) / stair_count
profile.append(Vector2(tread_start, tread_height))
if step_index > 0:
profile.append(Vector2(tread_start, rise_amount * float(step_index) / stair_count))
var triangulation := Geometry2D.triangulate_polygon(profile)
for side_sign in [-1.0, 1.0]:
for triangle_index in range(0, triangulation.size(), 3):
var a := profile[triangulation[triangle_index]]
var b := profile[triangulation[triangle_index + 1]]
var c := profile[triangulation[triangle_index + 2]]
var point_a: Vector3 = start + forward * a.x + Vector3.UP * a.y + width_axis * side_sign
var point_b: Vector3 = start + forward * b.x + Vector3.UP * b.y + width_axis * side_sign
var point_c: Vector3 = start + forward * c.x + Vector3.UP * c.y + width_axis * side_sign
if side_sign > 0.0:
_add_tri(vertices, normals, point_a, point_b, point_c)
else:
_add_tri(vertices, normals, point_c, point_b, point_a)
for profile_index in range(profile.size()):
var next_index := (profile_index + 1) % profile.size()
var a := profile[profile_index]
var b := profile[next_index]
var point_a_left := start + forward * a.x + Vector3.UP * a.y - width_axis
var point_b_left := start + forward * b.x + Vector3.UP * b.y - width_axis
var point_b_right := start + forward * b.x + Vector3.UP * b.y + width_axis
var point_a_right := start + forward * a.x + Vector3.UP * a.y + width_axis
_add_quad(vertices, normals, point_a_left, point_b_left, point_b_right, point_a_right)
return _array_mesh(vertices, normals)
func _make_fence_mesh() -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var start: Vector3 = custom_points[0]
var finish: Vector3 = custom_points[1]
var path := finish - start
var path_length := path.length()
if path_length < 0.05 or fence_height <= 0.0:
return _array_mesh(vertices, normals)
var path_axis := path / path_length
var post_count := fence_divisions + 2
var post_positions: Array[Vector3] = []
for post_index in range(post_count):
var base_position := start.lerp(finish, float(post_index) / float(post_count - 1))
post_positions.append(base_position)
var side_axis := path_axis.cross(Vector3.UP).normalized()
if side_axis.length_squared() < 0.001:
side_axis = Vector3.FORWARD
_append_oriented_box(vertices, normals, base_position + Vector3.UP * fence_height * 0.5, path_axis, Vector3.UP, side_axis, Vector3(thickness, fence_height, thickness))
for section_index in range(post_positions.size() - 1):
var section_start := post_positions[section_index]
var section_end := post_positions[section_index + 1]
var section_axis := (section_end - section_start).normalized()
var rail_up := Vector3.UP - section_axis * section_axis.dot(Vector3.UP)
if rail_up.length_squared() < 0.001:
rail_up = Vector3.FORWARD - section_axis * section_axis.dot(Vector3.FORWARD)
rail_up = rail_up.normalized()
var rail_side := section_axis.cross(rail_up).normalized()
var section_length := section_start.distance_to(section_end)
for height_ratio in [0.28, 0.78]:
var rail_center: Vector3 = (section_start + section_end) * 0.5 + Vector3.UP * fence_height * height_ratio
_append_oriented_box(vertices, normals, rail_center, section_axis, rail_up, rail_side, Vector3(section_length, thickness, thickness))
return _array_mesh(vertices, normals)
func _append_oriented_box(vertices: PackedVector3Array, normals: PackedVector3Array, center: Vector3, axis_x: Vector3, axis_y: Vector3, axis_z: Vector3, dimensions: Vector3) -> void:
var x := axis_x.normalized() * dimensions.x * 0.5
var y := axis_y.normalized() * dimensions.y * 0.5
var z := axis_z.normalized() * dimensions.z * 0.5
var nnn := center - x - y - z
var pnn := center + x - y - z
var ppn := center + x + y - z
var npn := center - x + y - z
var nnp := center - x - y + z
var pnp := center + x - y + z
var ppp := center + x + y + z
var npp := center - x + y + z
_add_quad(vertices, normals, nnp, pnp, ppp, npp)
_add_quad(vertices, normals, pnn, nnn, npn, ppn)
_add_quad(vertices, normals, npp, ppp, ppn, npn)
_add_quad(vertices, normals, nnn, pnn, pnp, nnp)
_add_quad(vertices, normals, nnn, nnp, npp, npn)
_add_quad(vertices, normals, pnp, pnn, ppn, ppp)
func _get_centered_gable_base() -> PackedVector3Array:
var selected_a := to_global(custom_points[0])
var selected_b := to_global(custom_points[1])
var search_root: Node = get_parent()
while search_root != null and not search_root is ModularBuilderPieceGeometry:
search_root = search_root.get_parent()
if search_root == null:
return PackedVector3Array([custom_points[0], custom_points[1]])
var walls: Array[ModularBuilderPieceGeometry] = []
_collect_centering_walls(search_root, walls)
var centered_a := selected_a
var centered_b := selected_b
var nearest_a := INF
var nearest_b := INF
for wall in walls:
if wall.custom_points.size() < 2:
continue
var wall_a := wall.to_global(wall.custom_points[0] + Vector3.UP * wall.unit_size)
var wall_b := wall.to_global(wall.custom_points[1] + Vector3.UP * wall.unit_size)
var tolerance: float = wall.thickness + 0.04
for endpoint in [wall_a, wall_b]:
var distance_a := selected_a.distance_to(endpoint)
if distance_a < nearest_a and distance_a <= tolerance:
nearest_a = distance_a
centered_a = endpoint
var distance_b := selected_b.distance_to(endpoint)
if distance_b < nearest_b and distance_b <= tolerance:
nearest_b = distance_b
centered_b = endpoint
if nearest_a < INF and nearest_b < INF and centered_a.distance_to(centered_b) >= 0.02:
return PackedVector3Array([to_local(centered_a), to_local(centered_b)])
return PackedVector3Array([custom_points[0], custom_points[1]])
func _collect_centering_walls(node: Node, walls: Array[ModularBuilderPieceGeometry]) -> void:
if node is ModularBuilderPieceGeometry and node.piece_type == PieceType.WALL:
walls.append(node)
for child in node.get_children():
_collect_centering_walls(child, walls)
func _append_solid_panel(vertices: PackedVector3Array, normals: PackedVector3Array, panel: Array) -> void:
if panel.size() < 3:
return
var p0: Vector3 = panel[0]
var p1: Vector3 = panel[1]
var p2: Vector3 = panel[2]
var normal: Vector3 = (p1 - p0).cross(p2 - p0).normalized()
if normal.is_zero_approx():
return
var half_depth: Vector3 = normal * thickness * 0.5
var front: Array[Vector3] = []
var back: Array[Vector3] = []
for point in panel:
front.append(point + half_depth)
back.append(point - half_depth)
for i in range(1, panel.size() - 1):
_add_tri(vertices, normals, front[0], front[i], front[i + 1])
_add_tri(vertices, normals, back[i + 1], back[i], back[0])
for i in range(panel.size()):
var next := (i + 1) % panel.size()
_add_quad(vertices, normals, front[i], back[i], back[next], front[next])
func _make_custom_wall_mesh() -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var wall_points: PackedVector3Array = _get_centered_wall_points() if center_align else custom_points
var a: Vector3 = wall_points[0]
var b: Vector3 = wall_points[1]
var direction := (b - a).normalized()
var length := a.distance_to(b)
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * thickness * 0.5
var height := Vector3.UP * unit_size
var openings: Array[PackedVector2Array] = []
for child in get_children():
if _is_detail_node(child) and int(child.get("detail_type")) != DETAIL_TYPE_HATCH and int(child.get("detail_type")) != DETAIL_TYPE_PILLAR:
var detail := child as Node3D
var center := Vector2((detail.position - a).dot(direction), detail.position.y - a.y)
var axis_x := Vector2(detail.transform.basis.x.dot(direction), detail.transform.basis.x.y).normalized()
var axis_y := Vector2(detail.transform.basis.y.dot(direction), detail.transform.basis.y.y).normalized()
var opening := PackedVector2Array([
center - axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
center + axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
center + axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5,
center - axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5
])
var wall_rect := PackedVector2Array([Vector2(0.0, 0.0), Vector2(length, 0.0), Vector2(length, unit_size), Vector2(0.0, unit_size)])
for clipped_opening in Geometry2D.intersect_polygons(opening, wall_rect):
if clipped_opening.size() >= 3:
openings.append(clipped_opening)
var xs: Array[float] = [0.0, length]
var ys: Array[float] = [0.0, unit_size]
for opening in openings:
for point in opening:
xs.append(clampf(point.x, 0.0, length))
ys.append(clampf(point.y, 0.0, unit_size))
xs.sort()
ys.sort()
for xi in range(xs.size() - 1):
for yi in range(ys.size() - 1):
var u0: float = xs[xi]
var u1: float = xs[xi + 1]
var v0: float = ys[yi]
var v1: float = ys[yi + 1]
var mid := Vector2((u0 + u1) * 0.5, (v0 + v1) * 0.5)
var cell := PackedVector2Array([Vector2(u0, v0), Vector2(u1, v0), Vector2(u1, v1), Vector2(u0, v1)])
var remaining: Array[PackedVector2Array] = [cell]
for opening in openings:
var next_remaining: Array[PackedVector2Array] = []
for polygon in remaining:
for clipped in Geometry2D.clip_polygons(polygon, opening):
if clipped.size() >= 3:
next_remaining.append(clipped)
remaining = next_remaining
for polygon in remaining:
var indices := Geometry2D.triangulate_polygon(polygon)
for ti in range(0, indices.size(), 3):
var p0: Vector2 = polygon[indices[ti]]
var p1: Vector2 = polygon[indices[ti + 1]]
var p2: Vector2 = polygon[indices[ti + 2]]
var f0 := a + direction * p0.x + Vector3.UP * p0.y
var f1 := a + direction * p1.x + Vector3.UP * p1.y
var f2 := a + direction * p2.x + Vector3.UP * p2.y
_add_tri(vertices, normals, f0 + side, f1 + side, f2 + side)
_add_tri(vertices, normals, f2 - side, f1 - side, f0 - side)
# Close the wall body at its ends, top, and base.
_add_quad(vertices, normals, a - side, a + side, a + side + height, a - side + height)
_add_quad(vertices, normals, b + side, b - side, b - side + height, b + side + height)
_add_quad(vertices, normals, a + side + height, b + side + height, b - side + height, a - side + height)
_add_quad(vertices, normals, a - side, b - side, b + side, a + side)
for opening in openings:
for edge_index in range(opening.size()):
var p0 := opening[edge_index]
var p1 := opening[(edge_index + 1) % opening.size()]
var a0 := a + direction * p0.x + Vector3.UP * p0.y
var a1 := a + direction * p1.x + Vector3.UP * p1.y
_add_quad(vertices, normals, a0 + side, a0 - side, a1 - side, a1 + side)
return _array_mesh(vertices, normals)
func _get_centered_wall_points() -> PackedVector3Array:
if custom_points.size() < 2:
return custom_points
var selected_a := to_global(custom_points[0])
var selected_b := to_global(custom_points[1])
var search_root: Node = get_parent()
while search_root != null and not search_root is ModularBuilderPieceGeometry:
search_root = search_root.get_parent()
if search_root == null:
return custom_points
var walls: Array[ModularBuilderPieceGeometry] = []
_collect_centering_walls(search_root, walls)
var centered_a := selected_a
var centered_b := selected_b
var nearest_a := INF
var nearest_b := INF
for wall in walls:
if wall == self or wall.custom_points.size() < 2:
continue
var direction := (wall.custom_points[1] - wall.custom_points[0]).normalized()
var side := Vector3(-direction.z, 0.0, direction.x).normalized() * wall.thickness * 0.5
for endpoint in wall.custom_points:
var center := wall.to_global(endpoint + Vector3.UP * wall.unit_size)
for surface_corner in [center + side, center - side]:
var distance_a := selected_a.distance_to(surface_corner)
if distance_a < nearest_a and distance_a <= wall.thickness + 0.04:
nearest_a = distance_a
centered_a = center
var distance_b := selected_b.distance_to(surface_corner)
if distance_b < nearest_b and distance_b <= wall.thickness + 0.04:
nearest_b = distance_b
centered_b = center
if nearest_a < INF and nearest_b < INF and centered_a.distance_to(centered_b) >= 0.02:
return PackedVector3Array([to_local(centered_a), to_local(centered_b)])
return custom_points
func _make_custom_floor_mesh() -> ArrayMesh:
var polygon: Array[Vector2] = []
for point in custom_points:
polygon.append(Vector2(point.x, point.z))
var hatch_mesh := _make_hatch_surface_mesh(polygon, custom_points[0].y, custom_points[0].y - thickness)
if hatch_mesh != null:
return hatch_mesh
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var points := custom_points
var bottom: Array[Vector3] = []
for point in points:
bottom.append(point - Vector3.UP * thickness)
for i in range(1, points.size() - 1):
_add_tri(vertices, normals, points[0], points[i + 1], points[i])
_add_tri(vertices, normals, bottom[0], bottom[i], bottom[i + 1])
for i in range(points.size()):
var j := (i + 1) % points.size()
_add_quad(vertices, normals, bottom[i], bottom[j], points[j], points[i])
return _array_mesh(vertices, normals)
func _make_hatch_surface_mesh(polygon: Array[Vector2], top_y: float, bottom_y: float) -> ArrayMesh:
var openings: Array[PackedVector2Array] = []
for child in get_children():
if _is_detail_node(child) and int(child.get("detail_type")) == DETAIL_TYPE_HATCH:
var detail := child as Node3D
var center := Vector2(detail.position.x, detail.position.z)
var axis_x := Vector2(detail.transform.basis.x.x, detail.transform.basis.x.z).normalized()
var axis_y := Vector2(detail.transform.basis.z.x, detail.transform.basis.z.z).normalized()
var opening := PackedVector2Array([
center - axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
center + axis_x * float(detail.get("width")) * 0.5 - axis_y * float(detail.get("height")) * 0.5,
center + axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5,
center - axis_x * float(detail.get("width")) * 0.5 + axis_y * float(detail.get("height")) * 0.5
])
openings.append(opening)
if openings.is_empty() or polygon.size() < 3:
return null
var min_x := polygon[0].x
var max_x := polygon[0].x
var min_z := polygon[0].y
var max_z := polygon[0].y
var xs: Array[float] = []
var zs: Array[float] = []
for point in polygon:
min_x = minf(min_x, point.x)
max_x = maxf(max_x, point.x)
min_z = minf(min_z, point.y)
max_z = maxf(max_z, point.y)
xs.append(point.x)
zs.append(point.y)
for opening in openings:
for point in opening:
xs.append(clampf(point.x, min_x, max_x))
zs.append(clampf(point.y, min_z, max_z))
xs.sort()
zs.sort()
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var source_polygon := PackedVector2Array()
for point in polygon:
source_polygon.append(point)
for xi in range(xs.size() - 1):
for zi in range(zs.size() - 1):
var x0: float = xs[xi]
var x1: float = xs[xi + 1]
var z0: float = zs[zi]
var z1: float = zs[zi + 1]
if x1 - x0 < 0.0001 or z1 - z0 < 0.0001:
continue
var cell := PackedVector2Array([Vector2(x0, z0), Vector2(x1, z0), Vector2(x1, z1), Vector2(x0, z1)])
var remaining: Array[PackedVector2Array] = []
for clipped_surface in Geometry2D.intersect_polygons(source_polygon, cell):
remaining.append(clipped_surface)
for opening in openings:
var next_remaining: Array[PackedVector2Array] = []
for surface_polygon in remaining:
for clipped_surface in Geometry2D.clip_polygons(surface_polygon, opening):
if clipped_surface.size() >= 3:
next_remaining.append(clipped_surface)
remaining = next_remaining
for clipped_surface in remaining:
var triangulated := Geometry2D.triangulate_polygon(clipped_surface)
for ti in range(0, triangulated.size(), 3):
var a: Vector2 = clipped_surface[triangulated[ti]]
var b: Vector2 = clipped_surface[triangulated[ti + 1]]
var c: Vector2 = clipped_surface[triangulated[ti + 2]]
_add_tri(vertices, normals, Vector3(a.x, top_y, a.y), Vector3(c.x, top_y, c.y), Vector3(b.x, top_y, b.y))
_add_tri(vertices, normals, Vector3(a.x, bottom_y, a.y), Vector3(b.x, bottom_y, b.y), Vector3(c.x, bottom_y, c.y))
for i in range(polygon.size()):
var j := (i + 1) % polygon.size()
var a := polygon[i]
var b := polygon[j]
_add_quad(vertices, normals, Vector3(a.x, bottom_y, a.y), Vector3(b.x, bottom_y, b.y), Vector3(b.x, top_y, b.y), Vector3(a.x, top_y, a.y))
for opening in openings:
var visible_edges := Geometry2D.intersect_polygons(opening, source_polygon)
for visible_opening in visible_edges:
for edge_index in range(visible_opening.size()):
var p0 := visible_opening[edge_index]
var p1 := visible_opening[(edge_index + 1) % visible_opening.size()]
_add_quad(vertices, normals, Vector3(p0.x, bottom_y, p0.y), Vector3(p0.x, top_y, p0.y), Vector3(p1.x, top_y, p1.y), Vector3(p1.x, bottom_y, p1.y))
return _array_mesh(vertices, normals)
func _is_detail_node(node: Node) -> bool:
if not node is Node3D or node.get_script() == null:
return false
return node.get_script().resource_path == DETAIL_SCRIPT_PATH
func _make_custom_foundation_mesh() -> ArrayMesh:
var polygon: Array[Vector2] = []
for point in custom_points:
polygon.append(Vector2(point.x, point.z))
var bottom_y := -thickness * 0.5 if centered_origin else 0.0
var top_y := thickness * 0.5 if centered_origin else thickness
var hatch_mesh := _make_hatch_surface_mesh(polygon, top_y, bottom_y)
if hatch_mesh != null:
return hatch_mesh
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
for i in range(1, custom_points.size() - 1):
var a := custom_points[0]
var b := custom_points[i]
var c := custom_points[i + 1]
_add_tri(vertices, normals, Vector3(a.x, top_y, a.z), Vector3(c.x, top_y, c.z), Vector3(b.x, top_y, b.z))
_add_tri(vertices, normals, Vector3(a.x, bottom_y, a.z), Vector3(b.x, bottom_y, b.z), Vector3(c.x, bottom_y, c.z))
for i in range(custom_points.size()):
var j := (i + 1) % custom_points.size()
var a := custom_points[i]
var b := custom_points[j]
_add_quad(vertices, normals, Vector3(a.x, bottom_y, a.z), Vector3(b.x, bottom_y, b.z), Vector3(b.x, top_y, b.z), Vector3(a.x, top_y, a.z))
return _array_mesh(vertices, normals)
func _add_quad(v: PackedVector3Array, n: PackedVector3Array, a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> void:
_add_tri(v, n, a, b, c)
_add_tri(v, n, a, c, d)
func _array_mesh(v: PackedVector3Array, n: PackedVector3Array) -> ArrayMesh:
var arrays: Array = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = v
arrays[Mesh.ARRAY_NORMAL] = n
var result := ArrayMesh.new()
result.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
return result
func _add_tri(v: PackedVector3Array, n: PackedVector3Array, a: Vector3, b: Vector3, c: Vector3) -> void:
var normal := (b - a).cross(c - a).normalized()
v.append(a)
v.append(b)
v.append(c)
n.append(normal)
n.append(normal)
n.append(normal)
# Also generate reversed faces to show both sides.
# This keeps floors and foundations visible from below with any material.
v.append(c)
v.append(b)
v.append(a)
n.append(-normal)
n.append(-normal)
n.append(-normal)

View file

@ -0,0 +1 @@
uid://dp1333xlylabf

View file

@ -133,8 +133,8 @@ func _export(oid: String, name_roh: String) -> void:
if not editor_dir.is_empty(): if not editor_dir.is_empty():
var img := await _thumbnail_bilden(mesh) var img := await _thumbnail_bilden(mesh)
if img != null: if img != null:
DirAccess.make_dir_recursive_absolute(editor_dir + "/core/thumbs") DirAccess.make_dir_recursive_absolute(editor_dir + "/library/thumbs")
img.save_png(editor_dir + "/core/thumbs/" + oid + ".png") img.save_png(editor_dir + "/library/thumbs/" + oid + ".png")
# 6) Editor-Sync (Registry-Kopie; vorhandene Thumbnails bleiben stehen). # 6) Editor-Sync (Registry-Kopie; vorhandene Thumbnails bleiben stehen).
var schritt4 := _lauf(objekt_tool, ["editor", oid]) var schritt4 := _lauf(objekt_tool, ["editor", oid])

1
core

@ -1 +0,0 @@
Subproject commit 5e98535a0afc83af4e4017806686a76af4913afd

View file

@ -19,7 +19,7 @@ Ein Bauteil ist ein **ganz normales Bibliotheks-Objekt** (`bau_*`, Kategorie
und Server lesen dieselbe Datei. und Server lesen dieselbe Datei.
* Poly-Budget zählt wie bei jedem Objekt (`poly` = Dreiecke des Teils). * Poly-Budget zählt wie bei jedem Objekt (`poly` = Dreiecke des Teils).
## Teile (Katalog: `core/editor/bau_katalog.gd`) ## Teile (Katalog: `src/editor/bau_katalog.gd`)
| Gruppe | Teile | | Gruppe | Teile |
| --- | --- | | --- | --- |
@ -34,7 +34,7 @@ Ein Bauteil ist ein **ganz normales Bibliotheks-Objekt** (`bau_*`, Kategorie
Fertige Pack-Objekte (Container, Hangar-Tore, Betonsperren) stehen NICHT im Bauen-Tab, Fertige Pack-Objekte (Container, Hangar-Tore, Betonsperren) stehen NICHT im Bauen-Tab,
sondern wie alle Objekte in der Objekt-Bibliothek unten. sondern wie alle Objekte in der Objekt-Bibliothek unten.
## Snap (`core/editor/build_snap.gd`) ## Snap (`src/editor/build_snap.gd`)
* **XZ:** Wände auf die nächste Zellkante — und zwar GANZ in die Zelle, in der die * **XZ:** Wände auf die nächste Zellkante — und zwar GANZ in die Zelle, in der die
Maus ist (Außenseite bündig mit der Kante, kein Überhang, Ecken schließen ohne Maus ist (Außenseite bündig mit der Kante, kein Überhang, Ecken schließen ohne

View file

@ -41,8 +41,8 @@ im Editor dann Adresse `127.0.0.1`) oder ein Reverse-Proxy mit https (Haken
- **Server-Log** mit Filter. - **Server-Log** mit Filter.
## Code ## Code
`core/server/` (AdminClient, ServerStore, AdminTexte, MapPush), `src/server/` (AdminClient, ServerStore, AdminTexte, MapPush),
`core/ui/panels/server_panel.gd`, `SvmapIO.to_signed_text`. `src/ui/panels/server_panel.gd`, `SvmapIO.to_signed_text`.
## Tests ## Tests
`tests/test_server_sektion.gd` (Unit), `tests/run_server_sektion_e2e.sh` `tests/test_server_sektion.gd` (Unit), `tests/run_server_sektion_e2e.sh`

View file

@ -5,12 +5,12 @@ of truth** für alle Shortcut-Karten — FINALE Version nach allen drei Karten:
Karte 1/3 (t_c46d31c2): Navigation, Auswahl, Löschen · Karte 2/3 (t_55257792): Karte 1/3 (t_c46d31c2): Navigation, Auswahl, Löschen · Karte 2/3 (t_55257792):
Objektmanipulation + Terrain · Karte 3/3 (t_11b5c25b): Datei & Undo, Objektmanipulation + Terrain · Karte 3/3 (t_11b5c25b): Datei & Undo,
Werkzeugwechsel, Hilfe, Test. Das Kurzhilfe-Overlay im Editor (Taste **H**) Werkzeugwechsel, Hilfe, Test. Das Kurzhilfe-Overlay im Editor (Taste **H**)
zeigt dieselbe Tabelle (Quelle: `core/ui/help_text.gd`). zeigt dieselbe Tabelle (Quelle: `src/ui/help_text.gd`).
Konventionen (alle Karten): Konventionen (alle Karten):
- Tastaturcode-basiert: InputMap-Actions mit physischen `KEY_*`-Konstanten - Tastaturcode-basiert: InputMap-Actions mit physischen `KEY_*`-Konstanten
(`core/editor/shortcut_keys.gd`). Die physische Taste liegt unabhängig vom (`src/editor/shortcut_keys.gd`). Die physische Taste liegt unabhängig vom
Tastaturlayout gleich — deutsches (QWERTZ) und Windows-Layout verhalten sich Tastaturlayout gleich — deutsches (QWERTZ) und Windows-Layout verhalten sich
identisch, keine OS-Hardcodes (PLAN.md Plattform-Regel). identisch, keine OS-Hardcodes (PLAN.md Plattform-Regel).
- Jede Objekt-Operation = **EIN** ActionHistory-Eintrag (Strg+Z nimmt die - Jede Objekt-Operation = **EIN** ActionHistory-Eintrag (Strg+Z nimmt die

3206
library/loadout_katalog.json Normal file

File diff suppressed because it is too large Load diff

240
library/svmap_wasser.gd Normal file
View file

@ -0,0 +1,240 @@
class_name SvmapWasser
extends RefCounted
## Wasser pro Map (Karte t_28fdef03) — SPIEGELGLEICHES Modul in Client UND
## Map-Editor (Muster: library/terrain_arrays.gd, t_ab408127 — identischer
## Pfad in beiden Repos, identischer Code; Repos bleiben entkoppelt).
##
## Sly-Entscheidungen 20.10.: Wasserlevel-Ansatz (EINE Höhe pro Map — Senken
## unter der Höhe füllen sich automatisch), Wat-Trägheit (knietief, kein
## Schwimmen), Ufer-Automatik (schlammiges Overlay knapp unter Wasserhöhe).
##
## Verantwortung dieses Moduls (Dictionary rein, Node out — wie SvmapTerrain):
## - hat()/level(): optionales svmap-Feld `water_level` (float, y-Höhe in m).
## Abwesenheit oder Fehltyp = KEIN Wasser (Legacy-Maps unverändert).
## - baue(): EINE transparente Fläche auf Wasserhöhe über der ganzen Map
## (1 Draw-Call, keine Geometrie-Wogen — Wellen NUR im Fragment-Shader).
## - ufer_bild(): Tiefe-Feld (R8, depth = wasser − Terrainhöhe, geklemmt auf
## tiefe_max) als Textur — der Shader liest daraus Ufer-Schaum + Flach-
## Wasser-Transparenz, OHNE Screen-Space-Tricks.
## - ufer_faktor()/matsch_anwenden(): Ufer-Automatik — Zellen knapp unter
## Wasserhöhe bekommen den Matsch-Ton. BEWUSST nur eine abgeleitete Tönung
## (kein gespeichertes Feld): die Material-Schicht bleibt malbar, der
## Matsch liegt als Höhen-Regel drüber — Client UND Editor-Vorschau
## rechnen IDENTISCH (dieselbe Datei).
## - make_material(): dasselbe Material wie der Client (Identitäts-Vertrag
## der Vorschau) — Shader-Pfad je Repo über SHADER_KANDIDATEN.
##
## KEINE echte Spiegelung/Brechung/SSR (bewusste Absage, Performance):
## Spiegel-Impression = Skybox-Farbreflex (uniform) + Fresnel-Glanz.
##
## Wasserdistanz-Regel (Perf-Beweis-Plan): ferne Fragmente vereinfachen im
## Shader selbst (kein Wellen-/Schaum-Aufwand) — keine Per-Frame-Logik nötig.
## Shader-Pfade je Repo (erster vorhandener gewinnt — Spiegel-Dateien sind
## byteidentisch, siehe tools/sync_wasser.sh im Map-Editor).
const SHADER_KANDIDATEN: Array[String] = [
"res://shaders/wasser.gdshader", # scopedvr-client
"res://src/viewport/wasser.gdshader", # scopedvr-map-editor (Spiegel)
]
## Plausibilitätsbereich des svmap-Feldes `water_level` (Meter, y-Höhe).
## GLEICHE Werte wie V18 im Match-Server (svmap_loader) und im Editor
## (svmap_io) — nur Dokumentations-Konstante hier.
const LEVEL_MIN := -5.0
const LEVEL_MAX := 500.0
## Ufer-Bild: Tiefe in 0..tiefe_max m → 0..255 (R8). Mehr Tiefe braucht kein
## Detail — Schaum lebt in den ersten Dezimetern, Transparenz in den ersten Metern.
const TIEFE_MAX := 4.0
## Schaum-Band am Ufer (Meter Wassertiefe, in denen Schaum sichtbar ist).
const SCHAUM_BAND := 0.35
## Matsch-Ring (Ufer-Automatik): Zellen, deren Terrainhöhe weniger als
## MATSCH_HOEHE unter dem Wasserlevel liegt (bis MATSCH_UEBER leicht darüber,
## damit die Wasserlinie selbst matschig ist), werden zum Matsch-Ton überblendet.
const MATSCH_HOEHE := 0.55
const MATSCH_UEBER := 0.06
const MATSCH_FARBE := Color(0.34, 0.26, 0.18) # schlammiges Braun-Betonung
## Wetter-Farbkopplung (Entscheidung der Atmosphären-Karte): Wasser-Tint je
## Preset. farbe_flach = Seicht-Wasser, farbe_tief = tiefe Mitte, reflex =
## Skybox-Farbreflex (Himmelsfarbe des Presets), glanz = Glint-Stärke.
## Schlüssel = Client-Wetter-Keys (WeatherPresets). Unbekannt → "mittag".
const WETTER_WASSER := {
"mittag": {
"farbe_flach": Color(0.10, 0.30, 0.34),
"farbe_tief": Color(0.02, 0.10, 0.15),
"reflex": Color(0.72, 0.83, 0.92),
"glanz": 1.0,
},
"abend": {
"farbe_flach": Color(0.18, 0.26, 0.28),
"farbe_tief": Color(0.04, 0.07, 0.11),
"reflex": Color(0.95, 0.62, 0.38),
"glanz": 1.2,
},
"regen": {
"farbe_flach": Color(0.20, 0.24, 0.26),
"farbe_tief": Color(0.06, 0.09, 0.11),
"reflex": Color(0.55, 0.58, 0.62),
"glanz": 0.35,
},
"nacht": {
"farbe_flach": Color(0.02, 0.05, 0.09),
"farbe_tief": Color(0.0, 0.015, 0.03),
"reflex": Color(0.16, 0.22, 0.38),
"glanz": 0.9,
},
}
## Optionales svmap-Feld `water_level` vorhanden und brauchbar? Abwesenheit
## ODER Fehltyp = kein Wasser (Legacy-Maps unverändert, Fehltyp toleriert).
static func hat(data: Dictionary) -> bool:
var v: Variant = data.get("water_level", null)
return v is float or v is int
## Wasserhöhe aus der Map (INF = kein Wasser).
static func level(data: Dictionary) -> float:
var v: Variant = data.get("water_level", null)
if v is float or v is int:
return float(v)
return INF
## Wasserfläche bauen. terrain = der geparste terrain-Block, wasser = Wasser-
## Höhe (Meter), wetter = Preset-Key ("mittag"/"abend"/"regen"/"nacht").
## Rückgabe: Node3D mit EINER transparenten Fläche — null, wenn kein Wasser
## sinnvoll ist (kein Feld, kein Terrain, Wasserhöhe unter dem Tiefpunkt,
## ungültige Maße). Die Senken "füllen sich" von selbst: alles unter der
## Höhe liegt hinterher unter der Fläche.
static func baue(terrain: Dictionary, wasser: float, wetter: String) -> Node3D:
if not is_finite(wasser):
return null
var res := int(terrain.get("resolution", 0))
var size_x := float(terrain.get("size_x_m", 0.0))
var size_z := float(terrain.get("size_z_m", 0.0))
if res < 2 or size_x <= 0.0 or size_z <= 0.0:
return null
var heights := _decode_heights(terrain)
if heights.size() < res * res:
return null
var hmin := heights[0]
for h in heights:
if h < hmin:
hmin = h
if wasser <= hmin:
return null # Wasserhöhe unter dem Tiefpunkt — nichts gefüllt.
var root := Node3D.new()
root.name = "Wasser"
var mi := MeshInstance3D.new()
mi.name = "WasserFlaeche"
var plane := PlaneMesh.new()
plane.size = Vector2(size_x, size_z)
mi.mesh = plane
mi.position = Vector3(0.0, wasser, 0.0)
mi.material_override = make_material(wetter, ufer_bild(heights, res, wasser))
root.add_child(mi)
return root
## Ufer-Tiefe-Bild (R8): Pixel = Terrainhöhe der Zelle, R-Wert = Tiefe unter
## dem Wasserlevel (0 = Wasserlinie/Ufer, 255 = tiefe_max oder tiefer).
## Identische Index-Konvention wie das Höhenfeld: (0,0) = −X/−Z-Ecke, x → +X,
## z → +Z (svmap-Vertrag). Der PlaneMesh des Wassers überdeckt denselben
## Bereich (zentriert, UV 0..1), der Shader sampelt direkt mit UV.
static func ufer_bild(heights: PackedFloat32Array, res: int, wasser: float) -> Image:
var img := Image.create(res, res, false, Image.FORMAT_R8)
for z in res:
for x in res:
var d := wasser - heights[z * res + x] # Tiefe (negativ = Land)
var v := int(round(clampf(d / TIEFE_MAX, 0.0, 1.0) * 255.0))
img.set_pixel(x, z, Color8(v, 0, 0))
return img
## Ufer-Automatik (Matsch-Ring): Faktor 0 (Land/tief) … 1 (Uferstreifen).
## Band = MATSCH_HOEHE unter bis MATSCH_UEBER über dem Wasserlevel, weich
## (smoothstep) an beiden Kanten — kein Harter Pixel-Kantensprung.
static func ufer_faktor(h: float, wasser: float) -> float:
if not is_finite(wasser):
return 0.0
var d := wasser - h # Tiefe der Zelle (negativ = über Wasser)
var oben := clampf((d + MATSCH_UEBER) / MATSCH_UEBER, 0.0, 1.0)
if oben <= 0.0:
return 0.0
var unten := clampf(d / MATSCH_HOEHE, 0.0, 1.0)
var f := minf(oben, 1.0 - unten * unten * (3.0 - 2.0 * unten))
return clampf(f, 0.0, 1.0)
## Matsch-Ton auf eine Terrain-Zellfarbe anwenden (Client UND Editor rufen
## dieselbe Formel — Identität der Vorschau). faktor = ufer_faktor(...).
## BEWUSST nur RGB: die Alpha der Terrain-Zellfarbe trägt die gepackte
## Material-ID (m/N, Karte t_ab408127) — die darf der Matsch nie anfassen,
## sonst landen die Zellen im Splat-Shader auf dem falschen Textur-Layer.
static func matsch_anwenden(farbe: Color, faktor: float) -> Color:
var gemischt := farbe.lerp(MATSCH_FARBE, faktor * 0.75) # 0.75: Ton bleibt sichtbar
gemischt.a = farbe.a
return gemischt
## Wasser-Material (dasselbe im Client UND in der Editor-Vorschau —
## Identitäts-Vertrag): Custom-Shader + Wetter-Tint + Ufer-Tiefen-Textur.
## Fehlt der Shader (Frisch-Klon ohne Spiegel), fällt das Material auf ein
## simples transparentes StandardMaterial zurück — Wasser bleibt sichtbar.
static func make_material(wetter: String, ufer: Image = null) -> Material:
var preset: Dictionary = WETTER_WASSER.get(wetter, WETTER_WASSER["mittag"])
var shader := _shader_laden()
if shader != null:
var mat := ShaderMaterial.new()
mat.shader = shader
mat.set_shader_parameter("farbe_flach", preset["farbe_flach"])
mat.set_shader_parameter("farbe_tief", preset["farbe_tief"])
mat.set_shader_parameter("reflex", preset["reflex"])
mat.set_shader_parameter("glanz", float(preset["glanz"]))
if ufer != null:
mat.set_shader_parameter("ufer_tex", ImageTexture.create_from_image(ufer))
return mat
# Fallback ohne Shader (Robustheit — Wasser bleibt sichtbar, nur ohne Wellen).
var std := StandardMaterial3D.new()
std.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
std.albedo_color = Color(preset["farbe_flach"], 0.6)
std.roughness = 0.15
std.metallic = 0.1
return std
## Shader aus den Repo-Pfaden laden (erster vorhandener gewinnt).
static func _shader_laden() -> Shader:
for pfad in SHADER_KANDIDATEN:
if ResourceLoader.exists(pfad):
var s := load(pfad)
if s is Shader:
return s
return null
## Höhenfeld dekodieren (R16, identische Formel wie SvmapTerrain/SvmapIO/
## match-server — hier NOCHMAL lokal, damit dieses Spiegel-Modul von keinem
## Repo-Skript abhängt; 15 Zeilen Duplikat gegen Repo-Kopplung getauscht).
static func _decode_heights(terrain: Dictionary) -> PackedFloat32Array:
var res := int(terrain.get("resolution", 0))
var out := PackedFloat32Array()
if res < 2:
return out
out.resize(res * res)
var raw: PackedByteArray = Marshalls.base64_to_raw(str(terrain.get("heightmap_r16_b64", "")))
if raw.size() < res * res * 2:
return PackedFloat32Array() # kaputtes/fehlendes Höhenfeld → kein Wasser
var hmin := float(terrain.get("min_height_m", 0.0))
var hmax := float(terrain.get("max_height_m", 1.0))
if hmax <= hmin:
hmax = hmin + 1.0
var n := mini(raw.size() / 2, res * res)
for i in n:
var v: int = raw.decode_u16(i * 2)
out[i] = hmin + (float(v) / 65535.0) * (hmax - hmin)
return out

View file

@ -0,0 +1 @@
uid://cppnjm0fhofpa

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bsnwxasr4kxnw"
path="res://.godot/imported/acacia_003.png-780c300e868256b9a9eb8bee97e274d7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/acacia_003.png"
dest_files=["res://.godot/imported/acacia_003.png-780c300e868256b9a9eb8bee97e274d7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dpng4qbmhgaxn"
path="res://.godot/imported/acacia_005.png-13391d2a2fbb9e1af9a9899676d6255b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/acacia_005.png"
dest_files=["res://.godot/imported/acacia_005.png-13391d2a2fbb9e1af9a9899676d6255b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://0vjob86u0mkh"
path="res://.godot/imported/acacia_006.png-543980dded5a44fa5e3d06584f5fe696.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/acacia_006.png"
dest_files=["res://.godot/imported/acacia_006.png-543980dded5a44fa5e3d06584f5fe696.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dxjkjfi837uo7"
path="res://.godot/imported/air_defense_001.png-b437d643c7024a7d8c111897c2ab61ec.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/air_defense_001.png"
dest_files=["res://.godot/imported/air_defense_001.png-b437d643c7024a7d8c111897c2ab61ec.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dmkfqnti7k861"
path="res://.godot/imported/air_defense_002.png-53fc58f2e7263c151a9515b088d1f631.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/air_defense_002.png"
dest_files=["res://.godot/imported/air_defense_002.png-53fc58f2e7263c151a9515b088d1f631.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cyvumt7k7tfkt"
path="res://.godot/imported/air_defense_platform_001.png-f3f7931f8c44d060f84082b8977bf627.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/air_defense_platform_001.png"
dest_files=["res://.godot/imported/air_defense_platform_001.png-f3f7931f8c44d060f84082b8977bf627.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://blm2ew1gswl1e"
path="res://.godot/imported/air_defense_platform_002.png-0a5ce0c8e1362f387319264873cf529f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/air_defense_platform_002.png"
dest_files=["res://.godot/imported/air_defense_platform_002.png-0a5ce0c8e1362f387319264873cf529f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cu2km1kitwe32"
path="res://.godot/imported/antenna_001.png-4dad960bc19a6b3ab31294105028d1e9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/antenna_001.png"
dest_files=["res://.godot/imported/antenna_001.png-4dad960bc19a6b3ab31294105028d1e9.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
library/thumbs/apc_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ckp5nasktsw6d"
path="res://.godot/imported/apc_001.png-9e80d2bf93d5fc993e616b7b32e8b724.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/apc_001.png"
dest_files=["res://.godot/imported/apc_001.png-9e80d2bf93d5fc993e616b7b32e8b724.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
library/thumbs/apc_002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b75b4yhnejh2m"
path="res://.godot/imported/apc_002.png-523f4a9b408f9b8c5eb6be67985b0502.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/apc_002.png"
dest_files=["res://.godot/imported/apc_002.png-523f4a9b408f9b8c5eb6be67985b0502.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
library/thumbs/apc_cap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://08m553i6la3k"
path="res://.godot/imported/apc_cap.png-23a774749caddf07feaf320bf58cabdd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/apc_cap.png"
dest_files=["res://.godot/imported/apc_cap.png-23a774749caddf07feaf320bf58cabdd.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dkxw5r6shmdeo"
path="res://.godot/imported/apc_cap_1.png-d621f9e71ef0b7877f2b15a48d673ee9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/apc_cap_1.png"
dest_files=["res://.godot/imported/apc_cap_1.png-d621f9e71ef0b7877f2b15a48d673ee9.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://clmsnievmhcdv"
path="res://.godot/imported/apc_weapon.png-c1a66e252a3e0b0605bbbabef999fd28.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/apc_weapon.png"
dest_files=["res://.godot/imported/apc_weapon.png-c1a66e252a3e0b0605bbbabef999fd28.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bhqfuk76plea1"
path="res://.godot/imported/barracks_001.png-155b2e8dc1723ecd52306bc4563ba954.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barracks_001.png"
dest_files=["res://.godot/imported/barracks_001.png-155b2e8dc1723ecd52306bc4563ba954.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://8d3x37mwv6xr"
path="res://.godot/imported/barrel_001.png-6908404f7d70f7a5c32b5535b30c54e6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_001.png"
dest_files=["res://.godot/imported/barrel_001.png-6908404f7d70f7a5c32b5535b30c54e6.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://x036pdk80s03"
path="res://.godot/imported/barrel_002.png-9223e213854153a1cae1e6683aba7a93.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_002.png"
dest_files=["res://.godot/imported/barrel_002.png-9223e213854153a1cae1e6683aba7a93.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://clandxfaibniy"
path="res://.godot/imported/barrel_003.png-dced15cfc3e6c765081d15d50c27a968.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_003.png"
dest_files=["res://.godot/imported/barrel_003.png-dced15cfc3e6c765081d15d50c27a968.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://r3uuorqbq6g7"
path="res://.godot/imported/barrel_004.png-94267e67e4b759e20fe5900b875a4eef.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_004.png"
dest_files=["res://.godot/imported/barrel_004.png-94267e67e4b759e20fe5900b875a4eef.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d1bivgvwdvioh"
path="res://.godot/imported/barrel_005.png-9542d468a6873a8fd580ad9cdef4fd0f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_005.png"
dest_files=["res://.godot/imported/barrel_005.png-9542d468a6873a8fd580ad9cdef4fd0f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://3vp6eyi4g2bj"
path="res://.godot/imported/barrel_006.png-5890db03d20712e23b0aeb0f28d480ae.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_006.png"
dest_files=["res://.godot/imported/barrel_006.png-5890db03d20712e23b0aeb0f28d480ae.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b4atq8e72m7my"
path="res://.godot/imported/barrel_007.png-d90f90a76918ff4bbd0adc316d5ffdf8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_007.png"
dest_files=["res://.godot/imported/barrel_007.png-d90f90a76918ff4bbd0adc316d5ffdf8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://co0hauu3itiu3"
path="res://.godot/imported/barrel_008.png-5f57f9825de2472879e769759a4679a8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_008.png"
dest_files=["res://.godot/imported/barrel_008.png-5f57f9825de2472879e769759a4679a8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cwb0huh22ist2"
path="res://.godot/imported/barrel_009.png-b83ebf0290fccdfee558913551761413.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_009.png"
dest_files=["res://.godot/imported/barrel_009.png-b83ebf0290fccdfee558913551761413.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://nwboqq7bq5cc"
path="res://.godot/imported/barrel_010.png-8e9db8f1f68c4fdee8260e5894adfe86.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_010.png"
dest_files=["res://.godot/imported/barrel_010.png-8e9db8f1f68c4fdee8260e5894adfe86.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bf2orfs8g27pk"
path="res://.godot/imported/barrel_011.png-e6f72afda072cf9838ed06700c6b5ebd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_011.png"
dest_files=["res://.godot/imported/barrel_011.png-e6f72afda072cf9838ed06700c6b5ebd.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dddfloskgqjui"
path="res://.godot/imported/barrel_012.png-69afd264c2078adae3355c1036520a7e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_012.png"
dest_files=["res://.godot/imported/barrel_012.png-69afd264c2078adae3355c1036520a7e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://8wcuxv0v56js"
path="res://.godot/imported/barrel_013.png-3b6581a9a4637f3f79f372095dbc1b6a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrel_013.png"
dest_files=["res://.godot/imported/barrel_013.png-3b6581a9a4637f3f79f372095dbc1b6a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drn6mij5p6hv5"
path="res://.godot/imported/barrier_001.png-f4f9b068e708eb35cf4f0689fd3c6f74.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_001.png"
dest_files=["res://.godot/imported/barrier_001.png-f4f9b068e708eb35cf4f0689fd3c6f74.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dbhbgk7y8j54p"
path="res://.godot/imported/barrier_002.png-226efbbe1880285f86c95e9a1be7518e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_002.png"
dest_files=["res://.godot/imported/barrier_002.png-226efbbe1880285f86c95e9a1be7518e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ccvfh73fkfikq"
path="res://.godot/imported/barrier_003.png-22cd5cdb2ab7396c5afef5360b5067dd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_003.png"
dest_files=["res://.godot/imported/barrier_003.png-22cd5cdb2ab7396c5afef5360b5067dd.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bgc3wwy6b41b0"
path="res://.godot/imported/barrier_004.png-980b996df5a61642aa5a8982d9429ff5.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_004.png"
dest_files=["res://.godot/imported/barrier_004.png-980b996df5a61642aa5a8982d9429ff5.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dmkxussbxmumo"
path="res://.godot/imported/barrier_005.png-9239fc40b7d66072393ec05a176a0899.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_005.png"
dest_files=["res://.godot/imported/barrier_005.png-9239fc40b7d66072393ec05a176a0899.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjhuexnsl151p"
path="res://.godot/imported/barrier_006.png-a42842c6692537da809bd937c042edfc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_006.png"
dest_files=["res://.godot/imported/barrier_006.png-a42842c6692537da809bd937c042edfc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://shriffj4a6cq"
path="res://.godot/imported/barrier_007.png-18ef1a4c7bc9c8b4fdc1b04d8b91dad1.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_007.png"
dest_files=["res://.godot/imported/barrier_007.png-18ef1a4c7bc9c8b4fdc1b04d8b91dad1.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cgio2t6y3iuik"
path="res://.godot/imported/barrier_008.png-f615e24e39cbfa5c4030021171d3bcc5.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_008.png"
dest_files=["res://.godot/imported/barrier_008.png-f615e24e39cbfa5c4030021171d3bcc5.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://btki2yug25x23"
path="res://.godot/imported/barrier_009.png-f723a86b81fe9b5d3d1a1ae7a8338df0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_009.png"
dest_files=["res://.godot/imported/barrier_009.png-f723a86b81fe9b5d3d1a1ae7a8338df0.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjs7nqt4rl4qr"
path="res://.godot/imported/barrier_010.png-eb2f5f66658412f8d13ad87794ce497c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_010.png"
dest_files=["res://.godot/imported/barrier_010.png-eb2f5f66658412f8d13ad87794ce497c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://j0rac170fjwn"
path="res://.godot/imported/barrier_011.png-6f858b9fbbf01e80766f67abd167b690.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_011.png"
dest_files=["res://.godot/imported/barrier_011.png-6f858b9fbbf01e80766f67abd167b690.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://e6r8uhiswdsx"
path="res://.godot/imported/barrier_012.png-977a45ba313cbc8a3475c3b4dd181d97.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_012.png"
dest_files=["res://.godot/imported/barrier_012.png-977a45ba313cbc8a3475c3b4dd181d97.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://6bx8ua6gpxf2"
path="res://.godot/imported/barrier_013.png-873e6d19995610db66444c98f588223d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_013.png"
dest_files=["res://.godot/imported/barrier_013.png-873e6d19995610db66444c98f588223d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://kbu8pt3aad80"
path="res://.godot/imported/barrier_014.png-e442cc785a151929218fc114716542eb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://library/thumbs/barrier_014.png"
dest_files=["res://.godot/imported/barrier_014.png-e442cc785a151929218fc114716542eb.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Some files were not shown because too many files have changed in this diff Show more