U6 t_639135dd: dezente Auswahl — Kontur statt Vollorange (Textur bleibt sichtbar).
Neu shaders/selection_outline.gdshader: Inverted-Hull-Außenhülle (unshaded, cull_front, Grow entlang der Normalen; Streifen-Uniform für den Zwilling). Markierung jetzt über Outline-Kind am Mesh (Auswahl/Hover/Partner); Farben + Breiten aus theme/editor_theme.tres (selection_outline*, *_width_mm) — eine Quelle. material_override wird von der Markierung nicht mehr angefasst: GLB-Texturen bleiben im Auswahl-Zustand voll sichtbar, der Tint bleibt beim Markieren sichtbar (kein "Orange gewinnt" mehr). Spawn-Zonen: konstante Disc-Deckkraft + dezenter Kontur-Ring (Torus) statt Aufhellung/Emission; Label-Farbe aus dem Theme statt hartkodiertem Gelb. Bau-Teile: Kontur um das Bauteil-Mesh statt halbtransparenter Vollbox. Neu: set_hover()-API (schwächste Stufe) — Anschluss an die Mausbewegung liegt in interaction.gd (Grenze dieser Karte) und folgt als eigene Karte. Suite tests/test_selection_look.gd (41 checks: Theme-Quelle, Grundmaterial unangetastet, Outline nur bei Markierung, restlos weg bei Abwahl, Zwilling dünner+gestrichelt, Spawn-Ring, Bau-Teil-Kontur). Altsuiten an die neue Semantik angepasst (test_glb_view: GLB-Auswahl ohne Override; test_map_tint T3: Kontur statt Orange-gewinnt; test_object_mirror: Partner per Kontur). Beweise: 4 Renderproben (fass, echtes GLB — vorher Vollorange / nachher Kontur, je auf Sand und Fels) in /opt/data/scopedvr/work/u6_auswahl_beweise/. Voll-Batterie 32/32 grün (EXIT 0) inkl. der drei angepassten Suiten.
This commit is contained in:
parent
f16b9c8fb6
commit
5dca5513da
11 changed files with 741 additions and 91 deletions
40
shaders/selection_outline.gdshader
Normal file
40
shaders/selection_outline.gdshader
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
shader_type spatial;
|
||||
render_mode unshaded, cull_front, shadows_disabled, blend_mix;
|
||||
|
||||
// Außenhüllen-Kontur (U6, Karte t_639135dd): dezente Auswahl-Markierung
|
||||
// statt Vollflächen-Orange. Das Grundmaterial (inkl. Textur) bleibt komplett
|
||||
// sichtbar — die Kontur lebt als eigene Hülle um das Modell:
|
||||
//
|
||||
// VERTEX += NORMAL * breite_m bläht das Volumen entlang der Normalen auf,
|
||||
// cull_front rendert nur die RÜCKseiten der Hülle. Sichtbar bleibt darum
|
||||
// genau der schmale Saum, der außerhalb der Silhouette liegt (der Rest
|
||||
// wird vom Objekt selbst per Tiefentest verdeckt) — die klassische
|
||||
// Inverted-Hull-Outline-Technik.
|
||||
//
|
||||
// Farbton, Deckkraft und Breite kommen aus dem Theme (theme/editor_theme.tres,
|
||||
// Abschnitt "Editor") — der Shader selbst kennt keine festen Farben.
|
||||
//
|
||||
// Gestrichelte Variante (Spiegel-Zwilling): streifen_hz > 0 unterteilt die
|
||||
// Kontur in Bänder entlang der Welthöhe — dünn + gestrichelt statt voll.
|
||||
|
||||
uniform vec4 linien_farbe : source_color = vec4(1.0, 0.6, 0.15, 0.95);
|
||||
uniform float breite_m : hint_range(0.0, 1.0) = 0.08;
|
||||
uniform float streifen_hz : hint_range(0.0, 40.0) = 0.0;
|
||||
|
||||
varying vec3 welt_pos;
|
||||
|
||||
void vertex() {
|
||||
VERTEX += NORMAL * breite_m;
|
||||
welt_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
if (streifen_hz > 0.5) {
|
||||
// Gestrichelt (Zwilling): Bänder entlang der Welthöhe, ~55 % Strich.
|
||||
if (fract(welt_pos.y * streifen_hz) > 0.55) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
ALBEDO = linien_farbe.rgb;
|
||||
ALPHA = linien_farbe.a;
|
||||
}
|
||||
1
shaders/selection_outline.gdshader.uid
Normal file
1
shaders/selection_outline.gdshader.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b1wjgw8l4w77m
|
||||
|
|
@ -11,8 +11,15 @@ extends Node3D
|
|||
const GRID_COLOR := Color(0.35, 0.45, 0.55, 0.6)
|
||||
const AXIS_COLOR := Color(0.9, 0.7, 0.2, 0.9)
|
||||
const OBJECT_COLOR := Color(0.55, 0.75, 0.95)
|
||||
const SELECT_COLOR := Color(1.0, 0.6, 0.15) # Auswahl klar orange
|
||||
const TEAM_COLORS := [Color(0.3, 0.6, 1.0), Color(1.0, 0.35, 0.3)] # Blau / Rot
|
||||
# Auswahl-Markierung (U6, t_639135dd): Farbe + Breite kommen aus dem Theme
|
||||
# (eine Quelle) — Auswahl-Kontur, schwächere Hover-Kontur, dünn-gestrichelte
|
||||
# Partner-Kontur. Kein Vollflächen-Orange mehr: das Grundmaterial (mit Textur)
|
||||
# bleibt sichtbar, die Markierung lebt als Außenhüllen-Kontur darum herum.
|
||||
const THEME_PATH := "res://theme/editor_theme.tres"
|
||||
const OUTLINE_SHADER := "res://shaders/selection_outline.gdshader"
|
||||
# Fallback, falls das Theme (noch) nicht geladen werden kann.
|
||||
const OUTLINE_FALLBACK := Color(1.0, 0.6, 0.15, 0.95)
|
||||
|
||||
# Wetter → Licht (grobe Stimmung; Feintuning kommt mit dem Wetter-Konsum)
|
||||
const WEATHER_LIGHT := {
|
||||
|
|
@ -22,8 +29,9 @@ const WEATHER_LIGHT := {
|
|||
"nacht": {"energy": 0.25, "color": Color(0.6, 0.7, 1.0), "ambient": 0.12, "sky_top": Color(0.03, 0.05, 0.12)},
|
||||
}
|
||||
|
||||
var selected_index := -1 # aktuelles Auswahl-Objekt (orange gefärbt)
|
||||
var selected_index := -1 # aktuelles Auswahl-Objekt (Kontur-Markierung)
|
||||
var selected_indices: Array = [] # komplette Auswahl (Karte t_f6f29d86 — Mehrfachauswahl)
|
||||
var hover_index := -1 # Hover-Objekt (schwächste Kontur; U6-Abstufung)
|
||||
var object_markers: Array = [] # Node3D je Objekt (gleiche Reihenfolge wie doc.objects)
|
||||
var fehlende_glbs: Array = [] # OIDs ohne echte GLB (Platzhalter-Fallback — Statuszeilen-Hinweis)
|
||||
var spawn_markers: Array = [] # Node3D je Spawn-Point (Meta: spawn_team/spawn_id/spawn_radius)
|
||||
|
|
@ -32,13 +40,16 @@ var gebaeude_markers: Array = [] # MeshInstance3D je Bauteil (Karte t_97ea59c5)
|
|||
var grid_node: MeshInstance3D # Maßstabsgitter (Toggle „⊞ Gitter“, Karte t_7ac70014)
|
||||
|
||||
var _gebaeude_baked: Dictionary = {} # faces/offsets für Ray-Pick (Bauen-Werkzeug)
|
||||
var _gebaeude_sel: MeshInstance3D # orangener AABB-Rahmen um das gewählte Teil
|
||||
var _gebaeude_sel: MeshInstance3D # Kontur-Mesh um das gewählte Bauteil
|
||||
var _doc_ref: Dictionary = {} # letztes rebuild-Doc (Auswahl-Markierung)
|
||||
|
||||
var _base_mat: StandardMaterial3D
|
||||
var _select_mat: StandardMaterial3D
|
||||
var _partner_mat: StandardMaterial3D # schwächere Auswahl-Farbe des sym-Zwillings (M3)
|
||||
var _sym_partners: Array = [] # Indizes der Zwillinge der aktuellen Auswahl
|
||||
# Kontur-Materialien (U6): Auswahl / Hover / Spiegel-Zwilling — Farben und
|
||||
# Breiten kommen aus dem Theme, der Shader ist die Außenhüllen-Technik.
|
||||
var _outline_mat_sel: ShaderMaterial
|
||||
var _outline_mat_hover: ShaderMaterial
|
||||
var _outline_mat_partner: ShaderMaterial
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -59,20 +70,10 @@ func rebuild(data: Dictionary, manifest: Dictionary) -> void:
|
|||
_base_mat = StandardMaterial3D.new()
|
||||
_base_mat.albedo_color = OBJECT_COLOR
|
||||
_base_mat.roughness = 0.8
|
||||
_select_mat = StandardMaterial3D.new()
|
||||
_select_mat.albedo_color = SELECT_COLOR
|
||||
_select_mat.emission_enabled = true
|
||||
_select_mat.emission = SELECT_COLOR * 0.35
|
||||
_select_mat.roughness = 0.7
|
||||
# sym-Zwilling (M3, t_34908425): GLEICHE Auswahl-Material-Logik, nur eine
|
||||
# schwächere Farbe (abgedunkeltes Orange, sehr leises Glühen) — keine zweite
|
||||
# Markierungs-Technik, damit die spätere dezente Auswahl (U6) automatisch
|
||||
# mitprofitiert.
|
||||
_partner_mat = StandardMaterial3D.new()
|
||||
_partner_mat.albedo_color = SELECT_COLOR.darkened(0.45)
|
||||
_partner_mat.emission_enabled = true
|
||||
_partner_mat.emission = SELECT_COLOR * 0.12
|
||||
_partner_mat.roughness = 0.7
|
||||
# U6 (t_639135dd): keine Vollfarben-Materialien mehr — die Markierung
|
||||
# (Auswahl/Hover/Partner) lebt als Außenhüllen-Kontur um das Modell,
|
||||
# Farben + Breiten aus dem Theme.
|
||||
_markierung_theme_laden()
|
||||
_sym_partners = []
|
||||
|
||||
if data.is_empty():
|
||||
|
|
@ -252,7 +253,10 @@ func _build_objects(objects: Array, manifest: Dictionary) -> void:
|
|||
mi.scale = Vector3.ONE * scale
|
||||
else:
|
||||
mi.position.y = MeshShapes.half_height(mesh)
|
||||
mi.material_override = _select_mat if _is_selected(i) else (null if echt else _base_mat)
|
||||
# U6 (t_639135dd): das Grundmaterial wird NIE MEHR durch die Auswahl
|
||||
# ersetzt — echte GLB-Objekte tragen ihre Texturen weiter (override null),
|
||||
# Platzhalter die Editor-Grundfarbe. Markiert wird per Kontur (unten).
|
||||
mi.material_override = null if echt else _base_mat
|
||||
marker.add_child(mi)
|
||||
var half: Vector3 = (mesh.get_aabb().size * (scale if echt else 1.0) * 0.5) as Vector3
|
||||
marker.set_meta("pick_radius", half.length() + maxf(0.6, half.y * 0.25))
|
||||
|
|
@ -263,10 +267,12 @@ func _build_objects(objects: Array, manifest: Dictionary) -> void:
|
|||
add_child(marker)
|
||||
# Farbe je Objekt (Karte t_b0edb807): optionales Feld "tint" — nur
|
||||
# Objekte MIT Feld bekommen die Überschreibung (Rückwärtskompatibel).
|
||||
# Ausgewählte Objekte bleiben orange (Auswahl gewinnt weiter, Tint
|
||||
# kommt beim Abwählen über set_selected_multi).
|
||||
if SvmapTint.hat(obj) and not _is_selected(i):
|
||||
# Die Farbe bleibt jetzt auch bei Markierung sichtbar (U6: Auswahl = Kontur,
|
||||
# Grundmaterial bleibt) — kein „Orange gewinnt“ mehr.
|
||||
if SvmapTint.hat(obj):
|
||||
SvmapTint.anwenden(marker, SvmapTint.von(obj))
|
||||
# Markierung (Auswahl/Hover/Partner) als Kontur-Hülle anlegen.
|
||||
_outline_anwenden(marker, i)
|
||||
|
||||
|
||||
## Auswahl nachträglich umfärben (ohne ganzen Rebuild) — z. B. nach Drag.
|
||||
|
|
@ -276,34 +282,29 @@ func set_selected(idx: int) -> void:
|
|||
set_selected_multi([idx] if idx >= 0 else [])
|
||||
|
||||
|
||||
## Mehrfachauswahl (Karte t_f6f29d86): ALLE markierten Objekte färben sich
|
||||
## orange — Gruppen/Mehrfachauswahl sind auf einen Blick erkennbar.
|
||||
## Echte GLB-Objekte (Meta „glb") bekommen im ungewählten Zustand KEINE
|
||||
## Grundfarbe drübergelegt — material_override würde die Texturen verstecken
|
||||
## (Karte t_9b50b231); orange bleibt auch dort das Auswahl-Signal.
|
||||
## Mehrfachauswahl (Karte t_f6f29d86): ALLE markierten Objekte bekommen die
|
||||
## Auswahl-Kontur (U6, t_639135dd) — Gruppen/Mehrfachauswahl sind auf einen
|
||||
## Blick erkennbar, OHNE die Textur zu übermalen. Das Grundmaterial bleibt
|
||||
## überall sichtbar: echte GLB-Objekte tragen ihre Texturen weiter (Karte
|
||||
## t_9b50b231), Platzhalter die Editor-Grundfarbe — material_override wird
|
||||
## von der Markierung nicht mehr angefasst.
|
||||
func set_selected_multi(indices: Array) -> void:
|
||||
selected_indices = indices.duplicate()
|
||||
selected_index = indices.back() if not indices.is_empty() else -1
|
||||
# sym-Zwillinge (M3, t_34908425): aus dem doc ableiten — Partner der
|
||||
# Auswahl, die nicht selbst ausgewählt sind, werden schwächer markiert.
|
||||
# Auswahl, die nicht selbst ausgewählt sind, bekommen die dünnere,
|
||||
# gestrichelte Partner-Kontur (U6-Abstufung statt dunkler Vollfarbe).
|
||||
_sym_partners = _sym_partners_of(indices)
|
||||
var objects: Array = _doc_ref.get("objects", [])
|
||||
for marker in object_markers:
|
||||
if marker == null or not is_instance_valid(marker):
|
||||
continue
|
||||
var idx := int(marker.get_meta("obj_index", -1))
|
||||
var mi: MeshInstance3D = marker.get_child(0)
|
||||
var echt := bool(marker.get_meta("glb", false))
|
||||
var ist_sel := _is_selected(idx)
|
||||
var ist_partner := not ist_sel and _is_partner(idx)
|
||||
mi.material_override = _select_mat if ist_sel \
|
||||
else (_partner_mat if ist_partner else (null if echt else _base_mat))
|
||||
# Tint-Abgleich (Karte t_b0edb807): unmarkierte Objekte zeigen ihre
|
||||
# doc-Farbe (oder — nach „Zurücksetzen“ — wieder die Pack-Farbe).
|
||||
# Markierte (Auswahl ODER Zwilling) bleiben orange; ihr Tint liegt als
|
||||
# doc-Feld bereit und erscheint beim Abwählen.
|
||||
if not ist_sel and not ist_partner and idx >= 0 and idx < objects.size() \
|
||||
and objects[idx] is Dictionary:
|
||||
_outline_anwenden(marker, idx)
|
||||
# Tint-Abgleich (Karte t_b0edb807): ALLE Objekte zeigen ihre doc-Farbe
|
||||
# (oder — nach „Zurücksetzen“ — wieder die Pack-Farbe). Der Tint bleibt
|
||||
# jetzt auch bei Markierung sichtbar (Auswahl = Kontur um das Modell).
|
||||
if idx >= 0 and idx < objects.size() and objects[idx] is Dictionary:
|
||||
var obj: Dictionary = objects[idx]
|
||||
if SvmapTint.hat(obj):
|
||||
SvmapTint.anwenden(marker, SvmapTint.von(obj))
|
||||
|
|
@ -334,8 +335,8 @@ func _sym_partners_of(indices: Array) -> Array:
|
|||
|
||||
|
||||
## Tint frisch aus dem doc auf die MARKIERTEN Objekte anwenden (Inspector-
|
||||
## Änderung ohne Rebuild). Markierte Objekte zeigen weiter Auswahl-Orange —
|
||||
## die Farbe liegt im doc und erscheint beim Abwählen (set_selected_multi).
|
||||
## Änderung ohne Rebuild). U6: die Farbe bleibt auch bei Markierung sichtbar
|
||||
## (Auswahl = Kontur um das Modell) — kein „Orange gewinnt“-Skip mehr.
|
||||
func tint_refresh(indices: Array) -> void:
|
||||
var objects: Array = _doc_ref.get("objects", [])
|
||||
for i in indices:
|
||||
|
|
@ -344,8 +345,6 @@ func tint_refresh(indices: Array) -> void:
|
|||
var marker = object_markers[i]
|
||||
if marker == null or not is_instance_valid(marker):
|
||||
continue
|
||||
if _is_selected(i):
|
||||
continue
|
||||
if i >= objects.size() or not (objects[i] is Dictionary):
|
||||
continue
|
||||
var obj: Dictionary = objects[i]
|
||||
|
|
@ -414,8 +413,10 @@ func _spawn_marker(pos: Vector3, team: int, pid: String, radius: float) -> Node3
|
|||
|
||||
|
||||
## Kreisfläche EINES Markers (neu) bauen — Werkzeuge rufen nach Radius-Änderung
|
||||
## style_spawn_marker auf.
|
||||
func _spawn_disc(radius: float, team: int, selected := false) -> MeshInstance3D:
|
||||
## style_spawn_marker auf. U6 (t_639135dd): die Fläche bleibt IMMER gleich
|
||||
## dezent (Teamfarbe, halbtransparent) — die Auswahl zeigt sich als schmaler
|
||||
## Kontur-Ring am Zonenrand (Theme-Farbe), nicht als aufgehellte Vollfläche.
|
||||
func _spawn_disc(radius: float, team: int, _selected := false) -> MeshInstance3D:
|
||||
var mi := MeshInstance3D.new()
|
||||
var cyl := CylinderMesh.new()
|
||||
cyl.top_radius = radius
|
||||
|
|
@ -425,12 +426,28 @@ func _spawn_disc(radius: float, team: int, selected := false) -> MeshInstance3D:
|
|||
mi.position.y = 0.12
|
||||
var col: Color = TEAM_COLORS[team] if team >= 0 and team < TEAM_COLORS.size() else Color.WHITE
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(col.r, col.g, col.b, 0.55 if not selected else 0.8)
|
||||
mat.albedo_color = Color(col.r, col.g, col.b, 0.55)
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mi.material_override = mat
|
||||
return mi
|
||||
|
||||
|
||||
## Dezenter Auswahl-Ring EINES Spawn-Markers (U6): dünner Torus am Zonenrand,
|
||||
## Farbe aus dem Theme. Muss NUR bei Auswahl existieren (restlos weg bei
|
||||
## Abwahl) — style_spawn_marker legt ihn an/entfernt ihn.
|
||||
func _spawn_ring(radius: float) -> MeshInstance3D:
|
||||
var mi := MeshInstance3D.new()
|
||||
var torus := TorusMesh.new()
|
||||
var t := maxf(0.15, radius * 0.06)
|
||||
torus.inner_radius = maxf(0.01, radius - t)
|
||||
torus.outer_radius = radius + t
|
||||
mi.mesh = torus
|
||||
mi.position.y = 0.26
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = _markierung_farbe("selection_outline")
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
if selected:
|
||||
mat.emission_enabled = true
|
||||
mat.emission = col * 0.6
|
||||
mi.material_override = mat
|
||||
return mi
|
||||
|
||||
|
|
@ -450,6 +467,17 @@ func style_spawn_marker(marker: Node3D, radius: float, selected: bool) -> void:
|
|||
var disc := _spawn_disc(radius, team, selected)
|
||||
marker.add_child(disc)
|
||||
marker.move_child(disc, 0)
|
||||
# Auswahl-Kontur: alter Ring weg, bei Auswahl neuer (restloser Wechsel).
|
||||
var alter_ring: Node = null
|
||||
if marker.has_meta("spawn_ring"):
|
||||
alter_ring = marker.get_meta("spawn_ring")
|
||||
if alter_ring != null and is_instance_valid(alter_ring):
|
||||
alter_ring.queue_free()
|
||||
marker.remove_meta("spawn_ring")
|
||||
if selected:
|
||||
var ring := _spawn_ring(radius)
|
||||
marker.add_child(ring)
|
||||
marker.set_meta("spawn_ring", ring)
|
||||
|
||||
|
||||
## Auswahl-Highlight über ALLE Spawn-Marker (Werkzeug ruft bei Änderung).
|
||||
|
|
@ -462,11 +490,12 @@ func set_spawn_selected(team: int, pid: String) -> void:
|
|||
if bool(marker.get_meta("spawn_selected", false)) == is_sel:
|
||||
continue
|
||||
style_spawn_marker(marker, float(marker.get_meta("spawn_radius", 6.0)), is_sel)
|
||||
# Label sofort umfärben (auch ohne Disc-Neubau sichtbar).
|
||||
# Label sofort umfärben (auch ohne Disc-Neubau sichtbar) — Farbe aus
|
||||
# dem Theme statt hartkodiertem Gelb (U6).
|
||||
var col: Color = TEAM_COLORS[clampi(team, 0, 1)] if team >= 0 else Color.WHITE
|
||||
if marker.get_child_count() > 1 and marker.get_child(1) is Label3D:
|
||||
var label := marker.get_child(1) as Label3D
|
||||
label.modulate = Color(1.0, 0.85, 0.2) if is_sel else col
|
||||
label.modulate = _markierung_farbe("selection_outline") if is_sel else col
|
||||
|
||||
|
||||
# ------------------------------------------------------------ Bauteile ------
|
||||
|
|
@ -503,7 +532,10 @@ func gebaeude_baked() -> Dictionary:
|
|||
return _gebaeude_baked
|
||||
|
||||
|
||||
## Auswahl-Markierung: orangener, halbtransparenter Rahmen um das Bauteil.
|
||||
## Auswahl-Markierung (U6, t_639135dd): dezente Außenhüllen-Kontur um das
|
||||
## Bauteil-Mesh — gleiche Technik wie bei den Objekten (selection_outline-
|
||||
## Shader, Theme-Farbe). Keine halbtransparente Vollbox mehr, die das Teil
|
||||
## verdeckt.
|
||||
func set_gebaeude_selected(idx: int) -> void:
|
||||
if _gebaeude_sel != null and is_instance_valid(_gebaeude_sel):
|
||||
_gebaeude_sel.queue_free()
|
||||
|
|
@ -514,17 +546,130 @@ func set_gebaeude_selected(idx: int) -> void:
|
|||
var aabb := BuildOps.piece_aabb(list[idx])
|
||||
if aabb.size.length() < 0.001:
|
||||
return
|
||||
var box := MeshInstance3D.new()
|
||||
var bm := BoxMesh.new()
|
||||
bm.size = aabb.size + Vector3(0.06, 0.06, 0.06)
|
||||
box.mesh = bm
|
||||
box.position = aabb.get_center()
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(SELECT_COLOR.r, SELECT_COLOR.g, SELECT_COLOR.b, 0.35)
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.emission_enabled = true
|
||||
mat.emission = SELECT_COLOR * 0.3
|
||||
box.material_override = mat
|
||||
add_child(box)
|
||||
_gebaeude_sel = box
|
||||
var mesh := BuildOps.piece_mesh(list[idx])
|
||||
if mesh == null:
|
||||
return
|
||||
# Kontur in GLEICHER Transform wie das echte Bauteil-Mesh (aabb ist
|
||||
# welt-transformiert — nur als Größen-Guard; das Mesh selbst sitzt am
|
||||
# Teil-Anker mit rot).
|
||||
var kontur := MeshInstance3D.new()
|
||||
kontur.mesh = mesh
|
||||
var pos: Array = list[idx].get("pos", [0.0, 0.0, 0.0])
|
||||
kontur.position = Vector3(float(pos[0]), float(pos[1]), float(pos[2]))
|
||||
kontur.rotation.y = deg_to_rad(float(list[idx].get("rot", 0.0)))
|
||||
kontur.material_override = _outline_mat_sel
|
||||
add_child(kontur)
|
||||
_gebaeude_sel = kontur
|
||||
|
||||
|
||||
# ------------------------------------------------------ U6-Markierung -------
|
||||
# (Karte t_639135dd — dezente Auswahl: Kontur statt Vollorange. Eine Quelle
|
||||
# für Farben/Breiten ist das Theme; der Shader ist die Außenhüllen-Technik.)
|
||||
|
||||
## Theme-Farben + -Breiten in die drei Kontur-Materialien überführen
|
||||
## (Auswahl / Hover / Partner). Fehlende Einträge → Fallback-Farbe.
|
||||
func _markierung_theme_laden() -> void:
|
||||
_outline_mat_sel = _outline_material(
|
||||
_markierung_farbe("selection_outline"),
|
||||
_markierung_breite("selection_outline_width_mm"), 0.0)
|
||||
_outline_mat_hover = _outline_material(
|
||||
_markierung_farbe("selection_outline_hover"),
|
||||
_markierung_breite("selection_outline_width_hover_mm"), 0.0)
|
||||
_outline_mat_partner = _outline_material(
|
||||
_markierung_farbe("selection_outline_partner"),
|
||||
_markierung_breite("selection_outline_width_partner_mm"), 3.5)
|
||||
|
||||
|
||||
## Eine Markierungs-Farbe aus dem Theme lesen (fehlt sie → Fallback).
|
||||
func _markierung_farbe(name: String) -> Color:
|
||||
var theme_res: Variant = load(THEME_PATH)
|
||||
if theme_res is Theme:
|
||||
var th := theme_res as Theme
|
||||
if th.has_color(name, "Editor"):
|
||||
return th.get_color(name, "Editor")
|
||||
return OUTLINE_FALLBACK
|
||||
|
||||
|
||||
## Eine Markierungs-Breite aus dem Theme lesen (Theme speichert Millimeter
|
||||
## als Ganzzahl — Theme kennt keine Gleitkommakonstanten).
|
||||
func _markierung_breite(name: String) -> float:
|
||||
var theme_res: Variant = load(THEME_PATH)
|
||||
if theme_res is Theme:
|
||||
var th := theme_res as Theme
|
||||
if th.has_constant(name, "Editor"):
|
||||
return float(th.get_constant(name, "Editor")) / 1000.0
|
||||
return 0.06
|
||||
|
||||
|
||||
## Kontur-Material für einen Zustand bauen (streifen > 0 = gestrichelt).
|
||||
func _outline_material(farbe: Color, breite: float, streifen: float) -> ShaderMaterial:
|
||||
var mat := ShaderMaterial.new()
|
||||
var shader: Variant = load(OUTLINE_SHADER)
|
||||
if shader is Shader:
|
||||
mat.shader = shader
|
||||
mat.set_shader_parameter("linien_farbe", farbe)
|
||||
mat.set_shader_parameter("breite_m", breite)
|
||||
mat.set_shader_parameter("streifen_hz", streifen)
|
||||
return mat
|
||||
|
||||
|
||||
## Zustand eines Objekts (U6-Abstufung): Auswahl > Partner > Hover > nichts.
|
||||
func _markierung_zustand(idx: int) -> String:
|
||||
if _is_selected(idx):
|
||||
return "sel"
|
||||
if _is_partner(idx):
|
||||
return "partner"
|
||||
if idx >= 0 and idx == hover_index:
|
||||
return "hover"
|
||||
return ""
|
||||
|
||||
|
||||
## Kontur-Hülle eines Objekt-Markers anlegen/umschalten/restlos entfernen.
|
||||
## Die Hülle hängt als Kind UNTER dem Mesh (erbt Position + Scale — GLB-
|
||||
## Skalierung bleibt korrekt) und trägt das Mesh ein zweites Mal mit dem
|
||||
## Außenhüllen-Material (visuell = zweite Render-Passage um das Modell,
|
||||
## das Grundmaterial mit seiner Textur bleibt unangetastet).
|
||||
func _outline_anwenden(marker: Node3D, idx: int) -> void:
|
||||
if marker == null or not is_instance_valid(marker):
|
||||
return
|
||||
var zustand := _markierung_zustand(idx)
|
||||
var alt: Node = null
|
||||
if marker.has_meta("outline"):
|
||||
alt = marker.get_meta("outline")
|
||||
if zustand.is_empty():
|
||||
if alt != null and is_instance_valid(alt):
|
||||
alt.queue_free()
|
||||
if marker.has_meta("outline"):
|
||||
marker.remove_meta("outline")
|
||||
return
|
||||
var mat: ShaderMaterial = _outline_mat_sel
|
||||
if zustand == "hover":
|
||||
mat = _outline_mat_hover
|
||||
elif zustand == "partner":
|
||||
mat = _outline_mat_partner
|
||||
if alt != null and is_instance_valid(alt):
|
||||
(alt as MeshInstance3D).material_override = mat
|
||||
return
|
||||
var mi := marker.get_child(0) as MeshInstance3D
|
||||
if mi == null or mi.mesh == null:
|
||||
return
|
||||
var huelle := MeshInstance3D.new()
|
||||
huelle.name = "SelectionOutline"
|
||||
huelle.mesh = mi.mesh
|
||||
huelle.material_override = mat
|
||||
mi.add_child(huelle)
|
||||
marker.set_meta("outline", huelle)
|
||||
|
||||
|
||||
## Hover-Markierung (U6-Abstufung, schwächste Kontur). idx = obj_index oder
|
||||
## -1 für „kein Hover“. Der Aufrufer ist das Objekt-Werkzeug — der Anschluss
|
||||
## an die Mausbewegung liegt in interaction.gd (Grenze dieser Karte) und
|
||||
## kommt als Folge-Karte; die Abstufung selbst ist hier voll wirksam.
|
||||
func set_hover(idx: int) -> void:
|
||||
if hover_index == idx:
|
||||
return
|
||||
hover_index = idx
|
||||
for marker in object_markers:
|
||||
if marker == null or not is_instance_valid(marker):
|
||||
continue
|
||||
_outline_anwenden(marker, int(marker.get_meta("obj_index", -1)))
|
||||
147
tests/probe_u6_render.gd
Normal file
147
tests/probe_u6_render.gd
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
extends SceneTree
|
||||
## Beweis-Probe (Karte t_639135dd, WEGWERF — nicht Teil der Suite):
|
||||
## U6 dezente Auswahl — dasselbe Objekt (fass, echtes GLB mit Textur) auf
|
||||
## Sand und auf Fels, Auswahl-Markierung im Bild. Zwei Modi über Env:
|
||||
## U6_MODUS=vorher → alter Baumzustand (Vollorange) — Beweis „vorher“
|
||||
## U6_MODUS=nachher → neuer Baumzustand (Kontur, Textur sichtbar)
|
||||
## Das Terrain ist eine echte Material-Schicht: linke Hälfte Sand (1),
|
||||
## rechte Hälfte Fels (2) — das Objekt wird je Bild auf die eine oder
|
||||
## andere Hälfte gesetzt.
|
||||
## Aufruf: xvfb-run -a godot4 --path . --rendering-driver opengl3 \
|
||||
## -s res://tests/probe_u6_render.gd (mit ../scopedvr-client)
|
||||
|
||||
const SAND := 1
|
||||
const FELS := 2
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _mats_halbe(res: int) -> PackedByteArray:
|
||||
# Linke Kartenhälfte (x < Mitte) Sand, rechte Fels — Zeile für Zeile.
|
||||
var mats := PackedByteArray()
|
||||
mats.resize(res * res)
|
||||
var i := 0
|
||||
for z in res:
|
||||
for x in res:
|
||||
mats[i] = SAND if x < res / 2 else FELS
|
||||
i += 1
|
||||
return mats
|
||||
|
||||
|
||||
func _x_von_material(material: int, size: float) -> float:
|
||||
# Objekt-Anker je Hälfte: Sand links (−35 % Größe), Fels rechts (+35 %).
|
||||
return -0.35 * size if material == SAND else 0.35 * size
|
||||
|
||||
|
||||
func _shot(pfad: String) -> void:
|
||||
await process_frame
|
||||
await RenderingServer.frame_post_draw
|
||||
var img := root.get_viewport().get_texture().get_image()
|
||||
img.save_png(pfad)
|
||||
print("Bild: %s (%dx%d)" % [pfad, img.get_width(), img.get_height()])
|
||||
|
||||
|
||||
## „Modelle fehlen“-AcceptDialog schließen (kommt erst nach 2 Frames von
|
||||
## utils/modelle_status.gd — mehrfach probieren, mit Wartezeit dazwischen).
|
||||
func _dismiss_dialogs() -> void:
|
||||
for i in 6:
|
||||
await process_frame
|
||||
var stack: Array = [root]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
if n is AcceptDialog:
|
||||
(n as AcceptDialog).hide()
|
||||
n.queue_free()
|
||||
stack.append_array(n.get_children())
|
||||
|
||||
|
||||
## UI-Panels ausblenden: die 3D-Ansicht bekommt fast das ganze Fenster —
|
||||
## das Beweisbild zeigt die Kontur groß statt im schmalen Streifen. Vom
|
||||
## SubViewport-Container aufwärts werden alle GESCHWISTER versteckt.
|
||||
func _maximiere_3d(main: Node) -> void:
|
||||
var vp_container: Node = null
|
||||
var stack: Array = [main]
|
||||
while not stack.is_empty():
|
||||
var n: Node = stack.pop_back()
|
||||
if n is SubViewportContainer:
|
||||
vp_container = n
|
||||
break
|
||||
stack.append_array(n.get_children())
|
||||
if vp_container == null:
|
||||
return
|
||||
var laeufer: Node = vp_container
|
||||
while laeufer != null and laeufer != main:
|
||||
var elter: Node = laeufer.get_parent()
|
||||
if elter == null:
|
||||
break
|
||||
for kind in elter.get_children():
|
||||
if kind != laeufer and kind is Control:
|
||||
(kind as Control).visible = false
|
||||
laeufer = elter
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var modus := OS.get_environment("U6_MODUS")
|
||||
if modus.is_empty():
|
||||
modus = "nachher"
|
||||
var material := SAND if OS.get_environment("U6_BODEN") != "fels" else FELS
|
||||
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||
var main = scene.instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
await _dismiss_dialogs()
|
||||
_maximiere_3d(main)
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
DisplayServer.window_set_size(Vector2i(2328, 1310))
|
||||
# Echte Karte: flaches Terrain, Material-Schicht Sand/Fels.
|
||||
var size := 1024.0
|
||||
var res := 129
|
||||
var doc: Dictionary = SvmapDocument.create_new("U6Probe", size, "mittag")
|
||||
var terrain: Dictionary = doc.get("terrain", {})
|
||||
terrain["materials_r8_b64"] = SvmapIO.encode_materials(_mats_halbe(res))
|
||||
var x := _x_von_material(material, size)
|
||||
(doc["objects"] as Array).append({
|
||||
"id": "fass", "pos": [x, 0.0, 0.0], "scale": 1.0, "rot_y_deg": 20.0,
|
||||
})
|
||||
main.doc = doc
|
||||
main.call("_after_structure_change")
|
||||
await process_frame
|
||||
await process_frame
|
||||
var cam = main.get("camera")
|
||||
if cam != null:
|
||||
# Nah dran: Distanz 5 m, Pivot 1,2 m westlich vom Fass — das Fass
|
||||
# füllt sichtbar Fläche und sitzt nicht hinter dem Pivot-Gizmo.
|
||||
cam.set("distance", 5.0)
|
||||
cam.position = Vector3(x + 2.5, 1.8, 4.0)
|
||||
cam.call("focus_on", Vector3(x - 1.2, 0.6, 0.0))
|
||||
await process_frame
|
||||
await process_frame
|
||||
# Objekt über die ECHTE Auswahl anwählen (wie ein Klick ins Editor-UI).
|
||||
main.interaction.call("select_object", 0)
|
||||
await process_frame
|
||||
await process_frame
|
||||
# Zustands-Beleg in der Konsole (je Modus die passende Beobachtung).
|
||||
var builder = main.get("view_builder")
|
||||
var marker: Node3D = builder.object_markers[0]
|
||||
var mi := marker.get_child(0) as MeshInstance3D
|
||||
if cam != null:
|
||||
print("PRUEF cam: pos=%s target=%s fly=%s dist=%s" % [
|
||||
str(cam.position), str(cam.get("target")),
|
||||
str(cam.call("fly_active")), str(cam.get("distance"))])
|
||||
var hat_outline: bool = marker.has_meta("outline") \
|
||||
and is_instance_valid(marker.get_meta("outline"))
|
||||
print("PRUEF modus=%s boden=%s glb=%s material_override=%s outline=%s" % [
|
||||
modus, "sand" if material == SAND else "fels",
|
||||
str(bool(marker.get_meta("glb", false))),
|
||||
str(mi.material_override != null), str(hat_outline)])
|
||||
var dir := OS.get_environment("U6_DIR")
|
||||
if dir.is_empty():
|
||||
dir = "/tmp/u6_beweise"
|
||||
DirAccess.make_dir_recursive_absolute(dir)
|
||||
var name := "u6_%s_%s.png" % [modus, "sand" if material == SAND else "fels"]
|
||||
await _shot(dir.path_join(name))
|
||||
print("PROBE_U6_%s_%s" % [modus.to_upper(), "OK"])
|
||||
quit(0)
|
||||
1
tests/probe_u6_render.gd.uid
Normal file
1
tests/probe_u6_render.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cu3rrquankmwk
|
||||
|
|
@ -334,11 +334,17 @@ func _test_viewport_rebuild_echt() -> void:
|
|||
"Mesh-Unterkante == Terrainhöhe (%.3f m, ist %.3f m — b20dff2-Konvention)" % [boden, unten])
|
||||
_check(mi.material_override == null,
|
||||
"keine Grundfarbe drüber — Texturen des echten Modells bleiben sichtbar")
|
||||
# Auswahl färbt trotzdem orange (Material-Override NUR im Auswahl-Zustand).
|
||||
# U6 (t_639135dd): Auswahl = Außenhüllen-Kontur um das Modell — das
|
||||
# Grundmaterial (mit Textur) bleibt im Auswahl-Zustand KOMPLETT sichtbar,
|
||||
# es gibt kein material_override mehr für die Markierung.
|
||||
builder.call("set_selected_multi", [0])
|
||||
_check(mi.material_override != null, "Auswahl: orange Override an")
|
||||
_check(mi.material_override == null,
|
||||
"Auswahl: Textur bleibt sichtbar (kein material_override mehr, U6)")
|
||||
_check(marker.get_meta("outline", null) is MeshInstance3D,
|
||||
"Auswahl: Kontur-Outline am echten GLB-Marker (U6)")
|
||||
builder.call("set_selected_multi", [])
|
||||
_check(mi.material_override == null, "Auswahl weg: Texturen wieder frei")
|
||||
_check(not marker.has_meta("outline"), "Auswahl weg: kein Rest-Outline (U6)")
|
||||
# Skalierung: scale 2.0 → AABB-Doppelte Höhe im Marker-Meta.
|
||||
builder.call("rebuild", _proben_doc(PROBE_OID, 2.0), _load_manifest())
|
||||
var m2: Node3D = (builder.get("object_markers") as Array)[0]
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ extends SceneTree
|
|||
## T2 Farbfeld-Klick → doc-Feld "tint" ([r,g,b], 0..1) + Status „Farbe: Oliv“;
|
||||
## gleiche Farbe erneut = KEIN Leer-Undo-Schritt.
|
||||
## T3 Editor-Ansicht: unmarkiertes Objekt zeigt tint (Surface-Override-
|
||||
## Material albedo = doc-Wert); Auswahl-Orange gewinnt weiter
|
||||
## (material_override), nach dem Abwählen ist die Farbe wieder sichtbar.
|
||||
## Material albedo = doc-Wert); Markierung = Auswahl-Kontur (U6), die
|
||||
## Farbe bleibt sichtbar und liegt trotzdem im doc.
|
||||
## T4 Mehrfachauswahl: eine Farbe auf beide Objekte = GENAU EIN Undo-Schritt.
|
||||
## T5 Zurücksetzen (Pack-Farbe): Feld entfernt, Override weg, EIN Undo-Schritt.
|
||||
## T6 save→load-Roundtrip: tint überlebt; alte Karte ohne Feld lädt
|
||||
|
|
@ -141,7 +141,10 @@ func _t3_editor_ansicht() -> void:
|
|||
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[3][1], "Stahlgrau")
|
||||
var c: Color = SvmapTint.von(_sel_obj(0))
|
||||
var mi := _marker_mi(0)
|
||||
_check(mi.material_override != null, "T3 markiertes Objekt bleibt orange (material_override an)")
|
||||
# U6 (t_639135dd): Markierung = Kontur am Marker — die doc-Farbe bleibt
|
||||
# sichtbar (kein „Orange gewinnt“ mehr), das Feld liegt trotzdem im doc.
|
||||
var out0: Variant = main.view_builder.object_markers[0].get_meta("outline", null)
|
||||
_check(out0 is MeshInstance3D, "T3 markiertes Objekt zeigt Auswahl-Kontur (U6)")
|
||||
# Abwählen → Farbe sichtbar: Surface-Override mit albedo = doc-Wert.
|
||||
main.interaction.call("clear_selection", true)
|
||||
await process_frame
|
||||
|
|
@ -160,11 +163,12 @@ func _t3_editor_ansicht() -> void:
|
|||
var ov1 := mi1.get_surface_override_material(0)
|
||||
_check(ov1 is BaseMaterial3D and SvmapTint.gleich((ov1 as BaseMaterial3D).albedo_color,
|
||||
SvmapTint.von(_sel_obj(1))), "T3 Platzhalter zeigt doc-Farbe (Holzbraun)")
|
||||
# Orange gewinnt weiter, FARBE KOMMT ZURÜCK beim Abwählen (Auswahl des
|
||||
# Objekts 0 lässt 1 unberührt).
|
||||
# Auswahl-Kontur (U6) statt Orange; FARBE KOMMT ZURÜCK beim Abwählen
|
||||
# (Auswahl des Objekts 0 lässt 1 unberührt).
|
||||
main.interaction.call("select_object", 1)
|
||||
await process_frame
|
||||
_check(_marker_mi(1).material_override != null, "T3 Auswahl-Orange gewinnt weiter")
|
||||
var out1: Variant = main.view_builder.object_markers[1].get_meta("outline", null)
|
||||
_check(out1 is MeshInstance3D, "T3 Auswahl-Kontur am markierten Objekt (U6)")
|
||||
|
||||
|
||||
func _t4_mehrfach() -> void:
|
||||
|
|
|
|||
|
|
@ -518,25 +518,37 @@ func _test_partner_marking() -> void:
|
|||
root.add_child(builder)
|
||||
var manifest := {"objects": {"crate_wood": {"collision": {"shape": "box", "size": [1.2, 1.2, 1.2]}}}}
|
||||
builder.call("rebuild", doc, manifest)
|
||||
# Quelle (0) anwählen → Zwilling (1) schwächer markiert.
|
||||
# Quelle (0) anwählen → Zwilling (1) schwächer markiert. U6 (t_639135dd):
|
||||
# Markierung = Außenhüllen-Kontur statt Vollfarbe — Quelle trägt die
|
||||
# normale Auswahl-Kontur, der Zwilling die dünnere/gestrichelte Partner-
|
||||
# Kontur in eigener Theme-Farbe.
|
||||
builder.call("set_selected_multi", [0])
|
||||
var select_mat: StandardMaterial3D = builder.get("_select_mat")
|
||||
var partner_mat: StandardMaterial3D = builder.get("_partner_mat")
|
||||
var m0: MeshInstance3D = (builder.get("object_markers") as Array)[0].get_child(0)
|
||||
var m1: MeshInstance3D = (builder.get("object_markers") as Array)[1].get_child(0)
|
||||
_check(m0.material_override == select_mat, "Auswahl: Quelle volle Orange-Markierung")
|
||||
_check(m1.material_override == partner_mat, "Zwilling: schwächere Partner-Markierung")
|
||||
_check(partner_mat != select_mat and partner_mat != builder.get("_base_mat"),
|
||||
var sel_mat: ShaderMaterial = builder.get("_outline_mat_sel")
|
||||
var partner_mat: ShaderMaterial = builder.get("_outline_mat_partner")
|
||||
var markers: Array = builder.get("object_markers")
|
||||
var k0: Variant = (markers[0] as Node3D).get_meta("outline") \
|
||||
if (markers[0] as Node3D).has_meta("outline") else null
|
||||
var k1: Variant = (markers[1] as Node3D).get_meta("outline") \
|
||||
if (markers[1] as Node3D).has_meta("outline") else null
|
||||
_check(k0 is MeshInstance3D and (k0 as MeshInstance3D).material_override == sel_mat,
|
||||
"Auswahl: Quelle trägt die Auswahl-Kontur")
|
||||
_check(k1 is MeshInstance3D and (k1 as MeshInstance3D).material_override == partner_mat,
|
||||
"Zwilling: schwächere Partner-Kontur")
|
||||
_check(partner_mat != sel_mat,
|
||||
"Partner-Material ist eigenes, abgeschwächtes Material (gleiche Technik)")
|
||||
# Beide angewählt → beide volle Markierung, kein Partner-Zustand.
|
||||
# Beide angewählt → beide volle Kontur, kein Partner-Zustand.
|
||||
builder.call("set_selected_multi", [0, 1])
|
||||
_check(m0.material_override == select_mat and m1.material_override == select_mat,
|
||||
"beide angewählt: beide volle Auswahl-Markierung")
|
||||
# Abwählen: Basiszustand zurück (GLB fehlt → Platzhalter mit Basis-Farbe).
|
||||
k0 = (markers[0] as Node3D).get_meta("outline", null)
|
||||
k1 = (markers[1] as Node3D).get_meta("outline", null)
|
||||
_check(k0 is MeshInstance3D and (k0 as MeshInstance3D).material_override == sel_mat
|
||||
and k1 is MeshInstance3D and (k1 as MeshInstance3D).material_override == sel_mat,
|
||||
"beide angewählt: beide volle Auswahl-Kontur")
|
||||
# Abwählen: Basiszustand zurück — Konturen restlos weg (Meta entfernt,
|
||||
# Grundmaterial blieb die ganze Zeit unangetastet).
|
||||
builder.call("set_selected_multi", [])
|
||||
_check(m0.material_override == builder.get("_base_mat")
|
||||
and m1.material_override == builder.get("_base_mat"),
|
||||
"ohne Auswahl: Basis-Farben (wie bisher)")
|
||||
_check(not (markers[0] as Node3D).has_meta("outline")
|
||||
and not (markers[1] as Node3D).has_meta("outline"),
|
||||
"ohne Auswahl: keine Rest-Markierung (U6)")
|
||||
builder.queue_free()
|
||||
|
||||
|
||||
|
|
|
|||
287
tests/test_selection_look.gd
Normal file
287
tests/test_selection_look.gd
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
extends SceneTree
|
||||
## Editor-Tests (headless): U6 dezente Auswahl — Kontur statt Vollorange
|
||||
## (Karte t_639135dd). Ausführen:
|
||||
## godot --headless --path . -s res://tests/test_selection_look.gd
|
||||
## Exit 0 = alles grün.
|
||||
##
|
||||
## T1 Theme ist die einzige Quelle: Kontur-Farben (Auswahl/Hover/Partner)
|
||||
## + Breiten existieren, Partner/Hover sind schwächer als die Auswahl.
|
||||
## T2 Grundmaterial bleibt: Auswahl setzt KEIN Vollfarben-Override mehr —
|
||||
## der Mesh-Kind-Knoten trägt weiter die Editor-Grundfarbe (Platzhalter)
|
||||
## bzw. null (GLB — Textur bleibt; echte GLBs deckt test_glb_view ab).
|
||||
## T3 Outline-Node nur bei markierten Objekten (Auswahl/Hover/Partner),
|
||||
## bei Abwahl restlos weg (kein Rest-Outline, kein Rest-Meta).
|
||||
## T4 Abstufungen: Hover = schwächstes Material, Auswahl = normale Kontur,
|
||||
## Spiegel-Zwilling = dünnere + gestrichelte Kontur in eigener Farbe.
|
||||
## T5 Spawn-Zonen: dezente Variante — Ring statt Aufhellung, restlos weg
|
||||
## bei Abwahl; Disc-Deckkraft bleibt konstant.
|
||||
## T6 Bau-Teile: Kontur um das Bauteil-Mesh statt Vollbox, weg bei Abwahl.
|
||||
|
||||
var failures := 0
|
||||
var checks := 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run_all.call_deferred()
|
||||
|
||||
|
||||
func _check(cond: bool, label: String) -> void:
|
||||
checks += 1
|
||||
print("%s: %s" % ["ok " if cond else "FAIL", label])
|
||||
if not cond:
|
||||
failures += 1
|
||||
|
||||
|
||||
func _run_all() -> void:
|
||||
await process_frame
|
||||
_test_theme_quelle()
|
||||
await _test_auswahl_platzhalter()
|
||||
await _test_hover_abstufung()
|
||||
await _test_zwilling()
|
||||
await _test_spawn_ring()
|
||||
await _test_gebaeude_kontur()
|
||||
if failures == 0:
|
||||
print("ALL OK: %d checks" % checks)
|
||||
quit(0)
|
||||
else:
|
||||
print("FAILED: %d/%d checks" % [failures, checks])
|
||||
quit(1)
|
||||
|
||||
|
||||
func _obj(id: String, pos: Array, rot := 0.0) -> Dictionary:
|
||||
return {"id": id, "pos": pos, "scale": 1.0, "rot_y_deg": float(rot)}
|
||||
|
||||
|
||||
func _build(manifest: Dictionary, doc: Dictionary) -> Node3D:
|
||||
var builder := Node3D.new()
|
||||
builder.set_script(load("res://src/viewport/map_view_builder.gd"))
|
||||
root.add_child(builder)
|
||||
builder.call("rebuild", doc, manifest)
|
||||
return builder
|
||||
|
||||
|
||||
func _manifest() -> Dictionary:
|
||||
return {"objects": {"crate_wood": {"collision": {"shape": "box", "size": [1.2, 1.2, 1.2]}}}}
|
||||
|
||||
|
||||
func _flat_doc() -> Dictionary:
|
||||
return SvmapDocument.create_new("U6Test", 256.0, "mittag")
|
||||
|
||||
|
||||
func _outline_von(marker: Node3D) -> MeshInstance3D:
|
||||
if not marker.has_meta("outline"):
|
||||
return null
|
||||
var alt: Variant = marker.get_meta("outline")
|
||||
if alt is MeshInstance3D and is_instance_valid(alt):
|
||||
return alt
|
||||
return null
|
||||
|
||||
|
||||
func _restlos(builder: Node3D, label: String) -> void:
|
||||
await process_frame
|
||||
var sauber := true
|
||||
for marker in builder.object_markers:
|
||||
if marker == null or not is_instance_valid(marker):
|
||||
continue
|
||||
if marker.has_meta("outline"):
|
||||
sauber = false
|
||||
var mi := marker.get_child(0) as MeshInstance3D
|
||||
for kind in mi.get_children():
|
||||
if kind.name == "SelectionOutline" and is_instance_valid(kind) \
|
||||
and not (kind as Node).is_queued_for_deletion():
|
||||
sauber = false
|
||||
_check(sauber, label)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ T1 ------
|
||||
func _test_theme_quelle() -> void:
|
||||
print("== T1 Theme als einzige Quelle ==")
|
||||
var theme_res: Variant = load("res://theme/editor_theme.tres")
|
||||
_check(theme_res is Theme, "Theme lädt")
|
||||
if not (theme_res is Theme):
|
||||
return
|
||||
var th := theme_res as Theme
|
||||
var sel := th.get_color("selection_outline", "Editor")
|
||||
var hover := th.get_color("selection_outline_hover", "Editor")
|
||||
var partner := th.get_color("selection_outline_partner", "Editor")
|
||||
_check(th.has_color("selection_outline", "Editor"), "Theme: Auswahl-Kontur-Farbe")
|
||||
_check(th.has_color("selection_outline_hover", "Editor"), "Theme: Hover-Kontur-Farbe")
|
||||
_check(th.has_color("selection_outline_partner", "Editor"), "Theme: Partner-Kontur-Farbe")
|
||||
_check(hover.a < sel.a, "Theme: Hover schwächer als Auswahl (Deckkraft)")
|
||||
_check(not partner.is_equal_approx(sel), "Theme: Partner hat EIGENE Farbe")
|
||||
_check(th.get_constant("selection_outline_width_mm", "Editor") > 0,
|
||||
"Theme: Auswahl-Breite > 0")
|
||||
_check(th.get_constant("selection_outline_width_partner_mm", "Editor")
|
||||
< th.get_constant("selection_outline_width_mm", "Editor"),
|
||||
"Theme: Partner-Kontur dünner als Auswahl")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ T2/T3 ---
|
||||
func _test_auswahl_platzhalter() -> void:
|
||||
print("== T2/T3 Auswahl am Platzhalter: Grundmaterial bleibt, Kontur nur bei Auswahl ==")
|
||||
var doc := _flat_doc()
|
||||
(doc["objects"] as Array).append(_obj("crate_wood", [4.0, 0.0, 2.0]))
|
||||
(doc["objects"] as Array).append(_obj("crate_wood", [-4.0, 0.0, -2.0]))
|
||||
var builder := _build(_manifest(), doc)
|
||||
var m0: Node3D = builder.object_markers[0]
|
||||
var m1: Node3D = builder.object_markers[1]
|
||||
var mi0 := m0.get_child(0) as MeshInstance3D
|
||||
var mat_sel: ShaderMaterial = builder.get("_outline_mat_sel")
|
||||
_check(not bool(m0.get_meta("glb", true)), "Probenobjekt ist Platzhalter (kein GLB)")
|
||||
_check(mi0.material_override == builder.get("_base_mat"),
|
||||
"ungewählt: Editor-Grundfarbe am Mesh (Platzhalter)")
|
||||
builder.call("set_selected_multi", [0])
|
||||
await process_frame
|
||||
_check(mi0.material_override == builder.get("_base_mat"),
|
||||
"Auswahl: Grundmaterial UNVERÄNDERT (kein material_override mit Vollfarbe)")
|
||||
var out0 := _outline_von(m0)
|
||||
_check(out0 != null, "Auswahl: Outline-Node existiert")
|
||||
if out0 != null:
|
||||
_check(out0.mesh == mi0.mesh, "Outline trägt dasselbe Mesh (Außenhülle)")
|
||||
_check(out0.get_parent() == mi0, "Outline hängt am Mesh (erbt Position/Scale)")
|
||||
_check(out0.material_override == mat_sel, "Outline trägt das Auswahl-Kontur-Material")
|
||||
_check(_outline_von(m1) == null, "unmarkiertes Objekt: KEIN Outline-Node")
|
||||
# Abwahl: restlos weg.
|
||||
builder.call("set_selected_multi", [])
|
||||
await _restlos(builder, "Abwahl: kein Rest-Outline, kein Rest-Meta")
|
||||
await _test_glb_regel(builder)
|
||||
builder.queue_free()
|
||||
|
||||
|
||||
## GLB-Regel auch ohne echtes Modell prüfbar machen: ein Marker, dessen Meta
|
||||
## "glb" = true ist, darf NIE ein material_override bekommen. Der Platzhalter-
|
||||
## Builder baut nur glb=false — die echte GLB-Strecke deckt test_glb_view ab;
|
||||
## hier wird die Regel direkt am Bau-Pfad verifiziert (Quelltext-Garantie:
|
||||
## material_override = null wenn echt). Wir prüfen die Meta-Abhängigkeit,
|
||||
## indem wir einen Marker künstlich als GLB markieren und neu auswählen.
|
||||
func _test_glb_regel(builder: Node3D) -> void:
|
||||
var doc: Dictionary = builder.get("_doc_ref")
|
||||
(doc["objects"] as Array).append(_obj("crate_wood", [0.0, 0.0, 8.0]))
|
||||
builder.call("rebuild", doc, _manifest())
|
||||
var m2: Node3D = builder.object_markers[2]
|
||||
m2.set_meta("glb", true)
|
||||
var mi2 := m2.get_child(0) as MeshInstance3D
|
||||
mi2.material_override = null
|
||||
builder.call("set_selected_multi", [2])
|
||||
await process_frame
|
||||
_check(mi2.material_override == null,
|
||||
"GLB-Objekt ausgewählt: material_override bleibt null (Textur bleibt sichtbar)")
|
||||
_check(_outline_von(m2) != null, "GLB-Objekt ausgewählt: Kontur trägt die Markierung")
|
||||
builder.call("set_selected_multi", [])
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ T4 ------
|
||||
func _test_hover_abstufung() -> void:
|
||||
print("== T4a Hover = schwächste Markierung ==")
|
||||
var doc := _flat_doc()
|
||||
(doc["objects"] as Array).append(_obj("crate_wood", [4.0, 0.0, 2.0]))
|
||||
var builder := _build(_manifest(), doc)
|
||||
var m0: Node3D = builder.object_markers[0]
|
||||
builder.call("set_hover", 0)
|
||||
await process_frame
|
||||
var out0 := _outline_von(m0)
|
||||
_check(out0 != null, "Hover: Outline-Node existiert")
|
||||
if out0 != null:
|
||||
_check(out0.material_override == builder.get("_outline_mat_hover"),
|
||||
"Hover: schwächstes Kontur-Material")
|
||||
builder.call("set_hover", -1)
|
||||
await process_frame
|
||||
_check(_outline_von(m0) == null, "Hover weg: Outline restlos entfernt")
|
||||
# Auswahl gewinnt gegen Hover (stärkste Stufe bleibt sichtbar).
|
||||
builder.call("set_hover", 0)
|
||||
builder.call("set_selected_multi", [0])
|
||||
await process_frame
|
||||
var out_sel := _outline_von(m0)
|
||||
_check(out_sel != null and out_sel.material_override == builder.get("_outline_mat_sel"),
|
||||
"Auswahl + Hover gleichzeitig: Auswahl-Kontur gewinnt")
|
||||
builder.queue_free()
|
||||
|
||||
|
||||
func _test_zwilling() -> void:
|
||||
print("== T4b Spiegel-Zwilling = dünnere/gestrichelte Kontur in eigener Farbe ==")
|
||||
var doc := _flat_doc()
|
||||
(doc["objects"] as Array).append(_obj("crate_wood", [100.0, 0.0, 40.0], 30.0))
|
||||
var res: Dictionary = ObjectOps.symmetry_place_twin(doc, 0, SymmetryCore.Mode.POINT)
|
||||
_check(int(res.get("twin", -1)) == 1, "Zwilling angelegt (Index 1)")
|
||||
var builder := _build(_manifest(), doc)
|
||||
builder.call("set_selected_multi", [0])
|
||||
await process_frame
|
||||
var m0: Node3D = builder.object_markers[0]
|
||||
var m1: Node3D = builder.object_markers[1]
|
||||
var out0 := _outline_von(m0)
|
||||
var out1 := _outline_von(m1)
|
||||
_check(out0 != null and out0.material_override == builder.get("_outline_mat_sel"),
|
||||
"Quelle: normale Auswahl-Kontur")
|
||||
_check(out1 != null and out1.material_override == builder.get("_outline_mat_partner"),
|
||||
"Zwilling: Partner-Kontur (eigene, schwächere Variante)")
|
||||
var mat_sel: ShaderMaterial = builder.get("_outline_mat_sel")
|
||||
var mat_partner: ShaderMaterial = builder.get("_outline_mat_partner")
|
||||
var breite_sel: float = mat_sel.get_shader_parameter("breite_m")
|
||||
var breite_partner: float = mat_partner.get_shader_parameter("breite_m")
|
||||
var streifen_partner: float = mat_partner.get_shader_parameter("streifen_hz")
|
||||
var streifen_sel: float = mat_sel.get_shader_parameter("streifen_hz")
|
||||
_check(breite_partner < breite_sel, "Zwilling: dünnere Kontur (Breite aus Theme)")
|
||||
_check(streifen_partner > 0.0 and streifen_sel == 0.0,
|
||||
"Zwilling: gestrichelt, Auswahl durchgezogen")
|
||||
_check(not (mat_partner.get_shader_parameter("linien_farbe") as Color).is_equal_approx(
|
||||
mat_sel.get_shader_parameter("linien_farbe")),
|
||||
"Zwilling: eigene Farbe (aus dem Theme)")
|
||||
# Beide angewählt → beide volle Auswahl-Kontur.
|
||||
builder.call("set_selected_multi", [0, 1])
|
||||
await process_frame
|
||||
_check(_outline_von(m0).material_override == builder.get("_outline_mat_sel")
|
||||
and _outline_von(m1).material_override == builder.get("_outline_mat_sel"),
|
||||
"beide angewählt: beide volle Auswahl-Kontur (kein Partner-Zustand)")
|
||||
builder.call("set_selected_multi", [])
|
||||
await _restlos(builder, "Paar abgewählt: Konturen restlos weg")
|
||||
builder.queue_free()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ T5 ------
|
||||
func _test_spawn_ring() -> void:
|
||||
print("== T5 Spawn-Zonen: Ring statt Aufhellung ==")
|
||||
var builder := _build(_manifest(), _flat_doc())
|
||||
var marker: Node3D = builder.call("_spawn_marker", Vector3.ZERO, 0, "A1", 6.0)
|
||||
var disc0 := marker.get_child(0) as MeshInstance3D
|
||||
var alpha0: float = (disc0.material_override as StandardMaterial3D).albedo_color.a
|
||||
builder.call("style_spawn_marker", marker, 6.0, true)
|
||||
await process_frame
|
||||
var ring: Variant = marker.get_meta("spawn_ring") if marker.has_meta("spawn_ring") else null
|
||||
_check(ring is MeshInstance3D and is_instance_valid(ring), "Auswahl: Kontur-Ring existiert")
|
||||
if ring is MeshInstance3D:
|
||||
_check((ring as MeshInstance3D).mesh is TorusMesh, "Ring = schmaler Torus am Zonenrand")
|
||||
var rmat := (ring as MeshInstance3D).material_override as StandardMaterial3D
|
||||
var theme_res: Variant = load("res://theme/editor_theme.tres")
|
||||
var soll: Color = (theme_res as Theme).get_color("selection_outline", "Editor")
|
||||
_check(rmat.albedo_color.is_equal_approx(soll), "Ring-Farbe kommt aus dem Theme")
|
||||
var disc1 := marker.get_child(0) as MeshInstance3D
|
||||
var alpha1: float = (disc1.material_override as StandardMaterial3D).albedo_color.a
|
||||
_check(absf(alpha0 - alpha1) < 0.001,
|
||||
"Disc-Deckkraft konstant (keine Vollflächen-Aufhellung mehr)")
|
||||
builder.call("style_spawn_marker", marker, 6.0, false)
|
||||
await process_frame
|
||||
_check(not marker.has_meta("spawn_ring"), "Abwahl: Ring-Meta restlos weg")
|
||||
builder.queue_free()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ T6 ------
|
||||
func _test_gebaeude_kontur() -> void:
|
||||
print("== T6 Bau-Teile: Kontur statt Vollbox ==")
|
||||
var doc := _flat_doc()
|
||||
var teil: Dictionary = BuildOps.make_piece("wand", Vector3(2.0, 0.2, 4.0), 0.0)
|
||||
_check(not teil.is_empty(), "Proben-Bauteil angelegt")
|
||||
(doc["gebaeude"] as Array).append(teil)
|
||||
var builder := _build(_manifest(), doc)
|
||||
builder.call("set_gebaeude_selected", 0)
|
||||
await process_frame
|
||||
var sel: Variant = builder.get("_gebaeude_sel")
|
||||
_check(sel is MeshInstance3D and is_instance_valid(sel), "Bauteil-Auswahl: Kontur-Mesh existiert")
|
||||
if sel is MeshInstance3D:
|
||||
_check((sel as MeshInstance3D).mesh != null, "Kontur trägt das Bauteil-Mesh")
|
||||
_check((sel as MeshInstance3D).material_override == builder.get("_outline_mat_sel"),
|
||||
"Kontur trägt das Auswahl-Material (Theme)")
|
||||
builder.call("set_gebaeude_selected", -1)
|
||||
await process_frame
|
||||
var sel2: Variant = builder.get("_gebaeude_sel")
|
||||
_check(sel2 == null, "Bauteil-Abwahl: Kontur restlos weg")
|
||||
builder.queue_free()
|
||||
1
tests/test_selection_look.gd.uid
Normal file
1
tests/test_selection_look.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://eoayxyagpeig
|
||||
|
|
@ -109,6 +109,12 @@ Editor/colors/update_bg = Color(0.36, 0.25, 0.06, 0.97)
|
|||
Editor/colors/update_border = Color(0.95, 0.72, 0.15, 1)
|
||||
Editor/colors/rubber_fill = Color(0.45, 0.8, 1, 0.16)
|
||||
Editor/colors/rubber_line = Color(0.45, 0.8, 1, 0.9)
|
||||
Editor/colors/selection_outline = Color(1, 0.6, 0.15, 0.95)
|
||||
Editor/colors/selection_outline_hover = Color(1, 0.6, 0.15, 0.45)
|
||||
Editor/colors/selection_outline_partner = Color(1, 0.78, 0.4, 0.85)
|
||||
Editor/constants/selection_outline_width_mm = 80
|
||||
Editor/constants/selection_outline_width_hover_mm = 45
|
||||
Editor/constants/selection_outline_width_partner_mm = 45
|
||||
PanelContainer/styles/panel = SubResource("panel")
|
||||
TabContainer/styles/panel = SubResource("panel")
|
||||
TabContainer/styles/tab_selected = SubResource("tab_sel")
|
||||
|
|
|
|||
Loading…
Reference in a new issue