VR-Host: main läuft ohne Fenster, Modelle aus dem Projekt, Terrain in Stücken (nur Quest)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d196086cce
commit
83af25b6fb
7 changed files with 234 additions and 8 deletions
|
|
@ -355,6 +355,7 @@ func _process(delta: float) -> void:
|
||||||
var changed := _height_step(_cursor.x, _cursor.z, amount)
|
var changed := _height_step(_cursor.x, _cursor.z, amount)
|
||||||
if changed:
|
if changed:
|
||||||
_expand_range_if_needed()
|
_expand_range_if_needed()
|
||||||
|
_melde_geaenderte_hoehen()
|
||||||
# Karte t_55ed041a: Material-Schicht MITGEBEN — ohne sie hat der
|
# Karte t_55ed041a: Material-Schicht MITGEBEN — ohne sie hat der
|
||||||
# Live-Rebuild alles als Gras gerendert (Sand/Fels „verschwanden"
|
# Live-Rebuild alles als Gras gerendert (Sand/Fels „verschwanden"
|
||||||
# während des Heben/Senk-Strichs und kehrten erst am Stroke-Ende
|
# während des Heben/Senk-Strichs und kehrten erst am Stroke-Ende
|
||||||
|
|
@ -363,6 +364,19 @@ func _process(delta: float) -> void:
|
||||||
_ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3
|
_ring.position.y = _height_at_stroke(_cursor.x, _cursor.z) + 0.3
|
||||||
|
|
||||||
|
|
||||||
|
func _melde_geaenderte_hoehen() -> void:
|
||||||
|
if not view_builder.has_method("mark_terrain_dirty"):
|
||||||
|
return
|
||||||
|
var r := _brush_region(_world_to_grid(_cursor.x, _cursor.z))
|
||||||
|
view_builder.call("mark_terrain_dirty", r)
|
||||||
|
if _sym_active():
|
||||||
|
var ecke_a: Vector2i = SymmetryCore.mirror_cell(_sym_mode(), r.position.x, r.position.y, _res)
|
||||||
|
var ecke_b: Vector2i = SymmetryCore.mirror_cell(_sym_mode(), r.end.x - 1, r.end.y - 1, _res)
|
||||||
|
var spiegel := Rect2i(mini(ecke_a.x, ecke_b.x), mini(ecke_a.y, ecke_b.y),
|
||||||
|
absi(ecke_a.x - ecke_b.x) + 1, absi(ecke_a.y - ecke_b.y) + 1)
|
||||||
|
view_builder.call("mark_terrain_dirty", spiegel)
|
||||||
|
|
||||||
|
|
||||||
## Stärke dieses Frames: Meter (Heben/Senken) bzw. Mischfaktor 0–1 (Glätten/
|
## Stärke dieses Frames: Meter (Heben/Senken) bzw. Mischfaktor 0–1 (Glätten/
|
||||||
## Einebnen) — skaliert mit dem Druck.
|
## Einebnen) — skaliert mit dem Druck.
|
||||||
func _step_amount(mode: int, dt: float) -> float:
|
func _step_amount(mode: int, dt: float) -> float:
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,9 @@ static func panorama_textur(id: String) -> Texture2D:
|
||||||
return _tex_cache[id]
|
return _tex_cache[id]
|
||||||
var tex: Texture2D = null
|
var tex: Texture2D = null
|
||||||
var pfad := textur_pfad(id)
|
var pfad := textur_pfad(id)
|
||||||
if not pfad.is_empty() and FileAccess.file_exists(pfad):
|
if pfad.begins_with("res://"):
|
||||||
|
tex = load(pfad) as Texture2D
|
||||||
|
elif not pfad.is_empty() and FileAccess.file_exists(pfad):
|
||||||
var img := Image.load_from_file(pfad)
|
var img := Image.load_from_file(pfad)
|
||||||
if img != null:
|
if img != null:
|
||||||
if img.is_compressed():
|
if img.is_compressed():
|
||||||
|
|
|
||||||
34
ui/main.gd
34
ui/main.gd
|
|
@ -194,6 +194,11 @@ var test_btn: Button
|
||||||
var _dock_offsets: Dictionary = {}
|
var _dock_offsets: Dictionary = {}
|
||||||
var _split_lagen_playtest: Dictionary = {} # Playtest: Trenner-Lagen (Sly-Befund 20.10.)
|
var _split_lagen_playtest: Dictionary = {} # Playtest: Trenner-Lagen (Sly-Befund 20.10.)
|
||||||
|
|
||||||
|
## Läuft im Spiel auf der Brille: kein Fenster, kein Selbst-Update, Karten in user://.
|
||||||
|
var vr_host := false
|
||||||
|
signal vr_test_gewuenscht
|
||||||
|
signal vr_beenden_gewuenscht
|
||||||
|
|
||||||
const GizmoScript := preload("res://core/viewport/gizmo.gd")
|
const GizmoScript := preload("res://core/viewport/gizmo.gd")
|
||||||
const INSPECTOR_PANEL := preload("res://core/ui/inspector_panel.gd")
|
const INSPECTOR_PANEL := preload("res://core/ui/inspector_panel.gd")
|
||||||
|
|
||||||
|
|
@ -210,10 +215,13 @@ func _ready() -> void:
|
||||||
# Fenster-Zustand (Karte t_297221b5): Default MAXIMIERT (kein Vollbild);
|
# Fenster-Zustand (Karte t_297221b5): Default MAXIMIERT (kein Vollbild);
|
||||||
# wer auf Fenstergröße umschaltet, bekommt die gemerkte Lage wieder.
|
# wer auf Fenstergröße umschaltet, bekommt die gemerkte Lage wieder.
|
||||||
# Close-Request selbst behandeln, damit die Lage VOR dem Quit gemerkt wird.
|
# Close-Request selbst behandeln, damit die Lage VOR dem Quit gemerkt wird.
|
||||||
get_tree().set_auto_accept_quit(false)
|
if not vr_host:
|
||||||
_apply_window_state()
|
get_tree().set_auto_accept_quit(false)
|
||||||
|
_apply_window_state()
|
||||||
_load_dock_state()
|
_load_dock_state()
|
||||||
_start_with_new_map()
|
_start_with_new_map()
|
||||||
|
if vr_host:
|
||||||
|
return
|
||||||
_maybe_start_demo_mode()
|
_maybe_start_demo_mode()
|
||||||
# Selbst-Update (Karte t_d95f087a): STILLER Start-Check im Hintergrund —
|
# Selbst-Update (Karte t_d95f087a): STILLER Start-Check im Hintergrund —
|
||||||
# nur bei „Update verfügbar“ erscheint das Banner; offline/Tunnel weg
|
# nur bei „Update verfügbar“ erscheint das Banner; offline/Tunnel weg
|
||||||
|
|
@ -361,6 +369,9 @@ func _build_group_tabs(gruppe: String) -> TabContainer:
|
||||||
tabs.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
tabs.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
tabs.add_theme_constant_override("separation", UIScale.px(8))
|
tabs.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
var entries: Array = PanelRegistryScript.entries(gruppe)
|
var entries: Array = PanelRegistryScript.entries(gruppe)
|
||||||
|
if vr_host:
|
||||||
|
# Server-Verwaltung braucht den Admin-Schlüssel und bleibt am PC.
|
||||||
|
entries = entries.filter(func(e: Dictionary) -> bool: return str(e["id"]) != "server")
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
var script: GDScript = load(str(entry["skript"]))
|
var script: GDScript = load(str(entry["skript"]))
|
||||||
var panel: Control = script.new()
|
var panel: Control = script.new()
|
||||||
|
|
@ -514,7 +525,10 @@ func _build_dialogs_and_overlays() -> void:
|
||||||
|
|
||||||
open_dialog = FileDialog.new()
|
open_dialog = FileDialog.new()
|
||||||
open_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
open_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||||
open_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
open_dialog.access = FileDialog.ACCESS_USERDATA if vr_host else FileDialog.ACCESS_FILESYSTEM
|
||||||
|
if vr_host:
|
||||||
|
DirAccess.make_dir_recursive_absolute("user://maps")
|
||||||
|
open_dialog.current_dir = "user://maps"
|
||||||
open_dialog.title = "Map öffnen"
|
open_dialog.title = "Map öffnen"
|
||||||
open_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
open_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
||||||
open_dialog.size = UIScale.v2i(800, 560)
|
open_dialog.size = UIScale.v2i(800, 560)
|
||||||
|
|
@ -523,7 +537,9 @@ func _build_dialogs_and_overlays() -> void:
|
||||||
|
|
||||||
save_dialog = FileDialog.new()
|
save_dialog = FileDialog.new()
|
||||||
save_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
save_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||||||
save_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
save_dialog.access = FileDialog.ACCESS_USERDATA if vr_host else FileDialog.ACCESS_FILESYSTEM
|
||||||
|
if vr_host:
|
||||||
|
save_dialog.current_dir = "user://maps"
|
||||||
save_dialog.title = "Map speichern unter"
|
save_dialog.title = "Map speichern unter"
|
||||||
save_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
save_dialog.filters = PackedStringArray(["*.svmap ; ScopedVR Map"])
|
||||||
save_dialog.size = UIScale.v2i(800, 560)
|
save_dialog.size = UIScale.v2i(800, 560)
|
||||||
|
|
@ -900,6 +916,10 @@ func _save_window_state() -> void:
|
||||||
|
|
||||||
## Ein Ausgang für beide Beenden-Wege (Menü + Fenster-X).
|
## Ein Ausgang für beide Beenden-Wege (Menü + Fenster-X).
|
||||||
func _quit_editor() -> void:
|
func _quit_editor() -> void:
|
||||||
|
if vr_host:
|
||||||
|
_save_dock_state()
|
||||||
|
vr_beenden_gewuenscht.emit()
|
||||||
|
return
|
||||||
_save_window_state()
|
_save_window_state()
|
||||||
_save_dock_state()
|
_save_dock_state()
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|
@ -1248,6 +1268,9 @@ func _on_test_run() -> void:
|
||||||
if doc.is_empty():
|
if doc.is_empty():
|
||||||
_set_status("Erst eine Map anlegen (Datei → Neue Map), dann testen.", true)
|
_set_status("Erst eine Map anlegen (Datei → Neue Map), dann testen.", true)
|
||||||
return
|
return
|
||||||
|
if vr_host:
|
||||||
|
vr_test_gewuenscht.emit()
|
||||||
|
return
|
||||||
_start_playtest()
|
_start_playtest()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2379,7 +2402,8 @@ func _refresh_file_label() -> void:
|
||||||
file_label.tooltip_text = file_label.text
|
file_label.tooltip_text = file_label.text
|
||||||
# U7 (Karte t_2336c58e): ungespeicherte Änderungen auch im FENSTERTITEL —
|
# U7 (Karte t_2336c58e): ungespeicherte Änderungen auch im FENSTERTITEL —
|
||||||
# so sieht man sie selbst dann, wenn die Datei-Leiste egalisiert ist.
|
# so sieht man sie selbst dann, wenn die Datei-Leiste egalisiert ist.
|
||||||
get_window().title = "%s — %s%s" % [APP_TITLE, map_name,
|
if not vr_host:
|
||||||
|
get_window().title = "%s — %s%s" % [APP_TITLE, map_name,
|
||||||
" (ungespeichert)" if dirty else ""]
|
" (ungespeichert)" if dirty else ""]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,9 @@ func build() -> void:
|
||||||
popup.add_separator()
|
popup.add_separator()
|
||||||
popup.add_item("▶ Karte testen", 5)
|
popup.add_item("▶ Karte testen", 5)
|
||||||
popup.add_separator()
|
popup.add_separator()
|
||||||
popup.add_item("🖐 Hand-Haltung der Waffen…", 8)
|
if not main.vr_host:
|
||||||
popup.add_item("🔄 Updates prüfen", 7)
|
popup.add_item("🖐 Hand-Haltung der Waffen…", 8)
|
||||||
|
popup.add_item("🔄 Updates prüfen", 7)
|
||||||
popup.add_item("Beenden", 4)
|
popup.add_item("Beenden", 4)
|
||||||
# FIX 20.10. (Sly-Befund „Kartengröße ändern tut nichts“): id_pressed sendet
|
# FIX 20.10. (Sly-Befund „Kartengröße ändern tut nichts“): id_pressed sendet
|
||||||
# die Menü-ID. main.call.bind("_on_menu_action") hängt die ID aber ALS
|
# die Menü-ID. main.call.bind("_on_menu_action") hängt die ID aber ALS
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,14 @@ static func mesh_fuer(oid: String) -> Mesh:
|
||||||
return BauKatalog.mesh(oid)
|
return BauKatalog.mesh(oid)
|
||||||
if _cache.has(oid):
|
if _cache.has(oid):
|
||||||
return _cache[oid] if _cache[oid] is Mesh else null
|
return _cache[oid] if _cache[oid] is Mesh else null
|
||||||
|
# Im Spiel sind die Modelle importierter Teil des Projekts.
|
||||||
|
var im_projekt := _projekt_pfad(oid)
|
||||||
|
if not im_projekt.is_empty():
|
||||||
|
var szene: Node = (load(im_projekt) as PackedScene).instantiate()
|
||||||
|
var aus_projekt := _zusammenbacken(szene)
|
||||||
|
szene.free()
|
||||||
|
_cache[oid] = aus_projekt if aus_projekt != null else false
|
||||||
|
return aus_projekt
|
||||||
for pfad in _kandidaten(oid):
|
for pfad in _kandidaten(oid):
|
||||||
if FileAccess.file_exists(pfad):
|
if FileAccess.file_exists(pfad):
|
||||||
var m := _glb_mesh(pfad)
|
var m := _glb_mesh(pfad)
|
||||||
|
|
@ -198,6 +206,13 @@ static func mesh_fuer(oid: String) -> Mesh:
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
static func _projekt_pfad(oid: String) -> String:
|
||||||
|
var rel := str(_eintrag(oid).get("glb", ""))
|
||||||
|
if rel.begins_with("res://") and ResourceLoader.exists(rel, "PackedScene"):
|
||||||
|
return rel
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
## Liegt für die OID überhaupt eine GLB-Datei im Lager? (Billiger Check ohne
|
## Liegt für die OID überhaupt eine GLB-Datei im Lager? (Billiger Check ohne
|
||||||
## Parse — für Statuszeilen-Hinweise beim Platzieren.)
|
## Parse — für Statuszeilen-Hinweise beim Platzieren.)
|
||||||
static func fehlt(oid: String) -> bool:
|
static func fehlt(oid: String) -> bool:
|
||||||
|
|
@ -207,6 +222,8 @@ static func fehlt(oid: String) -> bool:
|
||||||
return false
|
return false
|
||||||
if _cache.has(oid):
|
if _cache.has(oid):
|
||||||
return not (_cache[oid] is Mesh)
|
return not (_cache[oid] is Mesh)
|
||||||
|
if not _projekt_pfad(oid).is_empty():
|
||||||
|
return false
|
||||||
for pfad in _kandidaten(oid):
|
for pfad in _kandidaten(oid):
|
||||||
if FileAccess.file_exists(pfad):
|
if FileAccess.file_exists(pfad):
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,12 @@ func update_terrain(heights: PackedFloat32Array, mats: PackedByteArray = PackedB
|
||||||
terrain_view.call("update_heights", heights, 0, 0.0, 0.0, mats, splat_rect_cells)
|
terrain_view.call("update_heights", heights, 0, 0.0, 0.0, mats, splat_rect_cells)
|
||||||
|
|
||||||
|
|
||||||
|
## Pinsel meldet die geänderten Höhenpunkte (nur im Stücke-Modus genutzt).
|
||||||
|
func mark_terrain_dirty(rect: Rect2i) -> void:
|
||||||
|
if terrain_view != null and is_instance_valid(terrain_view):
|
||||||
|
terrain_view.call("markiere_hoehen", rect)
|
||||||
|
|
||||||
|
|
||||||
## Misch-Schicht für das nächste Live-Update (Malen mit weichem Rand, Karte 25.10.).
|
## Misch-Schicht für das nächste Live-Update (Malen mit weichem Rand, Karte 25.10.).
|
||||||
func set_terrain_mix(mix_b: PackedByteArray, mix_w: PackedByteArray) -> void:
|
func set_terrain_mix(mix_b: PackedByteArray, mix_w: PackedByteArray) -> void:
|
||||||
if terrain_view != null and is_instance_valid(terrain_view):
|
if terrain_view != null and is_instance_valid(terrain_view):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
extends Node3D
|
extends Node3D
|
||||||
|
class_name TerrainMeshView
|
||||||
## Terrain-Ansicht: baut das Höhenfeld (svmap terrain-Block) als Mesh im
|
## Terrain-Ansicht: baut das Höhenfeld (svmap terrain-Block) als Mesh im
|
||||||
## Editor-Viewport (M2, Karte t_9aba04f1).
|
## Editor-Viewport (M2, Karte t_9aba04f1).
|
||||||
##
|
##
|
||||||
|
|
@ -73,6 +74,20 @@ const CONTOUR_MIN_SPAN := 2.0
|
||||||
const CONTOUR_SHADER := "res://core/viewport/terrain_contour.gdshader"
|
const CONTOUR_SHADER := "res://core/viewport/terrain_contour.gdshader"
|
||||||
|
|
||||||
var _mi: MeshInstance3D
|
var _mi: MeshInstance3D
|
||||||
|
|
||||||
|
## Quest: Terrain in Stücken, ein Pinselschritt schreibt nur die getroffenen
|
||||||
|
## Punkte neu (Messung 31.10.: 3 ms statt 132 ms bei Raster 257). Am PC reicht
|
||||||
|
## das ganze Mesh, dort läuft außerdem der Kontur-Shader.
|
||||||
|
static var stuecke_an := OS.has_feature("android")
|
||||||
|
## Eigenes Terrain-Material der App (Quest: der leichte Spiel-Shader).
|
||||||
|
static var material_fabrik := Callable()
|
||||||
|
const STUECK_ZELLEN := 32
|
||||||
|
var _stuecke: Array[Dictionary] = []
|
||||||
|
var _stuecke_res := 0
|
||||||
|
var _stuecke_groesse := Vector2.ZERO
|
||||||
|
var _dirty: Array[Rect2i] = []
|
||||||
|
var _hmin := 0.0
|
||||||
|
var _hmax := 1.0
|
||||||
var _res := 0
|
var _res := 0
|
||||||
var _size_x := 0.0
|
var _size_x := 0.0
|
||||||
var _size_z := 0.0
|
var _size_z := 0.0
|
||||||
|
|
@ -123,6 +138,8 @@ func _ensure_mesh_instance() -> void:
|
||||||
## StandardMaterial3D zurück, wenn der Shader nicht ladbar ist (Robustheit —
|
## StandardMaterial3D zurück, wenn der Shader nicht ladbar ist (Robustheit —
|
||||||
## Terrain bleibt immer sichtbar).
|
## Terrain bleibt immer sichtbar).
|
||||||
func _make_material() -> Material:
|
func _make_material() -> Material:
|
||||||
|
if material_fabrik.is_valid():
|
||||||
|
return material_fabrik.call()
|
||||||
_shader = load(CONTOUR_SHADER)
|
_shader = load(CONTOUR_SHADER)
|
||||||
if _shader != null:
|
if _shader != null:
|
||||||
var mat := ShaderMaterial.new()
|
var mat := ShaderMaterial.new()
|
||||||
|
|
@ -181,6 +198,7 @@ func rebuild_from_doc(data: Dictionary) -> void:
|
||||||
_triangles = 0
|
_triangles = 0
|
||||||
if _mi != null:
|
if _mi != null:
|
||||||
_mi.mesh = null
|
_mi.mesh = null
|
||||||
|
_stuecke_leeren()
|
||||||
return
|
return
|
||||||
# Splat-Textur verwerfen — sie wird im update_heights frisch aus der
|
# Splat-Textur verwerfen — sie wird im update_heights frisch aus der
|
||||||
# (nach Undo/Load evtl. geänderten) Zell-Schicht abgeleitet.
|
# (nach Undo/Load evtl. geänderten) Zell-Schicht abgeleitet.
|
||||||
|
|
@ -227,6 +245,12 @@ func update_heights(heights: PackedFloat32Array, res: int = 0, size_x: float = 0
|
||||||
elif not mats.is_empty():
|
elif not mats.is_empty():
|
||||||
_mats_cache = mats
|
_mats_cache = mats
|
||||||
var mres := mat_res_of(mats, res)
|
var mres := mat_res_of(mats, res)
|
||||||
|
if stuecke_an:
|
||||||
|
# Mal-Schritt (Rechteck gesetzt): Vertices hängen nicht am Material.
|
||||||
|
if not splat_rect_cells.has_area():
|
||||||
|
_stuecke_schreiben(heights, res, size_x, size_z, mats, mres)
|
||||||
|
_splat_aktualisieren(size_x, size_z, mats, mres)
|
||||||
|
return
|
||||||
var last := float(res - 1)
|
var last := float(res - 1)
|
||||||
var positions := PackedVector3Array()
|
var positions := PackedVector3Array()
|
||||||
var normals := PackedVector3Array()
|
var normals := PackedVector3Array()
|
||||||
|
|
@ -319,6 +343,10 @@ func update_heights(heights: PackedFloat32Array, res: int = 0, size_x: float = 0
|
||||||
_mi.mesh = mesh
|
_mi.mesh = mesh
|
||||||
_triangles = indices.size() / 3
|
_triangles = indices.size() / 3
|
||||||
|
|
||||||
|
_splat_aktualisieren(size_x, size_z, mats, mres)
|
||||||
|
|
||||||
|
|
||||||
|
func _splat_aktualisieren(size_x: float, size_z: float, mats: PackedByteArray, mres: int) -> void:
|
||||||
# --- Splat-Gewichtstextur (t_7a0a6b79): map_size + Sub-Rect-Update --------
|
# --- Splat-Gewichtstextur (t_7a0a6b79): map_size + Sub-Rect-Update --------
|
||||||
# map_size_x/z in Weltmetern (Shader rechnet map_uv = Weltkoord. über Map).
|
# map_size_x/z in Weltmetern (Shader rechnet map_uv = Weltkoord. über Map).
|
||||||
# Mal-Stroke: splat_rect_cells = betroffenes Zellrechteck → NUR die Region
|
# Mal-Stroke: splat_rect_cells = betroffenes Zellrechteck → NUR die Region
|
||||||
|
|
@ -343,6 +371,140 @@ func update_heights(heights: PackedFloat32Array, res: int = 0, size_x: float = 0
|
||||||
_build_splat(shm, mats, mres, size_x, size_z)
|
_build_splat(shm, mats, mres, size_x, size_z)
|
||||||
|
|
||||||
|
|
||||||
|
## Pinsel meldet die geänderten Höhenpunkte, das nächste Update schreibt nur sie.
|
||||||
|
func markiere_hoehen(rect: Rect2i) -> void:
|
||||||
|
_dirty.append(rect)
|
||||||
|
|
||||||
|
|
||||||
|
func _stuecke_leeren() -> void:
|
||||||
|
for s in _stuecke:
|
||||||
|
(s["mi"] as Node).queue_free()
|
||||||
|
_stuecke.clear()
|
||||||
|
_stuecke_res = 0
|
||||||
|
_dirty.clear()
|
||||||
|
|
||||||
|
|
||||||
|
func _stuecke_schreiben(heights: PackedFloat32Array, res: int, size_x: float, size_z: float,
|
||||||
|
mats: PackedByteArray, mres: int) -> void:
|
||||||
|
var groesse := Vector2(size_x, size_z)
|
||||||
|
var neu_bauen := _stuecke_res != res or _stuecke_groesse != groesse
|
||||||
|
if neu_bauen:
|
||||||
|
_stuecke_leeren()
|
||||||
|
_stuecke_res = res
|
||||||
|
_stuecke_groesse = groesse
|
||||||
|
var je_seite := int(ceil(float(res - 1) / float(STUECK_ZELLEN)))
|
||||||
|
for cz in je_seite:
|
||||||
|
for cx in je_seite:
|
||||||
|
_stuecke.append(_stueck_anlegen(cx, cz, res))
|
||||||
|
_triangles = (res - 1) * (res - 1) * 2
|
||||||
|
var alles := neu_bauen or _dirty.is_empty()
|
||||||
|
if alles:
|
||||||
|
# Farbskala nur beim vollen Durchlauf neu, im Strich bleibt sie stehen.
|
||||||
|
_hmin = INF
|
||||||
|
_hmax = -INF
|
||||||
|
for v in heights:
|
||||||
|
_hmin = minf(_hmin, v)
|
||||||
|
_hmax = maxf(_hmax, v)
|
||||||
|
_last_span = maxf(_hmax - _hmin, 0.0001)
|
||||||
|
_apply_contour_state(_last_span >= CONTOUR_MIN_SPAN)
|
||||||
|
for s in _stuecke:
|
||||||
|
var ganz := Rect2i(s["x0"], s["z0"], s["x1"] - s["x0"] + 1, s["z1"] - s["z0"] + 1)
|
||||||
|
var getroffen := alles
|
||||||
|
if alles:
|
||||||
|
_stueck_punkte(s, ganz, heights, res, size_x, size_z, mats, mres)
|
||||||
|
else:
|
||||||
|
for r in _dirty:
|
||||||
|
# Normalen hängen an den Nachbarn: ein Punkt Rand mitnehmen.
|
||||||
|
var teil := r.grow(1).intersection(ganz)
|
||||||
|
if teil.has_area():
|
||||||
|
_stueck_punkte(s, teil, heights, res, size_x, size_z, mats, mres)
|
||||||
|
getroffen = true
|
||||||
|
if getroffen:
|
||||||
|
var mesh: ArrayMesh = (s["mi"] as MeshInstance3D).mesh
|
||||||
|
mesh.clear_surfaces()
|
||||||
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, s["arrays"])
|
||||||
|
_dirty.clear()
|
||||||
|
|
||||||
|
|
||||||
|
func _stueck_anlegen(cx: int, cz: int, res: int) -> Dictionary:
|
||||||
|
var x0 := cx * STUECK_ZELLEN
|
||||||
|
var z0 := cz * STUECK_ZELLEN
|
||||||
|
var x1 := mini(x0 + STUECK_ZELLEN, res - 1)
|
||||||
|
var z1 := mini(z0 + STUECK_ZELLEN, res - 1)
|
||||||
|
var w := x1 - x0 + 1
|
||||||
|
var h := z1 - z0 + 1
|
||||||
|
var pos := PackedVector3Array()
|
||||||
|
var nor := PackedVector3Array()
|
||||||
|
var col := PackedColorArray()
|
||||||
|
var uv2 := PackedVector2Array()
|
||||||
|
var idx := PackedInt32Array()
|
||||||
|
pos.resize(w * h)
|
||||||
|
nor.resize(w * h)
|
||||||
|
col.resize(w * h)
|
||||||
|
uv2.resize(w * h)
|
||||||
|
idx.resize((w - 1) * (h - 1) * 6)
|
||||||
|
var n := 0
|
||||||
|
for z in h - 1:
|
||||||
|
for x in w - 1:
|
||||||
|
var i := z * w + x
|
||||||
|
# Wicklung wie im ganzen Mesh (update_heights).
|
||||||
|
idx[n] = i
|
||||||
|
idx[n + 1] = i + 1
|
||||||
|
idx[n + 2] = i + w
|
||||||
|
idx[n + 3] = i + 1
|
||||||
|
idx[n + 4] = i + w + 1
|
||||||
|
idx[n + 5] = i + w
|
||||||
|
n += 6
|
||||||
|
var arrays := []
|
||||||
|
arrays.resize(Mesh.ARRAY_MAX)
|
||||||
|
arrays[Mesh.ARRAY_VERTEX] = pos
|
||||||
|
arrays[Mesh.ARRAY_NORMAL] = nor
|
||||||
|
arrays[Mesh.ARRAY_COLOR] = col
|
||||||
|
arrays[Mesh.ARRAY_TEX_UV2] = uv2
|
||||||
|
arrays[Mesh.ARRAY_INDEX] = idx
|
||||||
|
var mi := MeshInstance3D.new()
|
||||||
|
mi.mesh = ArrayMesh.new()
|
||||||
|
mi.material_override = _mi.material_override
|
||||||
|
add_child(mi)
|
||||||
|
return {"x0": x0, "z0": z0, "x1": x1, "z1": z1, "w": w, "arrays": arrays, "mi": mi}
|
||||||
|
|
||||||
|
|
||||||
|
func _stueck_punkte(s: Dictionary, r: Rect2i, heights: PackedFloat32Array, res: int,
|
||||||
|
size_x: float, size_z: float, mats: PackedByteArray, mres: int) -> void:
|
||||||
|
var arrays: Array = s["arrays"]
|
||||||
|
var pos: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||||
|
var nor: PackedVector3Array = arrays[Mesh.ARRAY_NORMAL]
|
||||||
|
var col: PackedColorArray = arrays[Mesh.ARRAY_COLOR]
|
||||||
|
var uv2: PackedVector2Array = arrays[Mesh.ARRAY_TEX_UV2]
|
||||||
|
var x0: int = s["x0"]
|
||||||
|
var z0: int = s["z0"]
|
||||||
|
var w: int = s["w"]
|
||||||
|
var cell_x := size_x / float(res - 1)
|
||||||
|
var cell_z := size_z / float(res - 1)
|
||||||
|
for z in range(r.position.y, r.end.y):
|
||||||
|
for x in range(r.position.x, r.end.x):
|
||||||
|
var i := z * res + x
|
||||||
|
var v := (z - z0) * w + (x - x0)
|
||||||
|
var hv := heights[i]
|
||||||
|
pos[v] = Vector3(-size_x * 0.5 + float(x) * cell_x, hv, -size_z * 0.5 + float(z) * cell_z)
|
||||||
|
var n := Vector3(
|
||||||
|
(heights[z * res + maxi(x - 1, 0)] - heights[z * res + mini(x + 1, res - 1)]) * cell_z,
|
||||||
|
2.0 * cell_x * cell_z,
|
||||||
|
(heights[maxi(z - 1, 0) * res + x] - heights[mini(z + 1, res - 1) * res + x]) * cell_x).normalized()
|
||||||
|
nor[v] = n
|
||||||
|
var mat_id := clampi(SvmapIO.material_at_height_point(mats, mres, res, x, z),
|
||||||
|
0, MATERIAL_COLORS.size() - 1)
|
||||||
|
var farbe := relief_color(mat_id, hv, _hmin, _hmax, n)
|
||||||
|
if _wasser_level < INF:
|
||||||
|
farbe = SvmapWasser.matsch_anwenden(farbe, SvmapWasser.ufer_faktor(hv, _wasser_level))
|
||||||
|
col[v] = farbe
|
||||||
|
uv2[v] = Vector2(hv, 0.0)
|
||||||
|
arrays[Mesh.ARRAY_VERTEX] = pos
|
||||||
|
arrays[Mesh.ARRAY_NORMAL] = nor
|
||||||
|
arrays[Mesh.ARRAY_COLOR] = col
|
||||||
|
arrays[Mesh.ARRAY_TEX_UV2] = uv2
|
||||||
|
|
||||||
|
|
||||||
## Misch-Schicht fürs nächste update_heights (Live-Malen, Karte 25.10.).
|
## Misch-Schicht fürs nächste update_heights (Live-Malen, Karte 25.10.).
|
||||||
func set_mix(mix_b: PackedByteArray, mix_w: PackedByteArray) -> void:
|
func set_mix(mix_b: PackedByteArray, mix_w: PackedByteArray) -> void:
|
||||||
_mix_b_cache = mix_b
|
_mix_b_cache = mix_b
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue