diff --git a/app/assets/environment.tres b/app/assets/environment.tres new file mode 100644 index 0000000..1655b28 --- /dev/null +++ b/app/assets/environment.tres @@ -0,0 +1,15 @@ +[gd_resource type="Environment" load_steps=3 format=3 uid="uid://cfm0g4r0h2n1p"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_i4xao"] + +[sub_resource type="Sky" id="Sky_vhymk"] +sky_material = SubResource("ProceduralSkyMaterial_i4xao") + +[resource] +background_mode = 1 +background_color = Color(1, 1, 1, 1) +sky = SubResource("Sky_vhymk") +ambient_light_source = 2 +ambient_light_color = Color(1, 1, 1, 1) +ssao_radius = 6.52 +ssao_intensity = 5.68 diff --git a/app/assets/environment_passthrough.tres b/app/assets/environment_passthrough.tres new file mode 100644 index 0000000..043b190 --- /dev/null +++ b/app/assets/environment_passthrough.tres @@ -0,0 +1,15 @@ +[gd_resource type="Environment" load_steps=3 format=3 uid="uid://gljmitbkl86b"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_i4xao"] + +[sub_resource type="Sky" id="Sky_vhymk"] +sky_material = SubResource("ProceduralSkyMaterial_i4xao") + +[resource] +background_color = Color(0, 0, 0, 0) +background_energy_multiplier = 0.0 +sky = SubResource("Sky_vhymk") +ambient_light_source = 2 +ambient_light_color = Color(1, 1, 1, 1) +ssao_radius = 6.52 +ssao_intensity = 5.68 diff --git a/app/assets/materials/sky.material b/app/assets/materials/sky.material deleted file mode 100644 index 4d08c74..0000000 --- a/app/assets/materials/sky.material +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae8276428c45d39d78153880ad4133705f61063edbc26bea163fec23119bbfc1 -size 302 diff --git a/app/assets/materials/sky_passthrough.material b/app/assets/materials/sky_passthrough.material deleted file mode 100644 index f846ea8..0000000 --- a/app/assets/materials/sky_passthrough.material +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9bac0d498144e58cd467f8db75da57f71b519523ac08799d2e3d75b22ba2401f -size 361 diff --git a/app/content/main.gd b/app/content/main.gd index 0746948..5732423 100644 --- a/app/content/main.gd +++ b/app/content/main.gd @@ -1,8 +1,7 @@ extends Node3D -var sky = preload ("res://assets/materials/sky.material") -var sky_passthrough = preload ("res://assets/materials/sky_passthrough.material") const VoiceAssistant = preload ("res://content/system/assist/assist.tscn") +const environment_passthrough_material = preload ("res://assets/environment_passthrough.tres") @onready var environment: WorldEnvironment = $WorldEnvironment @onready var camera: XRCamera3D = $XROrigin3D/XRCamera3D @@ -15,9 +14,10 @@ var voice_assistant = null func _ready(): # In case we're running on the headset, use the passthrough sky + if OS.get_name() == "Android": # OS.request_permissions() - environment.environment.sky.set_material(sky_passthrough) + environment.environment = environment_passthrough_material get_viewport().transparent_bg = true else: RenderingServer.set_debug_generate_wireframes(true) diff --git a/app/content/main.tscn b/app/content/main.tscn index 47079ed..025c61f 100644 --- a/app/content/main.tscn +++ b/app/content/main.tscn @@ -1,31 +1,18 @@ -[gd_scene load_steps=17 format=3 uid="uid://eecv28y6jxk4"] +[gd_scene load_steps=15 format=3 uid="uid://eecv28y6jxk4"] [ext_resource type="PackedScene" uid="uid://clc5dre31iskm" path="res://addons/godot-xr-tools/xr/start_xr.tscn" id="1_i4c04"] [ext_resource type="Script" path="res://content/main.gd" id="1_uvrd4"] [ext_resource type="PackedScene" uid="uid://b30w6tywfj4fp" path="res://content/system/controller_left/controller_left.tscn" id="2_2lraw"] +[ext_resource type="Environment" uid="uid://cfm0g4r0h2n1p" path="res://assets/environment.tres" id="2_lsndp"] [ext_resource type="PackedScene" uid="uid://dscp8x0ari57n" path="res://content/system/raycast/raycast.tscn" id="3_67lii"] [ext_resource type="PackedScene" uid="uid://b2kjh1fpjptdr" path="res://content/system/camera/camera.tscn" id="3_rj4ac"] [ext_resource type="PackedScene" uid="uid://bsx12q23v8apy" path="res://content/system/hands/hands.tscn" id="4_v8xu6"] [ext_resource type="PackedScene" uid="uid://ctltchlf2j2r4" path="res://addons/xr-simulator/XRSimulator.tscn" id="5_3qc8g"] -[ext_resource type="Material" uid="uid://bf5ina366dwm6" path="res://assets/materials/sky.material" id="5_wgwf8"] [ext_resource type="PackedScene" uid="uid://c3kdssrmv84kv" path="res://content/ui/menu/menu.tscn" id="8_du83w"] [ext_resource type="PackedScene" uid="uid://lrehk38exd5n" path="res://content/system/keyboard/keyboard.tscn" id="9_e5n3p"] [ext_resource type="PackedScene" uid="uid://cbemihbxkd4ll" path="res://content/system/house/house.tscn" id="9_np6mw"] [ext_resource type="PackedScene" uid="uid://bhyddd1f0ry1x" path="res://content/ui/onboarding/onboarding.tscn" id="12_uq2nj"] -[sub_resource type="Sky" id="Sky_vhymk"] -sky_material = ExtResource("5_wgwf8") - -[sub_resource type="Environment" id="Environment_7ghp0"] -background_mode = 1 -background_color = Color(0, 0, 0, 0) -background_energy_multiplier = 0.0 -sky = SubResource("Sky_vhymk") -ambient_light_source = 2 -ambient_light_color = Color(1, 1, 1, 1) -ssao_radius = 6.52 -ssao_intensity = 5.68 - [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_m58yb"] ao_enabled = true @@ -38,7 +25,7 @@ transform = Transform3D(1, -0.000296142, 0.000270963, 0.000296143, 1, -4.5899e-0 script = ExtResource("1_uvrd4") [node name="WorldEnvironment" type="WorldEnvironment" parent="."] -environment = SubResource("Environment_7ghp0") +environment = ExtResource("2_lsndp") [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] transform = Transform3D(1, -2.51787e-05, 0.000567105, -0.000567105, 4.3985e-08, 1, -2.51784e-05, -1, 2.97105e-08, -4.65661e-10, 7.21041, 2.06458) diff --git a/app/content/system/house/doors/doors.gd b/app/content/system/house/doors/doors.gd new file mode 100644 index 0000000..9d6ed45 --- /dev/null +++ b/app/content/system/house/doors/doors.gd @@ -0,0 +1,242 @@ +extends Node3D + +const WallCornerScene = preload ("../room/wall_corner.tscn") +const WallEdgeScene = preload ("../room/wall_edge.tscn") + +## int | null +var editing_door = null + +var room1 = null +var room2 = null +var room1_corner1 = null +var room1_corner2 = null +var room2_corner1 = null +var room2_corner2 = null + +@onready var ray_cast = $RayCast3D + +func _ready(): + pass + +func is_editing(): + return editing_door != null + +func is_valid(): + return room1 != null&&room2 != null&&room1_corner1 != null&&room1_corner2 != null&&room2_corner1 != null&&room2_corner2 != null + +func add(): + var doors = Store.house.state.doors + + var next_index = 0 + for i in range(doors.size()): + next_index = max(next_index, doors[i].id) + edit(next_index + 1) + + return next_index + 1 + +func delete(door): + Store.house.state.doors = Store.house.state.doors.filter(func(d): return d.id != door) + Store.house.save_local() + +func edit(door): + var doors = Store.house.state.doors + editing_door = door + + var existing_door = null + + for i in range(doors.size()): + if doors[i].id == door: + existing_door = doors[i] + break + + if existing_door != null: + room1 = House.body.find_room(existing_door.room1) + room2 = House.body.find_room(existing_door.room2) + + room1_corner1 = WallCornerScene.instantiate() + room1_corner1.global_position = existing_door.room1_position1 + room1_corner1.get_node("Clickable").on_grab_move.connect(_move_corner.bind(room1, room1_corner1)) + add_child(room1_corner1) + + room1_corner2 = WallCornerScene.instantiate() + room1_corner2.global_position = existing_door.room1_position2 + room1_corner2.get_node("Clickable").on_grab_move.connect(_move_corner.bind(room1, room1_corner2)) + add_child(room1_corner2) + + room2_corner1 = WallCornerScene.instantiate() + room2_corner1.global_position = existing_door.room2_position1 + room2_corner1.get_node("Clickable").on_grab_move.connect(_move_corner.bind(room2, room2_corner1)) + add_child(room2_corner1) + + room2_corner2 = WallCornerScene.instantiate() + room2_corner2.global_position = existing_door.room2_position2 + room2_corner2.get_node("Clickable").on_grab_move.connect(_move_corner.bind(room2, room2_corner2)) + add_child(room2_corner2) + + for room in House.body.get_rooms(0): + if room != room1&&room != room2: + room.get_node("WallCollision/Clickable").on_click.connect(_add_corner.bind(room)) + else: + room.get_node("WallCollision/Clickable").on_click.disconnect(_add_corner.bind(room)) + + for room in House.body.get_rooms(0): + if door != null: + room.get_node("WallCollision/Clickable").on_click.connect(_add_corner.bind(room)) + else: + room.get_node("WallCollision/Clickable").on_click.disconnect(_add_corner.bind(room)) + +func discard(): + _clear() + +func save(): + var doors = Store.house.state.doors + + if is_valid() == false: + EventSystem.notify("Door is not valid", EventNotify.Type.WARNING) + return + + var existing_door_index = -1 + for i in range(doors.size()): + + if doors[i].id == editing_door: + existing_door_index = i + break + + var door = { + "id": editing_door, + "room1": room1.name, + "room2": room2.name, + "room1_position1": room1_corner1.global_position, + "room1_position2": room1_corner2.global_position, + "room2_position1": room2_corner1.global_position, + "room2_position2": room2_corner2.global_position + } + + if existing_door_index == - 1: + doors.append(door) + else: + doors[existing_door_index] = door + + Store.house.state.doors = Store.house.state.doors + + room1.update() + room2.update() + + Store.house.save_local() + + _clear() + +func _clear(): + editing_door = null + room1 = null + room2 = null + + if room1_corner1 != null: + remove_child(room1_corner1) + room1_corner1.queue_free() + room1_corner1 = null + + if room1_corner2 != null: + remove_child(room1_corner2) + room1_corner2.queue_free() + room1_corner2 = null + + if room2_corner1 != null: + remove_child(room2_corner1) + room2_corner1.queue_free() + room2_corner1 = null + + if room2_corner2 != null: + remove_child(room2_corner2) + room2_corner2.queue_free() + room2_corner2 = null + +func _add_corner(event: EventPointer, room): + _match_connected_room(event.ray.get_collision_point(), event.ray.get_collision_normal() * - 1) + + if room1_corner1 == null: + if ray_cast.get_collider() == null: + EventSystem.notify("Could not find connected room", EventNotify.Type.WARNING) + return + + var connected_room = ray_cast.get_collider().get_parent() + + room1 = room + room2 = connected_room + + room1_corner1 = WallCornerScene.instantiate() + room1_corner1.global_position = event.ray.get_collision_point() + room1_corner1.get_node("Clickable").on_grab_move.connect(_move_corner.bind(room, room1_corner1)) + add_child(room1_corner1) + + room2_corner1 = WallCornerScene.instantiate() + room2_corner1.global_position = ray_cast.get_collision_point() + room2_corner1.get_node("Clickable").on_grab_move.connect(_move_corner.bind(connected_room, room2_corner1)) + add_child(room2_corner1) + + elif room1_corner2 == null: + if ray_cast.get_collider() == null: + EventSystem.notify("Could not find connected room", EventNotify.Type.WARNING) + return + + if room1 != room: + EventSystem.notify("2nd corner must be in the same room", EventNotify.Type.WARNING) + return + + var connected_room = ray_cast.get_collider().get_parent() + + if room2 != connected_room: + EventSystem.notify("Connected rooms do not match", EventNotify.Type.WARNING) + return + + room1_corner2 = WallCornerScene.instantiate() + room1_corner2.global_position = event.ray.get_collision_point() + room1_corner2.get_node("Clickable").on_grab_move.connect(_move_corner.bind(room, room1_corner2)) + add_child(room1_corner2) + + room2_corner2 = WallCornerScene.instantiate() + room2_corner2.global_position = ray_cast.get_collision_point() + room2_corner2.get_node("Clickable").on_grab_move.connect(_move_corner.bind(connected_room, room2_corner2)) + add_child(room2_corner2) + +func _move_corner(event: EventPointer, room, corner): + _match_room(event.ray.global_position, event.ray.to_global(event.ray.target_position) - event.ray.global_position) + + if ray_cast.get_collider() == null||ray_cast.get_collider().get_parent() != room: + return + + var collision_point = ray_cast.get_collision_point() + + _match_connected_room(collision_point, ray_cast.get_collision_normal() * - 1) + + var connected_collision_point = ray_cast.get_collision_point() + + if ray_cast.get_collider() == null||ray_cast.get_collider().get_parent() != _get_connected_room(room): + return + + corner.global_position = collision_point + _get_connected_corner(corner).global_position = connected_collision_point + +func _get_connected_room(room): + return room1 if room == room2 else room2 + +func _get_connected_corner(corner): + match corner: + room1_corner1: + return room2_corner1 + room1_corner2: + return room2_corner2 + room2_corner1: + return room1_corner1 + room2_corner2: + return room1_corner2 + +func _match_room(pos: Vector3, dir: Vector3): + ray_cast.global_position = pos + ray_cast.target_position = dir.normalized() * 1000 + ray_cast.force_raycast_update() + +func _match_connected_room(pos: Vector3, dir: Vector3): + ray_cast.global_position = pos + ray_cast.target_position = dir.normalized() * 1000 + ray_cast.force_raycast_update() \ No newline at end of file diff --git a/app/content/system/house/doors/doors.tscn b/app/content/system/house/doors/doors.tscn new file mode 100644 index 0000000..ee08ba9 --- /dev/null +++ b/app/content/system/house/doors/doors.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://c8nh8582vwc8u"] + +[ext_resource type="Script" path="res://content/system/house/doors/doors.gd" id="1_rsgao"] + +[node name="Doors" type="Node3D"] +script = ExtResource("1_rsgao") + +[node name="RayCast3D" type="RayCast3D" parent="."] +top_level = true +enabled = false +collision_mask = 16 +hit_from_inside = true diff --git a/app/content/system/house/house.gd b/app/content/system/house/house.gd index 1d6135c..ed21058 100644 --- a/app/content/system/house/house.gd +++ b/app/content/system/house/house.gd @@ -3,12 +3,14 @@ extends Node3D const Room = preload ("./room/room.tscn") const RoomType = preload ("./room/room.gd") const Miniature = preload ("./mini/miniature.gd") +const Doors = preload ("./doors/doors.gd") const AlignReference = preload ("./align_reference.gd") @onready var levels = $Levels @onready var collision_shape = $Levels/CollisionShape3D @onready var align_reference: AlignReference = $AlignReference @onready var mini_view: Miniature = $Levels/Miniature +@onready var doors: Doors = $Levels/Doors var fixing_reference: bool = false var editing_room: RoomType = null diff --git a/app/content/system/house/house.tscn b/app/content/system/house/house.tscn index 17f7d78..e69fa0a 100644 --- a/app/content/system/house/house.tscn +++ b/app/content/system/house/house.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=6 format=3 uid="uid://cbemihbxkd4ll"] +[gd_scene load_steps=7 format=3 uid="uid://cbemihbxkd4ll"] [ext_resource type="Script" path="res://content/system/house/house.gd" id="1_p8amj"] [ext_resource type="Script" path="res://content/functions/movable.gd" id="2_w1auk"] [ext_resource type="PackedScene" uid="uid://jls16btb8nko" path="res://content/system/house/align_reference.tscn" id="3_e1tcn"] +[ext_resource type="PackedScene" uid="uid://c8nh8582vwc8u" path="res://content/system/house/doors/doors.tscn" id="4_bb3c2"] [ext_resource type="PackedScene" uid="uid://ds60i5n211hi3" path="res://content/system/house/mini/miniature.tscn" id="4_qjbly"] [sub_resource type="BoxShape3D" id="BoxShape3D_x81up"] @@ -25,6 +26,8 @@ lock_rotation = true [node name="Miniature" parent="Levels" instance=ExtResource("4_qjbly")] +[node name="Doors" parent="Levels" instance=ExtResource("4_bb3c2")] + [node name="AlignReference" parent="." instance=ExtResource("3_e1tcn")] visible = false disabled = true diff --git a/app/content/system/house/mini/miniature.gd b/app/content/system/house/mini/miniature.gd index 8079754..2b8f3f6 100644 --- a/app/content/system/house/mini/miniature.gd +++ b/app/content/system/house/mini/miniature.gd @@ -63,7 +63,15 @@ func _ready(): model.add_child(walls_mesh) model.add_child(floor_mesh) - walls_mesh.mesh=ConstructRoomMesh.generate_wall_mesh_grid(room.corners, room.height) + var doors=[] + + for door in Store.house.state.doors: + if door.room1 == room.name: + doors.append([door.room1_position1, door.room1_position2]) + elif door.room2 == room.name: + doors.append([door.room2_position1, door.room2_position2]) + + walls_mesh.mesh=ConstructRoomMesh.generate_wall_mesh_with_doors_grid(room.corners, room.height, doors) floor_mesh.mesh=ConstructRoomMesh.generate_ceiling_mesh_grid(room.corners) walls_mesh.material_override=wall_material diff --git a/app/content/system/house/room/room.gd b/app/content/system/house/room/room.gd index f2ed2f6..1c67057 100644 --- a/app/content/system/house/room/room.gd +++ b/app/content/system/house/room/room.gd @@ -19,10 +19,7 @@ var editable: bool = false: if !is_inside_tree(): return - if editable: - state_machine.change_to("Edit") - else: - state_machine.change_to("View") + update() func _ready(): Update.props(self, ["editable"]) @@ -30,6 +27,12 @@ func _ready(): func has_point(point: Vector3) -> bool: return get_aabb().has_point(point) +func update(): + if editable: + state_machine.change_to("Edit") + else: + state_machine.change_to("View") + func get_aabb(): var room_store = Store.house.get_room(name) @@ -56,13 +59,26 @@ func get_aabb(): return AABB(to_global(min_pos), to_global(max_pos) - to_global(min_pos)) -static func generate_wall_mesh(room_store: Dictionary): - var corners = room_store.corners - var height = room_store.height +func generate_wall_mesh(): + var room = Store.house.get_room(name) - return ConstructRoomMesh.generate_wall_mesh(corners, height) + if room == null||room.corners.size() < 3: + return null -static func generate_ceiling_mesh(room_store: Dictionary): - var corners = room_store.corners + var corners = room.corners + var height = room.height + var doors = [] + + for door in Store.house.state.doors: + if door.room1 == name: + doors.append([door.room1_position1, door.room1_position2]) + elif door.room2 == name: + doors.append([door.room2_position1, door.room2_position2]) + + # return ConstructRoomMesh.generate_wall_mesh(corners, height) + return ConstructRoomMesh.generate_wall_mesh_with_doors(corners, height, doors) + +static func generate_ceiling_mesh(room: Dictionary): + var corners = room.corners return ConstructRoomMesh.generate_ceiling_mesh(corners) diff --git a/app/content/system/house/room/room.tscn b/app/content/system/house/room/room.tscn index 53f4771..6c46e60 100644 --- a/app/content/system/house/room/room.tscn +++ b/app/content/system/house/room/room.tscn @@ -37,6 +37,9 @@ collision_mask = 0 [node name="CollisionShape3D" type="CollisionShape3D" parent="WallCollision"] +[node name="Clickable" type="Node" parent="WallCollision"] +script = ExtResource("1_ugebq") + [node name="Ceiling" type="StaticBody3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) collision_layer = 24 diff --git a/app/content/system/house/room/states/view.gd b/app/content/system/house/room/states/view.gd index 521cf36..7a9eaf6 100644 --- a/app/content/system/house/room/states/view.gd +++ b/app/content/system/house/room/states/view.gd @@ -5,10 +5,10 @@ const RoomState = preload ("./room_state.gd") func _on_enter(): var room_store = Store.house.get_room(room.name) - if room_store == null||room_store.corners.size() < 3: + if room_store == null: return - room.wall_mesh.mesh = Room.generate_wall_mesh(room_store) + room.wall_mesh.mesh = room.generate_wall_mesh() if room.wall_mesh.mesh == null: return diff --git a/app/content/ui/menu/edit/edit_menu.tscn b/app/content/ui/menu/edit/edit_menu.tscn index aa3c032..5a21003 100644 --- a/app/content/ui/menu/edit/edit_menu.tscn +++ b/app/content/ui/menu/edit/edit_menu.tscn @@ -127,6 +127,7 @@ size = Vector3(0.14, 0.03, 0.01) [node name="Background" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 1.06581e-14, 0, -1.06581e-14, 1, 0.21, -0.16, 0) +visible = false material_override = SubResource("ShaderMaterial_hstwo") mesh = SubResource("QuadMesh_4pj6f") skeleton = NodePath("../..") diff --git a/app/content/ui/menu/menu.tscn b/app/content/ui/menu/menu.tscn index c1c7045..f5e5027 100644 --- a/app/content/ui/menu/menu.tscn +++ b/app/content/ui/menu/menu.tscn @@ -269,7 +269,6 @@ visible = false [node name="EditMenu" parent="AnimationContainer/TabsContent" instance=ExtResource("4_r2raj")] [node name="RoomMenu" parent="AnimationContainer/TabsContent" instance=ExtResource("10_u4i1x")] -visible = false [node name="AutomateMenu" type="Node3D" parent="AnimationContainer/TabsContent"] visible = false diff --git a/app/content/ui/menu/room/room_menu.tscn b/app/content/ui/menu/room/room_menu.tscn index 41831b3..3bd69c7 100644 --- a/app/content/ui/menu/room/room_menu.tscn +++ b/app/content/ui/menu/room/room_menu.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=10 format=3 uid="uid://c01gkeldvjwtr"] +[gd_scene load_steps=11 format=3 uid="uid://c01gkeldvjwtr"] [ext_resource type="Script" path="res://content/ui/menu/room/room_menu.gd" id="1_ch4jb"] [ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="4_cghmp"] @@ -7,6 +7,7 @@ [ext_resource type="Script" path="res://content/ui/components/tabs/tabs_content.gd" id="6_ba00g"] [ext_resource type="PackedScene" uid="uid://bpta22fahai46" path="res://content/ui/menu/room/views/rooms.tscn" id="7_2f8e0"] [ext_resource type="Shader" path="res://content/ui/components/panel/glass.gdshader" id="7_bxp1w"] +[ext_resource type="PackedScene" uid="uid://biwinf4360f10" path="res://content/ui/menu/room/views/doors.tscn" id="7_fl4l8"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_2asng"] render_priority = -3 @@ -46,13 +47,22 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.1, 0, 0) label = "Rooms" size = Vector3(0.06, 0.04, 0.01) +[node name="Doors" parent="Interface/Tabs3D" instance=ExtResource("4_cghmp")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.17, 0, 0) +label = "Doors" +size = Vector3(0.06, 0.04, 0.01) + [node name="TabsContent3D" type="Node3D" parent="Interface" node_paths=PackedStringArray("tabs")] script = ExtResource("6_ba00g") tabs = NodePath("../Tabs3D") [node name="Overview" parent="Interface/TabsContent3D" instance=ExtResource("6_206ad")] +visible = false [node name="Rooms" parent="Interface/TabsContent3D" instance=ExtResource("7_2f8e0")] +visible = false + +[node name="Doors" parent="Interface/TabsContent3D" instance=ExtResource("7_fl4l8")] [node name="Background" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 1.06581e-14, 0, -1.06581e-14, 1, 0.21, -0.16, 0) diff --git a/app/content/ui/menu/room/views/doors.gd b/app/content/ui/menu/room/views/doors.gd new file mode 100644 index 0000000..c355e34 --- /dev/null +++ b/app/content/ui/menu/room/views/doors.gd @@ -0,0 +1,64 @@ +extends Node3D + +const ConstructRoomMesh = preload ("res://lib/utils/mesh/construct_room_mesh.gd") + +const material_selected = preload ("../room_selected.tres") +const material_unselected = preload ("../room_unselected.tres") + +@onready var door_button = $Button +@onready var delete_button = $DeleteButton +@onready var door_label = $Label3D +@onready var rooms_map = $Rooms +@onready var doors_map = $Doors + +var editing_door = R.state(false) + +func _ready(): + rooms_map.selectable.value = false + + var button_icon = R.computed(func(_arg): + if doors_map.selected_door.value == null: + return "add" + elif editing_door.value == false: + return "edit" + else: + return "save" + ) + + R.bind(door_button, "label", button_icon) + + var button_label = R.computed(func(_arg): + if doors_map.selected_door.value == null: + return "Add Door" + elif editing_door.value == false: + return "Edit Door" + else: + return "Save Door" + ) + + R.bind(door_label, "text", button_label) + + R.effect(func(_arg): + delete_button.disabled=doors_map.selected_door.value == null + delete_button.visible=doors_map.selected_door.value != null + ) + + door_button.on_button_up.connect(func(): + if doors_map.selected_door.value == null: + var id=House.body.doors.add() + editing_door.value=true + doors_map.selected_door.value=id + elif editing_door.value == false: + editing_door.value=true + House.body.doors.edit(doors_map.selected_door.value) + else: + House.body.doors.save() + editing_door.value=false + ) + + delete_button.on_button_up.connect(func(): + if doors_map.selected_door.value != null: + House.body.doors.delete(doors_map.selected_door.value) + doors_map.selected_door.value=null + ) + \ No newline at end of file diff --git a/app/content/ui/menu/room/views/doors.tscn b/app/content/ui/menu/room/views/doors.tscn new file mode 100644 index 0000000..d5ce7d6 --- /dev/null +++ b/app/content/ui/menu/room/views/doors.tscn @@ -0,0 +1,66 @@ +[gd_scene load_steps=9 format=3 uid="uid://biwinf4360f10"] + +[ext_resource type="Script" path="res://content/ui/menu/room/views/doors.gd" id="1_22w4h"] +[ext_resource type="Script" path="res://content/ui/menu/room/views/rooms_map.gd" id="2_to21g"] +[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="2_v01ty"] +[ext_resource type="Script" path="res://content/ui/menu/room/views/doors_map.gd" id="3_0k2cc"] +[ext_resource type="Shader" path="res://content/ui/components/panel/glass.gdshader" id="4_eorpn"] + +[sub_resource type="QuadMesh" id="QuadMesh_lkxbm"] +size = Vector2(0.2, 0.2) +orientation = 1 + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_dah0r"] +render_priority = -3 +shader = ExtResource("4_eorpn") +shader_parameter/color = Color(1, 1, 1, 0.3) +shader_parameter/border_color = Color(1, 1, 1, 1) +shader_parameter/edge_color = Color(0, 0, 0, 1) +shader_parameter/size = Vector2(0.42, 0.32) +shader_parameter/border_size = 0.001 +shader_parameter/border_fade_in = 0.005 +shader_parameter/border_fade_out = 0.0 +shader_parameter/corner_radius = 0.02 +shader_parameter/roughness = 0.3 +shader_parameter/grain_amount = 0.02 + +[sub_resource type="QuadMesh" id="QuadMesh_fq44b"] +size = Vector2(0.42, 0.32) + +[node name="Doors" type="Node3D"] +script = ExtResource("1_22w4h") + +[node name="Rooms" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.12, -0.16, 0.005) +script = ExtResource("2_to21g") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Rooms"] +mesh = SubResource("QuadMesh_lkxbm") + +[node name="Doors" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.12, -0.16, 0.006) +script = ExtResource("3_0k2cc") + +[node name="Button" parent="." instance=ExtResource("2_v01ty")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.04, -0.28, 0) +label = "add" +icon = true + +[node name="DeleteButton" parent="." instance=ExtResource("2_v01ty")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2, -0.28, 0) +label = "delete" +icon = true + +[node name="Background" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 1.06581e-14, 0, -1.06581e-14, 1, 0.21, -0.16, 0) +material_override = SubResource("ShaderMaterial_dah0r") +mesh = SubResource("QuadMesh_fq44b") +skeleton = NodePath("../..") + +[node name="Label3D" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.07, -0.28, 0) +pixel_size = 0.001 +text = "Add Door" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 diff --git a/app/content/ui/menu/room/views/doors_map.gd b/app/content/ui/menu/room/views/doors_map.gd new file mode 100644 index 0000000..1bf949a --- /dev/null +++ b/app/content/ui/menu/room/views/doors_map.gd @@ -0,0 +1,87 @@ +extends Node3D + +const BoundingBoxTools = preload ("res://lib/utils/mesh/bounding_box_tools.gd") +const ConstructRoomMesh = preload ("res://lib/utils/mesh/construct_room_mesh.gd") + +const material_selected = preload ("../room_selected.tres") +const material_unselected = preload ("../room_unselected.tres") + +var selectable = R.state(true) +var selected_door = R.state(null) + +func _ready(): + R.effect(func(_arg): + var rooms=Store.house.state.rooms + var doors=Store.house.state.doors + + for old_room in get_children(): + remove_child(old_room) + old_room.queue_free() + + if rooms.size() == 0: + return + + var target_rect=Rect2(0.0, 0.0, 0.2, 0.2) + var rooms_rect=Rect2() + + for room in rooms: + rooms_rect=rooms_rect.merge(BoundingBoxTools.get_bounding_box_2d(room.corners)) + + var box_transform=BoundingBoxTools.resize_bounding_box_2d(rooms_rect, target_rect) + + for door in doors: + var door_points=[ + Vector2(door.room1_position1.x, door.room1_position1.z), + Vector2(door.room1_position2.x, door.room1_position2.z), + Vector2(door.room2_position2.x, door.room2_position2.z), + Vector2(door.room2_position1.x, door.room2_position1.z) + ] + var mesh=ConstructRoomMesh.generate_ceiling_mesh(door_points) + + if mesh == null: + continue + + var body=StaticBody3D.new() + body.name=str(door.id) + body.position.x=box_transform.origin.x + body.position.z=box_transform.origin.y + body.set_collision_layer_value(1, false) + body.set_collision_layer_value(2, true) + + var mesh_instance=MeshInstance3D.new() + mesh_instance.name="Mesh" + mesh_instance.mesh=mesh + mesh_instance.material_override=material_unselected if selected_door.value != door.id else material_selected + + body.add_child(mesh_instance) + + var collision_shape=CollisionShape3D.new() + collision_shape.shape=mesh.create_trimesh_shape() + body.add_child(collision_shape) + + add_child(body) + + var box_scale=box_transform.get_scale() + var min_scale=min(box_scale.x, box_scale.y) + scale=Vector3(min_scale, min_scale, min_scale) + ) + +func _on_click(event: EventPointer): + if selectable.value == false: + return + + var door_id = int(str(event.target.name)) + + if selected_door.value == door_id: + selected_door.value = null + return + + selected_door.value = door_id + +func get_room(door_id): + if door_id == null: + return null + + if has_node(str(door_id)): + return get_node(str(door_id)) + return null diff --git a/app/content/ui/menu/room/views/rooms.gd b/app/content/ui/menu/room/views/rooms.gd index a583489..49c7b10 100644 --- a/app/content/ui/menu/room/views/rooms.gd +++ b/app/content/ui/menu/room/views/rooms.gd @@ -1,162 +1,72 @@ extends Node3D const Room = preload ("res://content/system/house/room/room.tscn") -const ConstructRoomMesh = preload ("res://lib/utils/mesh/construct_room_mesh.gd") - -const material_selected = preload ("../room_selected.tres") -const material_unselected = preload ("../room_unselected.tres") +const RoomsMap = preload ("rooms_map.gd") @onready var room_button = $Button @onready var input = $Input -@onready var rooms_map = $Rooms +@onready var rooms_map: RoomsMap = $Rooms -var selected_room = null: - set(value): - if selected_room != null&&value == null: - room_button.label = "add" - input.text = "Room %s" % (rooms_map.get_child_count() + 1) - - var old_room = get_room(selected_room) - - if old_room != null: - old_room.get_node("MeshInstance3D").material_override = material_unselected - - if value != null: - room_button.label = "edit" - input.text = value - edit_room = false - var new_room = get_room(value) - if new_room != null: - new_room.get_node("MeshInstance3D").material_override = material_selected - - selected_room = value - -var edit_room = false: - set(value): - if value == edit_room: - return - - edit_room = value - if value: - room_button.label = "save" - input.disabled = false - else: - room_button.label = "edit" - input.disabled = true - - if selected_room != null&&selected_room != input.text: - House.body.rename_room(selected_room, input.text) - selected_room = input.text +var editing_room = R.state(false) func _ready(): + + R.effect(func(_arg): + if rooms_map.selected_room.value == null: + room_button.label="add" + elif editing_room.value: + room_button.label="save" + else: + room_button.label="edit" + ) + + R.effect(func(_arg): + input.disabled=editing_room.value == false + ) + + R.effect(func(_arg): + if rooms_map.selected_room.value == null: + var i=1 + while rooms_map.get_room("Room %s" % i) != null: + i += 1 + + input.text="Room %s" % i + else: + input.text=rooms_map.selected_room.value + ) + if !Store.house.is_loaded(): await Store.house.on_loaded - _generate_room_map() - - input.text = "Room %s" % (rooms_map.get_child_count() + 1) - room_button.on_button_down.connect(func(): - if selected_room == null: + var selected_room=rooms_map.selected_room + + if selected_room.value == null: var room_name=input.text - if get_room(room_name) != null: + if rooms_map.get_room(room_name) != null: EventSystem.notify("Name already taken", EventNotify.Type.WARNING) return House.body.create_room(room_name, 0) House.body.edit_room(room_name) - selected_room=room_name - edit_room=true + selected_room.value=room_name + editing_room.value=true + rooms_map.selectable.value=false else: - if edit_room: - edit_room=false + editing_room.value=!editing_room.value + rooms_map.selectable.value=!editing_room.value - if !House.body.is_valid_room(selected_room): + if editing_room.value == false: + if !House.body.is_valid_room(selected_room.value): EventSystem.notify("Room was deleted as it had less than 3 corners.", EventNotify.Type.WARNING) - House.body.delete_room(selected_room) - selected_room=null - else: - House.body.edit_room(null) - _generate_room_map() + House.body.delete_room(selected_room.value) + selected_room.value=null + return + + if selected_room.value != null&&selected_room.value != input.text: + House.body.rename_room(selected_room.value, input.text) + selected_room.value=input.text + + House.body.edit_room(null) else: - edit_room=true - House.body.edit_room(selected_room) + House.body.edit_room(selected_room.value) ) - -func get_room(room_name): - if room_name == null: - return null - - if rooms_map.has_node("%s"% room_name): - return rooms_map.get_node("%s"% room_name) - return null - -func _on_click(event: EventPointer): - if event.target.get_parent() == rooms_map: - var room_name = event.target.name - - if selected_room == room_name: - selected_room = null - House.body.edit_room(null) - return - - selected_room = room_name - -func _generate_room_map(): - var rooms = Store.house.state.rooms - - var target_size = Vector2(0.2, 0.2) - var target_offset = Vector2(0, 0.05) - - for old_room in rooms_map.get_children(): - old_room.queue_free() - await old_room.tree_exited - - if rooms.size() == 0: - return - - if rooms[0].corners.size() == 0: - return - - var current_min = Vector2(rooms[0].corners[0].x, rooms[0].corners[0].y) - var current_max = current_min - - for room in rooms: - for corner in room.corners: - current_min.x = min(current_min.x, corner.x) - current_min.y = min(current_min.y, corner.y) - current_max.x = max(current_max.x, corner.x) - current_max.y = max(current_max.y, corner.y) - - for room in rooms: - var mesh = ConstructRoomMesh.generate_ceiling_mesh(room.corners) - - if mesh == null: - continue - - var body = StaticBody3D.new() - body.name = room.name - - var mesh_instance = MeshInstance3D.new() - mesh_instance.name = "MeshInstance3D" - mesh_instance.mesh = mesh - mesh_instance.material_override = material_unselected if selected_room != room.name else material_selected - body.add_child(mesh_instance) - - var collision_shape = CollisionShape3D.new() - collision_shape.shape = mesh.create_trimesh_shape() - body.add_child(collision_shape) - - rooms_map.add_child(body) - - if current_min == null: - return - - var current_size = current_max - current_min - var target_scale = target_size / current_size - var scale_value = min(target_scale.x, target_scale.y) - - rooms_map.position.x = -current_min.x * scale_value + target_offset.x - rooms_map.position.y = current_min.y * scale_value - target_offset.y - rooms_map.position.z = 0.002 - - rooms_map.scale = Vector3(scale_value, scale_value, scale_value) diff --git a/app/content/ui/menu/room/views/rooms.tscn b/app/content/ui/menu/room/views/rooms.tscn index 5074cea..9b15b75 100644 --- a/app/content/ui/menu/room/views/rooms.tscn +++ b/app/content/ui/menu/room/views/rooms.tscn @@ -1,10 +1,15 @@ -[gd_scene load_steps=7 format=3 uid="uid://bpta22fahai46"] +[gd_scene load_steps=9 format=3 uid="uid://bpta22fahai46"] [ext_resource type="Script" path="res://content/ui/menu/room/views/rooms.gd" id="1_3a1oa"] [ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="1_y3lty"] +[ext_resource type="Script" path="res://content/ui/menu/room/views/rooms_map.gd" id="2_2t24u"] [ext_resource type="PackedScene" uid="uid://blrhy2uccrdn4" path="res://content/ui/components/input/input.tscn" id="2_hstw7"] [ext_resource type="Shader" path="res://content/ui/components/panel/glass.gdshader" id="4_jx6i3"] +[sub_resource type="QuadMesh" id="QuadMesh_ehu5x"] +size = Vector2(0.2, 0.2) +orientation = 1 + [sub_resource type="ShaderMaterial" id="ShaderMaterial_dah0r"] render_priority = -3 shader = ExtResource("4_jx6i3") @@ -26,7 +31,11 @@ size = Vector2(0.42, 0.32) script = ExtResource("1_3a1oa") [node name="Rooms" type="Node3D" parent="."] -transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.14, -0.15, 0) +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.12, -0.16, 0.005) +script = ExtResource("2_2t24u") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Rooms"] +mesh = SubResource("QuadMesh_ehu5x") [node name="Button" parent="." instance=ExtResource("1_y3lty")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2, -0.28, 0) diff --git a/app/content/ui/menu/room/views/rooms_map.gd b/app/content/ui/menu/room/views/rooms_map.gd new file mode 100644 index 0000000..3635f1e --- /dev/null +++ b/app/content/ui/menu/room/views/rooms_map.gd @@ -0,0 +1,86 @@ +extends Node3D + +const BoundingBoxTools = preload ("res://lib/utils/mesh/bounding_box_tools.gd") +const ConstructRoomMesh = preload ("res://lib/utils/mesh/construct_room_mesh.gd") + +const material_selected = preload ("../room_selected.tres") +const material_unselected = preload ("../room_unselected.tres") + +var selectable = R.state(true) +var selected_room = R.state(null) + +func _ready(): + R.effect(func(_arg): + var rooms=Store.house.state.rooms + + for old_room in get_children(): + remove_child(old_room) + old_room.queue_free() + + if rooms.size() == 0: + return + + var target_rect=Rect2(0.0, 0.0, 0.2, 0.2) + var rooms_rect=Rect2() + + for room in rooms: + rooms_rect=rooms_rect.merge(BoundingBoxTools.get_bounding_box_2d(room.corners)) + + var box_transform=BoundingBoxTools.resize_bounding_box_2d(rooms_rect, target_rect) + + for room in rooms: + var mesh=ConstructRoomMesh.generate_ceiling_mesh(room.corners) + + if mesh == null: + continue + + var body=StaticBody3D.new() + body.name=room.name + body.position.x=box_transform.origin.x + body.position.z=box_transform.origin.y + body.set_collision_layer_value(1, false) + body.set_collision_layer_value(2, true) + + var mesh_instance=MeshInstance3D.new() + mesh_instance.name="Mesh" + mesh_instance.mesh=mesh + mesh_instance.material_override=material_unselected if selected_room.value != room.name else material_selected + + body.add_child(mesh_instance) + + var collision_shape=CollisionShape3D.new() + collision_shape.shape=mesh.create_trimesh_shape() + body.add_child(collision_shape) + + add_child(body) + + var box_scale=box_transform.get_scale() + var min_scale=min(box_scale.x, box_scale.y) + scale=Vector3(min_scale, min_scale, min_scale) + ) + +func _on_click(event: EventPointer): + if selectable.value == false: + return + + var previous_room = get_room(selected_room.value) + + if previous_room != null: + previous_room.get_node("Mesh").material_override = material_unselected + + var room_name = event.target.name + + if selected_room.value == room_name: + selected_room.value = null + return + + selected_room.value = room_name + get_room(selected_room.value).get_node("Mesh").material_override = material_selected + +func get_room(room_name): + if room_name == null: + return null + + if has_node(str(room_name)): + return get_node(str(room_name)) + return null diff --git a/app/lib/globals/home_api.gd b/app/lib/globals/home_api.gd index 474af9b..d7155f0 100644 --- a/app/lib/globals/home_api.gd +++ b/app/lib/globals/home_api.gd @@ -36,7 +36,6 @@ func _ready(): var success = Store.settings.load_local() if success: - print(Store.settings) start_adapter(Store.settings.state.type.to_lower(), Store.settings.state.url, Store.settings.state.token) ## Starts the adapter for the given type and url diff --git a/app/lib/stores/house.gd b/app/lib/stores/house.gd index 2f72c3c..902f543 100644 --- a/app/lib/stores/house.gd +++ b/app/lib/stores/house.gd @@ -19,6 +19,15 @@ func _init(): ## room: String ## interface: String "entities": [], + ## Type Door + ## id: int + ## room1: String + ## room2: String + ## room1_position1: Vec3 + ## room1_position2: Vec3 + ## room2_position1: Vec3 + ## room2_position2: Vec3 + "doors": [], "align_position1": Vector3(), "align_position2": Vector3() }) diff --git a/app/lib/utils/mesh/bounding_box_tools.gd b/app/lib/utils/mesh/bounding_box_tools.gd new file mode 100644 index 0000000..1e23ab8 --- /dev/null +++ b/app/lib/utils/mesh/bounding_box_tools.gd @@ -0,0 +1,22 @@ +static func get_bounding_box_2d(points) -> Rect2: + if points.size() == 0: + return Rect2() + + var min_x = points[0].x + var min_y = points[0].y + var max_x = points[0].x + var max_y = points[0].y + + for i in range(1, points.size()): + min_x = min(min_x, points[i].x) + min_y = min(min_y, points[i].y) + max_x = max(max_x, points[i].x) + max_y = max(max_y, points[i].y) + + return Rect2(Vector2(min_x, min_y), Vector2(max_x - min_x, max_y - min_y)) + +static func resize_bounding_box_2d(bbox: Rect2, target_box: Rect2) -> Transform2D: + var scale = Vector2(target_box.size.x / bbox.size.x, target_box.size.y / bbox.size.y) + var offset = target_box.get_center() - bbox.get_center() + + return Transform2D(0, scale, 0, offset) \ No newline at end of file diff --git a/app/lib/utils/mesh/construct_room_mesh.gd b/app/lib/utils/mesh/construct_room_mesh.gd index 2ee9f5f..2ea55e6 100644 --- a/app/lib/utils/mesh/construct_room_mesh.gd +++ b/app/lib/utils/mesh/construct_room_mesh.gd @@ -25,6 +25,84 @@ static func generate_wall_mesh(corners, height): return mesh +## Generate a wall mesh with doors +## corners: Array of Vector2 +## height: float +## doors: Array[Array[Vector3, Vector3]] +static func generate_wall_mesh_with_doors(corners, height, doors): + if corners.size() < 3: + return null + + var mesh = ArrayMesh.new() + + for i in range(0, corners.size()): + var corner = corners[i] + var next_corner = corners[(i + 1) % corners.size()] + + var forward = Vector3(next_corner.x - corner.x, 0, next_corner.y - corner.y).normalized() + + var points := PackedVector2Array() + + points.append(Vector2(0, 0)) + points.append(Vector2(0, height)) + points.append(Vector2(corner.distance_to(next_corner), height)) + points.append(Vector2(corner.distance_to(next_corner), 0)) + + for door in doors: + var door_point1 = Vector2(door[0].x, door[0].z) + var door_point2 = Vector2(door[1].x, door[1].z) + var proj_point1 = Geometry2D.get_closest_point_to_segment_uncapped(door_point1, corner, next_corner) + var proj_point2 = Geometry2D.get_closest_point_to_segment_uncapped(door_point2, corner, next_corner) + + if proj_point1.distance_to(door_point1) > 0.02&&proj_point2.distance_to(door_point2) > 0.02: + continue + + if proj_point1.distance_to(proj_point2) < 0.02: + continue + + var point1_distance = corner.distance_to(proj_point1) + var point2_distance = corner.distance_to(proj_point2) + + var door_points := PackedVector2Array() + + door_points.append(Vector2(point1_distance, -1)) + door_points.append(Vector2(point1_distance, door[0].y)) + door_points.append(Vector2(point2_distance, door[1].y)) + door_points.append(Vector2(point2_distance, -1)) + + var clip = Geometry2D.clip_polygons(points, door_points) + if clip.size() == 0: + continue + + assert(clip.size() != 2, "Door clip should not create holes") + + points = clip[0] + + var edges = PackedInt32Array() + for k in range(points.size()): + edges.append(k) + edges.append((k + 1) % points.size()) + + var cdt: ConstrainedTriangulation = ConstrainedTriangulation.new() + cdt.init(true, true, 0.1) + + cdt.insert_vertices(points) + cdt.insert_edges(edges) + + cdt.erase_outer_triangles() + + points = cdt.get_all_vertices() + var triangles: PackedInt32Array = cdt.get_all_triangles() + + var points_3d = PackedVector3Array() + + for k in range(points.size()): + points_3d.append(Vector3(corner.x, 0, corner.y) + points[k].x * forward + Vector3(0, points[k].y, 0)) + + mesh = _create_mesh_3d(points_3d, triangles, mesh) + + return mesh + static func generate_ceiling_mesh(corners): var points: PackedVector2Array = PackedVector2Array() var edges: PackedInt32Array = PackedInt32Array() @@ -51,7 +129,107 @@ static func generate_ceiling_mesh(corners): points = cdt.get_all_vertices() triangles = cdt.get_all_triangles() - return _create_mesh(points, triangles) + return _create_mesh_2d(points, triangles) + +static func generate_wall_mesh_with_doors_grid(corners, height, doors, grid:=0.1): + if corners.size() < 3: + return null + + var mesh = ArrayMesh.new() + + for i in range(0, corners.size()): + var corner = corners[i] + var next_corner = corners[(i + 1) % corners.size()] + + var forward = Vector3(next_corner.x - corner.x, 0, next_corner.y - corner.y).normalized() + + var points := PackedVector2Array() + + points.append(Vector2(0, 0)) + points.append(Vector2(0, height)) + points.append(Vector2(corner.distance_to(next_corner), height)) + points.append(Vector2(corner.distance_to(next_corner), 0)) + + for door in doors: + var door_point1 = Vector2(door[0].x, door[0].z) + var door_point2 = Vector2(door[1].x, door[1].z) + var proj_point1 = Geometry2D.get_closest_point_to_segment_uncapped(door_point1, corner, next_corner) + var proj_point2 = Geometry2D.get_closest_point_to_segment_uncapped(door_point2, corner, next_corner) + + if proj_point1.distance_to(door_point1) > 0.02&&proj_point2.distance_to(door_point2) > 0.02: + continue + + if proj_point1.distance_to(proj_point2) < 0.02: + continue + + var point1_distance = corner.distance_to(proj_point1) + var point2_distance = corner.distance_to(proj_point2) + + var door_points := PackedVector2Array() + + door_points.append(Vector2(point1_distance, -1)) + door_points.append(Vector2(point1_distance, door[0].y)) + door_points.append(Vector2(point2_distance, door[1].y)) + door_points.append(Vector2(point2_distance, -1)) + + var clip = Geometry2D.clip_polygons(points, door_points) + if clip.size() == 0: + continue + + assert(clip.size() != 2, "Door clip should not create holes") + + points = clip[0] + + # Subdivide edge to grid + + var new_points = PackedVector2Array() + + for k in range(points.size()): + var point = points[k] + var next_point = points[(k + 1) % points.size()] + + new_points.append(point) + + var steps = floor(point.distance_to(next_point) / grid) + + for x in range(1, steps): + new_points.append(point + (next_point - point).normalized() * grid * x) + + points = new_points + + var edges = PackedInt32Array() + for k in range(points.size()): + edges.append(k) + edges.append((k + 1) % points.size()) + + # Subdivide inner polygon to grid + var steps = ceil(Vector2(corner.distance_to(next_corner) / grid, height / grid)) + + for y in range(1, steps.y): + for x in range(1, steps.x): + var point = Vector2(x * grid, y * grid) + + points.append(point) + + var cdt: ConstrainedTriangulation = ConstrainedTriangulation.new() + cdt.init(true, true, 0.001) + + cdt.insert_vertices(points) + cdt.insert_edges(edges) + + cdt.erase_outer_triangles() + + points = cdt.get_all_vertices() + var triangles: PackedInt32Array = cdt.get_all_triangles() + + var points_3d = PackedVector3Array() + + for k in range(points.size()): + points_3d.append(Vector3(corner.x, 0, corner.y) + points[k].x * forward + Vector3(0, points[k].y, 0)) + + mesh = _create_mesh_3d(points_3d, triangles, mesh) + + return mesh static func generate_wall_mesh_grid(corners, height, grid: Vector2=Vector2(0.1, 0.1)): if corners.size() < 3: @@ -186,9 +364,29 @@ static func generate_ceiling_mesh_grid(corners, grid: Vector2=Vector2(0.1, 0.1)) points = cdt.get_all_vertices() triangles = cdt.get_all_triangles() - return _create_mesh(points, triangles) + return _create_mesh_2d(points, triangles) -static func _create_mesh(points: PackedVector2Array, triangles: PackedInt32Array): +static func _create_mesh_3d(points: PackedVector3Array, triangles: PackedInt32Array, existing=null): + var st = SurfaceTool.new() + + st.begin(Mesh.PRIMITIVE_TRIANGLES) + + for i in range(points.size()): + st.add_vertex(Vector3(points[i].x, points[i].y, points[i].z)) + + for i in range(triangles.size()): + st.add_index(triangles[i]) + + st.index() + st.generate_normals() + st.generate_tangents() + + if existing != null: + return st.commit(existing) + + return st.commit() + +static func _create_mesh_2d(points: PackedVector2Array, triangles: PackedInt32Array): var st = SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) diff --git a/app/lib/utils/variant_serializer.gd b/app/lib/utils/variant_serializer.gd index 636ee7d..3b2cf83 100644 --- a/app/lib/utils/variant_serializer.gd +++ b/app/lib/utils/variant_serializer.gd @@ -14,6 +14,8 @@ static func stringify_value(value): ) TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_NIL: return value + TYPE_STRING_NAME: + return str(value) TYPE_VECTOR2: return { "x": value.x,