## Stanza 18x18x5 con 4 finestre (una per muro) e nessuna porta. ## Geometria costruita in codice per determinismo; vetro = Area3D rompibile. class_name Room extends Node3D const W: float = 18.0 # larghezza (x) const D: float = 18.0 # profondita (z) const H: float = 5.0 # altezza (y) const WALL_T: float = 0.3 const WALL_REPEAT_M := 4.0 # 128x160 texture spans 4x5m: equal texel density const CEILING_REPEAT_M := 1.2 # CEIL5 contains 2x2 panels: each panel = 0.6m # Finestra: 4 m x 2 m, bordo basso a 1.5 m const WIN_W: float = 4.0 const WIN_H: float = 2.0 const WIN_Y0: float = 1.5 var glass_panes: Array[Area3D] = [] ## Punti di spawn esterni, uno per finestra (4 finestre x 4 slot) var window_spawn_points: Array[Vector3] = [] var window_normals: Array[Vector3] = [] # direzione "dentro" la stanza per ogni finestra func _ready() -> void: var wall_mat := _surface_material("wall", 0.9) var floor_mat := _surface_material("floor", 0.95) var ceil_mat := _surface_material("ceiling", 0.85) # Pavimento _box(Vector3(W, 0.2, D), Vector3(0, -0.1, 0), floor_mat, true) # Soffitto _box(Vector3(W, 0.2, D), Vector3(0, H + 0.1, 0), ceil_mat, false) # 4 muri con apertura finestra al centro # nord (z = -D/2), sud (z = +D/2), ovest (x = -W/2), est (x = +W/2) var walls := [ {"center": Vector3(0, H / 2, -D / 2 - WALL_T / 2), "size": Vector3(W, H, WALL_T), "axis": "x"}, {"center": Vector3(0, H / 2, D / 2 + WALL_T / 2), "size": Vector3(W, H, WALL_T), "axis": "x"}, {"center": Vector3(-W / 2 - WALL_T / 2, H / 2, 0), "size": Vector3(WALL_T, H, D), "axis": "z"}, {"center": Vector3(W / 2 + WALL_T / 2, H / 2, 0), "size": Vector3(WALL_T, H, D), "axis": "z"}, ] for w in walls: _build_wall_with_window(w.center, w.size, w.axis, wall_mat) # Vetrata per ogni finestra _add_glass(Vector3(0, WIN_Y0 + WIN_H / 2, -D / 2), Vector3(WIN_W, WIN_H, 0.05), Vector3(1, 0, 0)) _add_glass(Vector3(0, WIN_Y0 + WIN_H / 2, D / 2), Vector3(WIN_W, WIN_H, 0.05), Vector3(1, 0, 0)) _add_glass(Vector3(-W / 2, WIN_Y0 + WIN_H / 2, 0), Vector3(0.05, WIN_H, WIN_W), Vector3(0, 0, 1)) _add_glass(Vector3(W / 2, WIN_Y0 + WIN_H / 2, 0), Vector3(0.05, WIN_H, WIN_W), Vector3(0, 0, 1)) # Punti di spawn: 4 slot per finestra, fuori dal muro var win_centers := [ {"c": Vector3(0, 0, -D / 2), "n": Vector3(0, 0, 1)}, {"c": Vector3(0, 0, D / 2), "n": Vector3(0, 0, -1)}, {"c": Vector3(-W / 2, 0, 0), "n": Vector3(1, 0, 0)}, {"c": Vector3(W / 2, 0, 0), "n": Vector3(-1, 0, 0)}, ] for wc in win_centers: var side: Vector3 = wc.n.cross(Vector3(0, 1, 0)) # asse laterale della finestra for i in 4: var off := (i - 1.5) * (WIN_W / 3.0) var p: Vector3 = wc.c + side * off + wc.n * (-1.2) # 1.2 m fuori dalla finestra window_spawn_points.append(p) window_normals.append(wc.n) func _build_wall_with_window(center: Vector3, size: Vector3, axis: String, mat: StandardMaterial3D) -> void: # Segments around the window opening (window is centered on the wall face) var wall := StaticBody3D.new() wall.name = "Wall" add_child(wall) var half_win_x := WIN_W / 2.0 var y0 := WIN_Y0 var y1 := WIN_Y0 + WIN_H if axis == "x": # muro lungo x var L := size.x var segs := [ {"s": Vector3(L / 2 - half_win_x, size.y, size.z), "c": Vector3(-half_win_x - (L / 2 - half_win_x) / 2.0, 0, 0)}, {"s": Vector3(L / 2 - half_win_x, size.y, size.z), "c": Vector3(half_win_x + (L / 2 - half_win_x) / 2.0, 0, 0)}, {"s": Vector3(WIN_W, y0, size.z), "c": Vector3(0, -size.y / 2 + y0 / 2.0, 0)}, {"s": Vector3(WIN_W, size.y - y1, size.z), "c": Vector3(0, size.y / 2 - (size.y - y1) / 2.0, 0)}, ] for seg in segs: _add_box_to(wall, seg.s, center + seg.c, mat) else: # muro lungo z var L := size.z var segs := [ {"s": Vector3(size.x, size.y, L / 2 - half_win_x), "c": Vector3(0, 0, -half_win_x - (L / 2 - half_win_x) / 2.0)}, {"s": Vector3(size.x, size.y, L / 2 - half_win_x), "c": Vector3(0, 0, half_win_x + (L / 2 - half_win_x) / 2.0)}, {"s": Vector3(size.x, y0, WIN_W), "c": Vector3(0, -size.y / 2 + y0 / 2.0, 0)}, {"s": Vector3(size.x, size.y - y1, WIN_W), "c": Vector3(0, size.y / 2 - (size.y - y1) / 2.0, 0)}, ] for seg in segs: _add_box_to(wall, seg.s, center + seg.c, mat) func _add_glass(center: Vector3, size: Vector3, rot_axis: Vector3) -> void: var glass := Area3D.new() glass.name = "Glass" glass.collision_layer = 8 glass.collision_mask = 0 add_child(glass) var shape := CollisionShape3D.new() var box := BoxShape3D.new() box.size = size shape.shape = box glass.add_child(shape) glass.global_position = center var mesh := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = size var gm := StandardMaterial3D.new() gm.albedo_color = Color(0.7, 0.85, 0.9, 0.16) gm.cull_mode = BaseMaterial3D.CULL_DISABLED gm.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA gm.shading_mode = BaseMaterial3D.SHADING_MODE_PER_PIXEL gm.roughness = 0.05 gm.metallic = 0.1 bm.material = gm mesh.mesh = bm glass.add_child(mesh) glass_panes.append(glass) func shatter_glass(glass: Area3D) -> void: var sound_origin := Node3D.new() add_child(sound_origin) sound_origin.global_position = glass.global_position preload("res://scripts/hl_audio.gd").play(sound_origin, "debris/bustglass%d.wav" % randi_range(1, 3), true, -12) get_tree().create_timer(4).timeout.connect(sound_origin.queue_free) for i in glass_panes.size(): if glass_panes[i] == glass: glass_panes.remove_at(i) break # Schegge di vetro (particelle semplici) var sh := CPUParticles3D.new() sh.direction = Vector3(0, -0.3, 0) sh.spread = 180 sh.initial_velocity_min = 1.5 sh.initial_velocity_max = 4 sh.gravity = Vector3(0, -9.8, 0) sh.scale_amount_min = 0.4 sh.scale_amount_max = 1.2 sh.color = Color(0.7, 0.85, 1.0, 0.8) sh.emission_shape = CPUParticles3D.EMISSION_SHAPE_SPHERE sh.emission_sphere_radius = 1.2 var shard := BoxMesh.new() shard.size = Vector3(0.05, 0.02, 0.08) var shard_mat := StandardMaterial3D.new() shard_mat.albedo_color = Color(0.6, 0.8, 0.9) shard.material = shard_mat sh.mesh = shard sh.amount = 40 sh.lifetime = 0.9 sh.one_shot = true sh.explosiveness = 1.0 sh.position = glass.global_position add_child(sh) sh.emitting = true get_tree().create_timer(2).timeout.connect(sh.queue_free) glass.queue_free() func _box(s: Vector3, c: Vector3, m: StandardMaterial3D, collide: bool) -> void: var body := StaticBody3D.new() add_child(body) _add_box_to(body, s, c, m) if not collide: body.get_node("shape").set_deferred("disabled", true) func _add_box_to(parent: Node3D, s: Vector3, c: Vector3, m: StandardMaterial3D) -> void: var mi := MeshInstance3D.new() var bm := BoxMesh.new() bm.size = s bm.material = m if m.has_meta("office_surface"): # BoxMesh's per-face UVs reset on every wall segment. Rebuild only UVs; # geometry, normals, indices and collision boxes remain identical. var arrays := bm.surface_get_arrays(0) var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX] var uv := PackedVector2Array() for vertex in vertices: uv.append(office_uv(vertex + c, c, m.get_meta("office_surface"))) arrays[Mesh.ARRAY_TEX_UV] = uv var mesh := ArrayMesh.new() mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) mesh.surface_set_material(0, m) mi.mesh = mesh mi.add_to_group("office_surfaces") else: mi.mesh = bm parent.add_child(mi) var cs := CollisionShape3D.new() var bs := BoxShape3D.new() bs.size = s cs.shape = bs cs.position = c - parent.position if parent is StaticBody3D else c cs.name = "shape" parent.add_child(cs) # il mesh va centrato sullo stesso punto della collision mi.position = c - parent.position if parent is StaticBody3D else c func _surface_material(texture_name: String, rough: float) -> StandardMaterial3D: var material := StandardMaterial3D.new() var office := texture_name in ["wall", "ceiling"] var directory := "res://assets/textures/office/" if office else "res://assets/textures/" material.albedo_texture = load(directory + texture_name + ".png") material.albedo_color = Color.WHITE material.roughness = rough material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC material.texture_repeat = true if office: material.set_meta("office_surface", texture_name) else: # Existing floor projection intentionally unchanged. material.uv1_triplanar = true material.uv1_world_triplanar = true material.uv1_scale = Vector3.ONE * 0.5 return material ## Shared room-space UVs, independent of each window/wall segment size. static func office_uv(point: Vector3, center: Vector3, surface: String) -> Vector2: if surface == "ceiling": return Vector2(point.x + W * 0.5, point.z + D * 0.5) / CEILING_REPEAT_M var along: float if center.z < -D * 0.5: along = point.x + W * 0.5 # north, clockwise elif center.x > W * 0.5: along = W + point.z + D * 0.5 # east elif center.z > D * 0.5: along = W + D + W * 0.5 - point.x # south else: along = W * 2 + D + D * 0.5 - point.z # west # Top pixel at ceiling, bottom at floor: no vertical texture repeats. # Half-texel inset avoids sampling the opposite edge at exact 0/1. return Vector2(along / WALL_REPEAT_M, clampf(1.0 - point.y / H, 0.5 / 160.0, 1.0 - 0.5 / 160.0)) func _mat(color: Color, rough: float) -> StandardMaterial3D: var m := StandardMaterial3D.new() m.albedo_color = color m.roughness = rough return m