diff --git a/content/entities/button/button.gd b/content/entities/button/button.gd index 693355e..9e5713b 100644 --- a/content/entities/button/button.gd +++ b/content/entities/button/button.gd @@ -1,9 +1,12 @@ -extends Node3D +extends Entity + +const Entity = preload("../entity.gd") -var entity_id = "button.plug_printer_2" @onready var button = $Button func _ready(): + super() + var stateInfo = await HomeApi.get_state(entity_id) if stateInfo == null: @@ -29,9 +32,3 @@ func set_state(state): else: button.icon = false button.label = name - -func _save(): - return { - "transform": transform, - "entity_id": entity_id - } diff --git a/content/entities/camera/camera.gd b/content/entities/camera/camera.gd index 21f9b9e..1e8b08e 100644 --- a/content/entities/camera/camera.gd +++ b/content/entities/camera/camera.gd @@ -1,6 +1,7 @@ -extends StaticBody3D +extends Entity + +const Entity = preload("../entity.gd") -@export var entity_id = "camera.bedroomspeaker" @export var view_width = 0.15 @onready var view = $View @@ -10,6 +11,8 @@ extends StaticBody3D # Called when the node enters the scene tree for the first time. func _ready(): + super() + var stateInfo = await HomeApi.get_state(entity_id) set_state(stateInfo) @@ -56,10 +59,4 @@ func load_image(url: String): var texture = ImageTexture.create_from_image(image) view.texture = texture view.pixel_size = pixel_size - mesh.visible = false - -func _save(): - return { - "transform": transform, - "entity_id": entity_id - } + mesh.visible = false \ No newline at end of file diff --git a/content/entities/entity.gd b/content/entities/entity.gd new file mode 100644 index 0000000..04d861d --- /dev/null +++ b/content/entities/entity.gd @@ -0,0 +1,11 @@ +extends StaticBody3D + +var entity_id: String + +func _ready(): + var movable = get_node("Movable") + + if movable: + movable.on_moved.connect(func(): + House.body.save_all_entities() + ) diff --git a/content/entities/light/light.gd b/content/entities/light/light.gd index c1bdeb2..5406ee4 100644 --- a/content/entities/light/light.gd +++ b/content/entities/light/light.gd @@ -1,6 +1,7 @@ -extends StaticBody3D +extends Entity + +const Entity = preload("../entity.gd") -@export var entity_id = "switch.plug_printer_2" @export var color_off = Color(0.23, 0.23, 0.23) @export var color_on = Color(1.0, 0.85, 0.0) @@ -12,6 +13,8 @@ var brightness = 0 # 0-255 # Called when the node enters the scene tree for the first time. func _ready(): + super() + var stateInfo = await HomeApi.get_state(entity_id) set_state(stateInfo["state"] == "on") @@ -55,10 +58,4 @@ func _on_click(event): attributes["brightness"] = int(brightness) HomeApi.set_state(entity_id, "on" if !state else "off", attributes) - set_state(!state, brightness) - -func _save(): - return { - "transform": transform, - "entity_id": entity_id - } \ No newline at end of file + set_state(!state, brightness) \ No newline at end of file diff --git a/content/entities/media_player/media_player.gd b/content/entities/media_player/media_player.gd index ea1a707..5efaa9c 100644 --- a/content/entities/media_player/media_player.gd +++ b/content/entities/media_player/media_player.gd @@ -1,6 +1,7 @@ -extends StaticBody3D +extends Entity + +const Entity = preload("../entity.gd") -@export var entity_id = "media_player.bedroomspeaker" @export var image_width = 0.15 @onready var previous = $Previous @@ -18,6 +19,8 @@ var volume = 50 # Called when the node enters the scene tree for the first time. func _ready(): + super() + var stateInfo = await HomeApi.get_state(entity_id) set_state(stateInfo) @@ -85,10 +88,4 @@ func load_image(url: String): var texture = ImageTexture.create_from_image(image) logo.texture = texture - logo.pixel_size = pixel_size - -func _save(): - return { - "transform": transform, - "entity_id": entity_id - } \ No newline at end of file + logo.pixel_size = pixel_size \ No newline at end of file diff --git a/content/entities/sensor/sensor.gd b/content/entities/sensor/sensor.gd index 78fd781..ac22e43 100644 --- a/content/entities/sensor/sensor.gd +++ b/content/entities/sensor/sensor.gd @@ -1,10 +1,13 @@ -extends StaticBody3D +extends Entity + +const Entity = preload("../entity.gd") -@export var entity_id = "sensor.sun_next_dawn" @onready var label: Label3D = $Label # Called when the node enters the scene tree for the first time. func _ready(): + super() + var stateInfo = await HomeApi.get_state(entity_id) set_text(stateInfo) @@ -21,10 +24,4 @@ func set_text(stateInfo): if stateInfo["attributes"].has("unit_of_measurement") && stateInfo["attributes"]["unit_of_measurement"] != null: text += " " + stateInfo["attributes"]["unit_of_measurement"] - label.text = text - -func _save(): - return { - "transform": transform, - "entity_id": entity_id - } \ No newline at end of file + label.text = text \ No newline at end of file diff --git a/content/entities/switch/switch.gd b/content/entities/switch/switch.gd index 1322d78..f91098b 100644 --- a/content/entities/switch/switch.gd +++ b/content/entities/switch/switch.gd @@ -1,10 +1,13 @@ -extends StaticBody3D +extends Entity + +const Entity = preload("../entity.gd") -@export var entity_id = "switch.plug_printer_2" @onready var sprite: AnimatedSprite3D = $Icon # Called when the node enters the scene tree for the first time. func _ready(): + super() + var stateInfo = await HomeApi.get_state(entity_id) if stateInfo == null: return @@ -30,10 +33,4 @@ func _on_click(event): sprite.set_frame(0) func _on_request_completed(): - pass - -func _save(): - return { - "transform": transform, - "entity_id": entity_id - } \ No newline at end of file + pass \ No newline at end of file diff --git a/content/functions/movable.gd b/content/functions/movable.gd index 46ad7ad..1937512 100644 --- a/content/functions/movable.gd +++ b/content/functions/movable.gd @@ -3,6 +3,7 @@ extends Function class_name Movable signal on_move(position: Vector3, rotation: Vector3) +signal on_moved() @export var restricted: bool = false @export var restrict_movement: Callable @@ -33,6 +34,7 @@ func _on_grab_move(_event: EventPointer): func _on_grab_up(event: EventPointer): event.initiator.node.remove_child(hit_node) + on_moved.emit() func _get_configuration_warnings() -> PackedStringArray: var warnings := PackedStringArray() diff --git a/content/system/house/align_reference.gd b/content/system/house/align_reference.gd index 92ae08b..204d810 100644 --- a/content/system/house/align_reference.gd +++ b/content/system/house/align_reference.gd @@ -44,3 +44,12 @@ func get_new_transform(old_transform: Transform3D): marker.scale = Vector3(1, 1, 1) return marker.global_transform +func update_align_reference(): + corner1.global_position = Store.house.align_position1 + corner2.global_position = Store.house.align_position2 + + edge.align_to_corners(corner1.global_position, corner2.global_position) + +func update_store(): + Store.house.align_position1 = corner1.global_position + Store.house.align_position2 = corner2.global_position \ No newline at end of file diff --git a/content/system/house/house.gd b/content/system/house/house.gd index e5fb4e9..36678c2 100644 --- a/content/system/house/house.gd +++ b/content/system/house/house.gd @@ -16,21 +16,47 @@ var mini_view: bool = false: var target_size: float = 1.0 +func _ready(): + Store.house.on_loaded.connect(func(): + update_house() + ) + func _physics_process(delta): levels.scale.x = lerp(levels.scale.x, target_size, 10.0 * delta) levels.scale.y = lerp(levels.scale.y, target_size, 10.0 * delta) levels.scale.z = lerp(levels.scale.z, target_size, 10.0 * delta) +func update_house(): + for old_room in get_rooms(0): + old_room.queue_free() + await old_room.tree_exited + + align_reference.update_align_reference() + + for index in range(Store.house.rooms.size()): + var new_room = Store.house.rooms[index] + create_room(new_room.name, 0) + + for entity in Store.house.entities: + var entity_instance = create_entity_in(entity.id, entity.room) + + if entity_instance == null: + continue + + entity_instance.global_rotation = entity.rotation func create_room(room_name: String, level: int) -> RoomType: - if editing_room != null: - editing_room.editable = false - editing_room = null + var existing_room = Store.house.get_room(room_name) + + if existing_room == null: + Store.house.rooms.append({ + "name": room_name, + "height": 2.0, + "corners": [], + }) var room = Room.instantiate() room.name = room_name - room.editable = true - editing_room = room get_level(level).add_child(room) @@ -70,22 +96,21 @@ func delete_room(room_name): room.get_parent().remove_child(room) room.queue_free() + await room.tree_exited func is_editiong(room_name): return editing_room != null && editing_room.name == room_name func find_room(room_name): - for level in levels.get_children(): - for room in level.get_children(): - if room.name == room_name: - return room + for room in get_rooms(0): + if room.name == room_name: + return room return null func find_room_at(entity_position: Vector3): - for level in levels.get_children(): - for room in level.get_children(): - if room.has_point(entity_position): - return room + for room in get_rooms(0): + if room.has_point(entity_position): + return room return null func get_level(level: int): @@ -120,16 +145,38 @@ func create_entity(entity_id: String, entity_position: Vector3): var room = find_room_at(entity_position) if room == null: - return + return null var entity = EntityFactory.create_entity(entity_id) if entity == null: - return + return null - room.add_child(entity) + room.get_node("Entities").add_child(entity) entity.global_position = entity_position + save_all_entities() + + return entity + +func create_entity_in(entity_id: String, room_name: String): + var room = find_room(room_name) + + if room == null: + return null + + var entity = EntityFactory.create_entity(entity_id) + + if entity == null: + return null + + room.get_node("Entities").add_child(entity) + entity.global_position = room.get_aabb().position + room.get_aabb().size / 2.0 + + save_all_entities() + + return entity + func update_mini_view(): collision_shape.disabled = !mini_view @@ -165,4 +212,23 @@ func save_reference(): align_reference.global_transform = align_transform align_reference.disabled = true - align_reference.update_initial_positions() \ No newline at end of file + align_reference.update_initial_positions() + + align_reference.update_store() + Store.house.save_local() + +func save_all_entities(): + Store.house.entities.clear() + + for room in get_rooms(0): + for entity in room.get_node("Entities").get_children(): + var entity_data = { + "id": entity.entity_id, + "position": entity.global_position, + "rotation": entity.global_rotation, + "room": String(room.name) + } + + Store.house.entities.append(entity_data) + + Store.house.save_local() diff --git a/content/system/house/room/room.gd b/content/system/house/room/room.gd index ea0b908..ec9e131 100644 --- a/content/system/house/room/room.gd +++ b/content/system/house/room/room.gd @@ -20,34 +20,103 @@ var editable: bool = false: else: state_machine.change_to("View") -func get_corner(index: int) -> MeshInstance3D: - return wall_corners.get_child(index % wall_corners.get_child_count()) - -func get_edge(index: int) -> MeshInstance3D: - return wall_edges.get_child(index % wall_edges.get_child_count()) - func has_point(point: Vector3) -> bool: return get_aabb().has_point(point) -func remove_corner(index: int): - get_corner(index).queue_free() - get_edge(index).queue_free() - func get_aabb(): - if wall_corners.get_child_count() == 0: + var room_store = Store.house.get_room(name) + + var corners = room_store.corners + + if corners.size() == 0: return AABB() - var min_pos = wall_corners.get_child(0).position - var max_pos = wall_corners.get_child(0).position + var min_pos = Vector3(corners[0].x, 0, corners[0].y) + var max_pos = min_pos - for corner in wall_corners.get_children(): - min_pos.x = min(min_pos.x, corner.position.x) - min_pos.z = min(min_pos.z, corner.position.z) + for corner in corners: + min_pos.x = min(min_pos.x, corner.x) + min_pos.z = min(min_pos.z, corner.y) - max_pos.x = max(max_pos.x, corner.position.x) - max_pos.z = max(max_pos.z, corner.position.z) + max_pos.x = max(max_pos.x, corner.x) + max_pos.z = max(max_pos.z, corner.y) - min_pos.y = room_floor.position.y - max_pos.y = room_ceiling.position.y + min_pos.y = 0 + max_pos.y = room_store.height + + return AABB(to_global(min_pos), to_global(max_pos) - to_global(min_pos)) + +static func generate_wall_mesh(room_store: Dictionary): + if room_store.corners.size() < 2: + return null + + var st = SurfaceTool.new() + var wall_up = Vector3.UP * room_store.height + + st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP) + + for corner in room_store.corners: + var corner3D = Vector3(corner.x, 0, corner.y) + + st.add_vertex(corner3D) + st.add_vertex(corner3D + wall_up) + + var first_corner = Vector3(room_store.corners[0].x, 0, room_store.corners[0].y) + + st.add_vertex(first_corner) + st.add_vertex(first_corner + wall_up) + + st.index() + st.generate_normals() + st.generate_tangents() + var mesh = st.commit() + + return mesh + +static func generate_ceiling_mesh(room_store: Dictionary): + + var points: PackedVector2Array = PackedVector2Array() + var edges: PackedInt32Array = PackedInt32Array() + var triangles: PackedInt32Array + + var corners = room_store.corners + + if corners.size() < 3: + return null + + for i in range(corners.size()): + var corner = corners[i] + points.append(Vector2(corner.x, corner.y)) + edges.append(i) + edges.append((i + 1) % corners.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() + triangles = cdt.get_all_triangles() + + var st = SurfaceTool.new() + + st.begin(Mesh.PRIMITIVE_TRIANGLES) + + for i in range(points.size()): + st.add_vertex(Vector3(points[i].x, 0, points[i].y)) + + for i in range(triangles.size()): + st.add_index(triangles[i]) + + st.index() + st.generate_normals() + st.generate_tangents() + + var mesh = st.commit() + + return mesh - return AABB(to_global(min_pos), to_global(max_pos) - to_global(min_pos)) \ No newline at end of file diff --git a/content/system/house/room/room.tscn b/content/system/house/room/room.tscn index bd0cedd..0c89780 100644 --- a/content/system/house/room/room.tscn +++ b/content/system/house/room/room.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=11 format=3 uid="uid://bswgmclohuqui"] +[gd_scene load_steps=10 format=3 uid="uid://bswgmclohuqui"] [ext_resource type="Script" path="res://content/system/house/room/room.gd" id="1_fccq0"] [ext_resource type="Script" path="res://content/functions/clickable.gd" id="1_ugebq"] @@ -10,8 +10,6 @@ [sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_08sv0"] -[sub_resource type="ArrayMesh" id="ArrayMesh_7dibq"] - [sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_ap613"] plane = Plane(0, -1, 0, 0) @@ -30,7 +28,6 @@ script = ExtResource("1_ugebq") [node name="WallMesh" type="MeshInstance3D" parent="."] material_override = ExtResource("3_al1ev") -mesh = SubResource("ArrayMesh_7dibq") [node name="CeilingMesh" type="MeshInstance3D" parent="."] material_override = ExtResource("3_al1ev") @@ -65,3 +62,5 @@ script = ExtResource("7_ap14h") [node name="Mini" type="Node" parent="StateMachine"] script = ExtResource("6_g4qca") + +[node name="Entities" type="Node3D" parent="."] diff --git a/content/system/house/room/states/edit.gd b/content/system/house/room/states/edit.gd index 05dd188..edb3c97 100644 --- a/content/system/house/room/states/edit.gd +++ b/content/system/house/room/states/edit.gd @@ -11,13 +11,21 @@ var height_corner: StaticBody3D = null var height_edge: StaticBody3D = null func _on_enter(): - room.wall_corners.visible = true - room.wall_edges.visible = true + var room_store = Store.house.get_room(room.name) - if floor_corner != null: - floor_corner.visible = true - height_corner.visible = true - height_edge.visible = true + if room_store == null: + return + + var corners = room_store.corners + + if corners.size() > 0: + add_floor_corner(Vector3(corners[0].x, 0, corners[0].y)) + add_height_corner(Vector3(corners[0].x, 0, corners[0].y)) + room.room_ceiling.position.y = room_store.height + height_edge.align_to_corners(floor_corner.global_position, height_corner.global_position) + + for i in range(1, corners.size()): + add_corner(Vector3(corners[i].x, 0, corners[i].y)) room.room_ceiling.get_node("CollisionShape3D").disabled = (floor_corner == null && height_corner == null) room.room_floor.get_node("CollisionShape3D").disabled = false @@ -32,13 +40,21 @@ func _on_enter(): room.room_floor.get_node("Clickable").on_click.connect(_on_click_floor) func _on_leave(): - room.wall_corners.visible = false - room.wall_edges.visible = false + update_store() + + for child in room.wall_corners.get_children(): + child.queue_free() + await child.tree_exited + + for child in room.wall_edges.get_children(): + child.queue_free() + await child.tree_exited if floor_corner != null: - floor_corner.visible = false - height_corner.visible = false - height_edge.visible = false + floor_corner.queue_free() + await floor_corner.tree_exited + height_edge.queue_free() + await height_edge.tree_exited room.room_ceiling.get_node("CollisionShape3D").disabled = true room.room_floor.get_node("CollisionShape3D").disabled = true @@ -46,6 +62,16 @@ func _on_leave(): room.room_ceiling.get_node("Clickable").on_click.disconnect(_on_click_ceiling) room.room_floor.get_node("Clickable").on_click.disconnect(_on_click_floor) +func get_corner(index: int) -> MeshInstance3D: + return room.wall_corners.get_child(index % room.wall_corners.get_child_count()) + +func get_edge(index: int) -> MeshInstance3D: + return room.wall_edges.get_child(index % room.wall_edges.get_child_count()) + +func remove_corner(index: int): + get_corner(index).queue_free() + get_edge(index).queue_free() + func _on_click_floor(event): if floor_corner != null && height_corner != null: return @@ -68,7 +94,7 @@ func add_floor_corner(position: Vector3): floor_corner.position = position height_edge = wall_edge_scene.instantiate() - height_edge.align_to_corners(position, position + Vector3.UP * room.room_ceiling.global_position.y) + height_edge.align_to_corners(position, position + Vector3.UP * room.room_ceiling.position.y) floor_corner.get_node("Clickable").on_grab_down.connect(func(event): if !is_active() || moving != null: @@ -92,14 +118,14 @@ func add_floor_corner(position: Vector3): height_edge.align_to_corners(new_position, new_position + Vector3.UP * room.room_ceiling.global_position.y) - room.get_corner(moving_index).position.x = new_position.x - room.get_corner(moving_index).position.z = new_position.z + get_corner(moving_index).position.x = new_position.x + get_corner(moving_index).position.z = new_position.z if room.wall_edges.get_child_count() == 0: return - room.get_edge(moving_index).align_to_corners(new_position, room.get_corner(moving_index + 1).position) - room.get_edge(moving_index - 1).align_to_corners(room.get_corner(moving_index - 1).position, new_position) + get_edge(moving_index).align_to_corners(new_position, get_corner(moving_index + 1).position) + get_edge(moving_index - 1).align_to_corners(get_corner(moving_index - 1).position, new_position) ) floor_corner.get_node("Clickable").on_grab_up.connect(func(_event): @@ -174,13 +200,13 @@ func add_corner(position: Vector3, index: int = -1): new_position = event.ray.global_position + direction - room.get_corner(moving_index).global_position = new_position + get_corner(moving_index).global_position = new_position if room.wall_edges.get_child_count() == 0: return - room.get_edge(moving_index).align_to_corners(room.get_corner(moving_index - 1).position, room.get_corner(moving_index + 1).position) - room.get_edge(moving_index - 1).transform = room.get_edge(moving_index).transform + get_edge(moving_index).align_to_corners(get_corner(moving_index - 1).position, get_corner(moving_index + 1).position) + get_edge(moving_index - 1).transform = get_edge(moving_index).transform return @@ -194,14 +220,14 @@ func add_corner(position: Vector3, index: int = -1): if room.wall_edges.get_child_count() == 0: return - room.get_edge(moving_index).align_to_corners(new_position, room.get_corner(moving_index + 1).position) - room.get_edge(moving_index - 1).align_to_corners(room.get_corner(moving_index - 1).position, new_position) + get_edge(moving_index).align_to_corners(new_position, get_corner(moving_index + 1).position) + get_edge(moving_index - 1).align_to_corners(get_corner(moving_index - 1).position, new_position) ) corner.get_node("Clickable").on_grab_up.connect(func(_event): if deleting: var moving_index = moving.get_index() - room.remove_corner(moving_index) + remove_corner(moving_index) moving = null deleting = false @@ -213,13 +239,13 @@ func add_corner(position: Vector3, index: int = -1): var num_corners = room.wall_corners.get_child_count() if num_corners > 1: - add_edge(position, room.get_corner(index + 1).position, index) + add_edge(position, get_corner(index + 1).position, index) if num_corners > 2: if num_corners != room.wall_edges.get_child_count(): - add_edge(room.get_corner(-2).position, room.get_corner(-1).position, -2) + add_edge(get_corner(-2).position, get_corner(-1).position, -2) else: - room.get_edge(index - 1).align_to_corners(room.get_corner(index - 1).position, position) + get_edge(index - 1).align_to_corners(get_corner(index - 1).position, position) func add_edge(from_pos: Vector3, to_pos: Vector3, index: int = -1): @@ -235,4 +261,20 @@ func add_edge(from_pos: Vector3, to_pos: Vector3, index: int = -1): room.wall_edges.add_child(edge) room.wall_edges.move_child(edge, index) - return edge \ No newline at end of file + return edge + +func update_store(): + var store_room = Store.house.get_room(room.name) + + var corners = [] + + for corner in room.wall_corners.get_children(): + corners.append(Vector2(corner.position.x, corner.position.z)) + + store_room.corners = corners + store_room.height = room.room_ceiling.position.y + + if corners.size() < 3: + House.body.delete_room(room.name) + + Store.house.save_local() diff --git a/content/system/house/room/states/view.gd b/content/system/house/room/states/view.gd index 06e4715..441c3ac 100644 --- a/content/system/house/room/states/view.gd +++ b/content/system/house/room/states/view.gd @@ -2,20 +2,17 @@ extends RoomState const RoomState = preload("./room_state.gd") -var room_height = 3 -var corner_count = 0 func _on_enter(): - corner_count = room.wall_corners.get_child_count() + var room_store = Store.house.get_room(room.name) - if corner_count < 3: + if room_store == null || room_store.corners.size() < 3: return - room_height = room.room_ceiling.position.y - room.wall_mesh.visible = true room.ceiling_mesh.visible = true - room.wall_mesh.mesh = generate_mesh() + + room.wall_mesh.mesh = Room.generate_wall_mesh(room_store) if room.wall_mesh.mesh == null: return @@ -26,7 +23,7 @@ func _on_enter(): ceiling_shape.disabled = false floor_shape.disabled = false - room.ceiling_mesh.mesh = generate_ceiling_mesh() + room.ceiling_mesh.mesh = Room.generate_ceiling_mesh(room_store) ceiling_shape.shape = room.ceiling_mesh.mesh.create_trimesh_shape() floor_shape.shape = room.ceiling_mesh.mesh.create_trimesh_shape() ceiling_shape.shape.backface_collision = true @@ -52,87 +49,28 @@ func _on_leave(): for collision in room.wall_collisions.get_children(): collision.queue_free() - -func generate_mesh(): - var st = SurfaceTool.new() - var wall_up = Vector3.UP * room_height - - st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP) - - for i in range(corner_count): - var corner = room.get_corner(i) - - st.add_vertex(corner.position) - st.add_vertex(corner.position + wall_up) - - var first_corner = room.get_corner(0) - - st.add_vertex(first_corner.position) - st.add_vertex(first_corner.position + wall_up) - - st.index() - st.generate_normals() - st.generate_tangents() - var mesh = st.commit() - - return mesh - -func generate_ceiling_mesh(): - var points: PackedVector2Array = PackedVector2Array() - var edges: PackedInt32Array = PackedInt32Array() - var triangles: PackedInt32Array - - for i in range(corner_count): - var corner = room.get_corner(i) - points.append(Vector2(corner.position.x, corner.position.z)) - edges.append(i) - edges.append((i + 1) % corner_count) - - 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() - triangles = cdt.get_all_triangles() - - var st = SurfaceTool.new() - - st.begin(Mesh.PRIMITIVE_TRIANGLES) - - for i in range(points.size()): - st.add_vertex(Vector3(points[i].x, 0, points[i].y)) - - for i in range(triangles.size()): - st.add_index(triangles[i]) - - st.index() - st.generate_normals() - st.generate_tangents() - - var mesh = st.commit() - - return mesh + await collision.tree_exited func generate_collision(): + var room_store = Store.house.get_room(room.name) + var collision_shapes: Array[CollisionShape3D] = [] - for i in range(corner_count): - var corner = room.get_corner(i) - var next_corner = room.get_corner(i + 1) + var corners = room_store.corners + + for i in range(corners.size()): + var corner = Vector3(corners[i].x, 0, corners[i].y) + var next_corner_index = (i + 1) % corners.size() + var next_corner = Vector3(corners[next_corner_index].x, 0, corners[next_corner_index].y) var shape = BoxShape3D.new() - shape.size = Vector3((next_corner.position - corner.position).length(), room_height, 0.04) + shape.size = Vector3((next_corner - corner).length(), room_store.height, 0.04) var transform = Transform3D() - var back_vector = (corner.position - next_corner.position).cross(Vector3.UP).normalized() * shape.size.z / 2 + var back_vector = (corner - next_corner).cross(Vector3.UP).normalized() * shape.size.z / 2 - transform.basis = Basis((next_corner.position - corner.position).normalized(), Vector3.UP, back_vector.normalized()) - transform.origin = corner.position + (next_corner.position - corner.position) / 2 + back_vector + Vector3.UP * shape.size.y / 2 + transform.basis = Basis((next_corner - corner).normalized(), Vector3.UP, back_vector.normalized()) + transform.origin = corner + (next_corner - corner) / 2 + back_vector + Vector3.UP * shape.size.y / 2 var collision_shape = CollisionShape3D.new() diff --git a/content/ui/menu/edit/edit_menu.gd b/content/ui/menu/edit/edit_menu.gd index 247e717..6db33e9 100644 --- a/content/ui/menu/edit/edit_menu.gd +++ b/content/ui/menu/edit/edit_menu.gd @@ -146,7 +146,10 @@ func _on_entity_click(entity_name): AudioPlayer.play_effect("spawn") - House.body.create_entity(entity_name, global_position) + var entity = House.body.create_entity(entity_name, global_position) + + if entity == null: + EventSystem.notify("Entity is not in Room", EventNotify.Type.INFO) func clear_menu(): for child in devices_node.get_children(): diff --git a/content/ui/menu/room/views/rooms.gd b/content/ui/menu/room/views/rooms.gd index 5efc268..535fa4d 100644 --- a/content/ui/menu/room/views/rooms.gd +++ b/content/ui/menu/room/views/rooms.gd @@ -41,6 +41,9 @@ var edit_room = false: room_button.label = "edit" func _ready(): + Store.house.on_loaded.connect(func(): + _generate_room_map() + ) room_button.on_button_down.connect(func(): if selected_room == null: @@ -50,6 +53,7 @@ func _ready(): return House.body.create_room(room_name, 0) + House.body.edit_room(room_name) selected_room = room_name edit_room = true else: @@ -84,26 +88,35 @@ func _on_click(event: EventPointer): selected_room = room_name func _generate_room_map(): - var rooms = House.body.get_rooms(0) + var rooms = Store.house.rooms - var target_size = Vector3(0.3, 1, 0.24) + var target_size = Vector2(0.3, 0.24) for old_room in rooms_map.get_children(): old_room.queue_free() await old_room.tree_exited - var aabb = House.body.get_level_aabb(0) - var current_min = aabb.position - var current_max = aabb.position + aabb.size + if rooms.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: - var mesh = room.ceiling_mesh.mesh + 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 = RoomType.generate_ceiling_mesh(room) + if mesh == null: continue var body = StaticBody3D.new() body.name = room.name - var mesh_instance = MeshInstance3D.new() mesh_instance.name = "MeshInstance3D" @@ -122,9 +135,9 @@ func _generate_room_map(): var current_size = current_max - current_min var target_scale = target_size / current_size - var scale_value = min(target_scale.x, target_scale.z) + var scale_value = min(target_scale.x, target_scale.y) rooms_map.position.x = -current_min.x * scale_value - rooms_map.position.z = -current_min.z * scale_value + rooms_map.position.z = -current_min.y * scale_value rooms_map.scale = Vector3(scale_value, scale_value, scale_value) diff --git a/content/ui/menu/settings/settings_menu.gd b/content/ui/menu/settings/settings_menu.gd index 08cba1f..9ddc2a0 100644 --- a/content/ui/menu/settings/settings_menu.gd +++ b/content/ui/menu/settings/settings_menu.gd @@ -27,39 +27,37 @@ func _ready(): ) credits.on_click.connect(func(_event): - print("_active_controller") var credits_instance = credits_scene.instantiate() get_tree().root.add_child(credits_instance) var label = $Content/Credits/Label credits_instance.global_position = + label.to_global(label.position + Vector3(0.1, 0, -0.15)) ) - var config = ConfigData.load_config() - - if config.has("url"): - input_url.text = config["url"] - if config.has("token"): - input_token.text = config["token"] + Store.settings.on_loaded.connect(func(): + input_url.text = Store.settings.url + input_token.text = Store.settings.token + ) button_connect.on_button_down.connect(func(): - var url = input_url.text + "/api/websocket" + var url = input_url.text var token = input_token.text HomeApi.start_adapter("hass_ws", url, token) - ConfigData.save_config({ - "api_type": "hass_ws", - "url": input_url.text, - "token": input_token.text - }) + Store.settings.url = url + Store.settings.token = token + + Store.settings.save_local() ) save.on_button_down.connect(func(): - SaveSystem.save() + House.body.save_all_entities() + Store.house.save_local() ) clear_save.on_button_down.connect(func(): - SaveSystem.clear() + Store.house.clear() + House.body.update_house() ) HomeApi.on_connect.connect(func(): diff --git a/lib/globals/config_data.gd b/lib/globals/config_data.gd deleted file mode 100644 index 95706cf..0000000 --- a/lib/globals/config_data.gd +++ /dev/null @@ -1,25 +0,0 @@ -extends Node - -const VariantSerializer = preload("res://lib/utils/variant_serializer.gd") - -var file_url: String = "user://config.cfg" - -func save_config(data: Dictionary): - var file := FileAccess.open(file_url, FileAccess.WRITE) - - if file == null: - return - - var json_data := JSON.stringify(VariantSerializer.stringify_value(data)) - file.store_string(json_data) - -func load_config(): - var file := FileAccess.open(file_url, FileAccess.READ) - - if file == null: - return {} - - var json_data := file.get_as_text() - var data = VariantSerializer.parse_value(JSON.parse_string(json_data)) - - return data \ No newline at end of file diff --git a/lib/globals/home_api.gd b/lib/globals/home_api.gd index 81801da..c922e5a 100644 --- a/lib/globals/home_api.gd +++ b/lib/globals/home_api.gd @@ -24,14 +24,10 @@ var api: Node func _ready(): print("HomeApi ready") - var config = ConfigData.load_config() + var success = Store.settings.load_local() - if config.has("api_type") && config.has("url") && config.has("token"): - var type = config["api_type"] - var url = config["url"] + "/api/websocket" - var token = config["token"] - - start_adapter(type, url, token) + if success: + start_adapter(Store.settings.type.to_lower(), Store.settings.url, Store.settings.token) func start_adapter(type: String, url: String, token: String): @@ -47,7 +43,7 @@ func start_adapter(type: String, url: String, token: String): add_child(api) api.on_connect.connect(func(): - SaveSystem.load() + Store.house.load_local() on_connect.emit() ) @@ -96,5 +92,5 @@ func watch_state(entity: String, callback: Callable): func _notification(what): if what == NOTIFICATION_WM_CLOSE_REQUEST || what == NOTIFICATION_WM_GO_BACK_REQUEST: - # SaveSystem.save() - pass \ No newline at end of file + # Store.house.save_local() + pass diff --git a/lib/globals/main_store.gd b/lib/globals/main_store.gd new file mode 100644 index 0000000..3d822ea --- /dev/null +++ b/lib/globals/main_store.gd @@ -0,0 +1,9 @@ +extends Node + +const SettingsStore = preload("res://lib/stores/settings.gd") +const HouseStore = preload("res://lib/stores/house.gd") +const DevicesStore = preload("res://lib/stores/devices.gd") + +var settings = SettingsStore.new() +var house = HouseStore.new() +var devices = DevicesStore.new() diff --git a/lib/globals/save_system.gd b/lib/globals/save_system.gd deleted file mode 100644 index c4d7e10..0000000 --- a/lib/globals/save_system.gd +++ /dev/null @@ -1,140 +0,0 @@ -extends Node - -const VariantSerializer = preload("res://lib/utils/variant_serializer.gd") - -signal loaded() -signal unloaded() - -var is_loaded := false -var export_config = ConfigFile.new() -var export_config_path = "res://export_presets.cfg" - -func clear(): - await _clear_save_tree(get_tree().root.get_node("Main")) - unloaded.emit() - is_loaded = false - -func save(): - if HomeApi.has_connected() == false: - return - - var filename = HomeApi.api.url.split("//")[1].replace("/api/websocket", "").replace(".", "_").replace(":", "_") - - var save_file = FileAccess.open("user://%s.save" % filename, FileAccess.WRITE) - - if save_file == null: - return - - var save_tree = _generate_save_tree(get_tree().root.get_node("Main")) - - var json_text = JSON.stringify({ - "version": get_version(), - "tree": save_tree - }) - save_file.store_line(json_text) - -func load(): - await clear() - - if HomeApi.has_connected() == false: - return - - var filename = HomeApi.api.url.split("//")[1].replace("/api/websocket", "").replace(".", "_").replace(":", "_") - - var save_file = FileAccess.open("user://%s.save" % filename, FileAccess.READ) - - if save_file == null: - return - - var json_text = save_file.get_line() - var save_data = JSON.parse_string(json_text) - - if save_data == null: - return - - if save_data.has("version") == false: - save() - return - - var save_tree = save_data["tree"] - - if save_tree == null: - return - - if save_tree is Array: - for tree in save_tree: - _build_save_tree(tree) - else: - _build_save_tree(save_tree) - - loaded.emit() - is_loaded = true - -func get_version(): - var config_error = export_config.load(export_config_path) - - if config_error != OK: - return null - - var version = export_config.get_value("preset.1.options", "version/name") - - if version == null: - return null - - return version - -func _clear_save_tree(node: Node): - for child in node.get_children(): - await _clear_save_tree(child) - - if node.has_method("_save"): - node.queue_free() - await node.tree_exited - -func _generate_save_tree(node: Node): - var children = [] - - if node.has_method("_save") == false: - for child in node.get_children(): - var data = _generate_save_tree(child) - - if data is Array: - for child_data in data: - children.append(child_data) - else: - children.append(data) - return children - - - var save_tree = { - "data": VariantSerializer.stringify_value(node.call("_save")), - "parent": node.get_parent().get_path(), - "filename": node.get_scene_file_path() - } - - for child in node.get_children(): - var child_data = _generate_save_tree(child) - - if child_data is Array: - for data in child_data: - children.append(data) - else: - children.append(child_data) - - save_tree["children"] = children - - return save_tree - -func _build_save_tree(tree: Dictionary): - var new_object = load(tree["filename"]).instantiate() - - if new_object.has_method("_load"): - new_object.call("_load", VariantSerializer.parse_value(tree["data"])) - else: - for key in tree["data"].keys(): - new_object.set(key, VariantSerializer.parse_value(tree["data"][key])) - - get_node(tree["parent"]).add_child(new_object) - - for child in tree["children"]: - _build_save_tree(child) diff --git a/lib/home_apis/hass_ws/hass.gd b/lib/home_apis/hass_ws/hass.gd index c04a3ff..7c26a2a 100644 --- a/lib/home_apis/hass_ws/hass.gd +++ b/lib/home_apis/hass_ws/hass.gd @@ -34,13 +34,13 @@ func connect_ws(): if url == "" || token == "": return - print("Connecting to %s" % self.url) - socket.connect_to_url(self.url) + print("Connecting to %s" % url + "/api/websocket") + socket.connect_to_url(url + "/api/websocket") set_process(true) # https://github.com/godotengine/godot/issues/84423 # Otherwise the WebSocketPeer will crash when receiving large packets - socket.set_inbound_buffer_size(pow(2, 22)) # ~4MB buffer + socket.set_inbound_buffer_size(pow(2, 23)) # ~8MB buffer func _process(delta): socket.poll() diff --git a/lib/stores/devices.gd b/lib/stores/devices.gd new file mode 100644 index 0000000..df2a8e2 --- /dev/null +++ b/lib/stores/devices.gd @@ -0,0 +1,5 @@ +extends StoreClass +const StoreClass = preload("./store.gd") + +func clear(): + pass \ No newline at end of file diff --git a/lib/stores/house.gd b/lib/stores/house.gd new file mode 100644 index 0000000..d06f9a4 --- /dev/null +++ b/lib/stores/house.gd @@ -0,0 +1,30 @@ +extends StoreClass + +const StoreClass = preload("./store.gd") + +# Type Room +# name: String +# corners: Vec2[] +# height: float +var rooms = [] +# Type Entity +# id: String +# position: Vec3 +# rotation: Vec3 +# room: String +var entities = [] +var align_position1: Vector3 +var align_position2: Vector3 + + +func _init(): + _save_path = "user://house.json" + +func clear(): + pass + +func get_room(name): + for room in rooms: + if room.name == name: + return room + return null \ No newline at end of file diff --git a/lib/stores/settings.gd b/lib/stores/settings.gd new file mode 100644 index 0000000..e2fc1ee --- /dev/null +++ b/lib/stores/settings.gd @@ -0,0 +1,16 @@ +extends StoreClass + +const StoreClass = preload("./store.gd") + + +var type: String = "HASS_WS" +var url: String = "" +var token: String = "" + +func _init(): + _save_path = "user://settings.json" + +func clear(): + type = "HASS_WS" + url = "" + token = "" \ No newline at end of file diff --git a/lib/stores/store.gd b/lib/stores/store.gd new file mode 100644 index 0000000..2a68ba8 --- /dev/null +++ b/lib/stores/store.gd @@ -0,0 +1,84 @@ +extends RefCounted + +const VariantSerializer = preload("res://lib/utils/variant_serializer.gd") + +signal on_loaded +signal on_saved + +var _loaded = false +var _save_path = null + +func is_loaded(): + return _loaded + +func clear(): + pass + +func create_dict(): + var data: Dictionary = {} + + for prop_info in get_property_list(): + if prop_info.name.begins_with("_") || prop_info.hint_string != "": + continue + + var prop = get(prop_info.name) + + if prop is Store: + data[prop_info.name] = prop.create_dict() + else: + data[prop_info.name] = VariantSerializer.stringify_value(prop) + + return data + +func use_dict(dict: Dictionary): + for prop_info in get_property_list(): + if prop_info.name.begins_with("_") || prop_info.hint_string != "": + continue + + var prop = get(prop_info.name) + var prop_value = dict[prop_info.name] + + if prop is Store: + prop.use_dict(prop_value) + else: + set(prop_info.name, prop_value) + +func save_local(path = _save_path): + if path == null: + return false + + var data = create_dict() + + var save_file = FileAccess.open(path, FileAccess.WRITE) + + if save_file == null: + return false + + var json_text = JSON.stringify(data) + save_file.store_line(json_text) + + on_saved.emit() + + return true + +func load_local(path = _save_path): + if path == null: + return false + + var save_file = FileAccess.open(path, FileAccess.READ) + + if save_file == null: + return false + + var json_text = save_file.get_as_text() + var save_data = VariantSerializer.parse_value(JSON.parse_string(json_text)) + + if save_data == null: + return false + + use_dict(save_data) + + _loaded = true + on_loaded.emit() + + return true diff --git a/lib/utils/state_machine/state_machine.gd b/lib/utils/state_machine/state_machine.gd index a5dbc81..0ef6377 100644 --- a/lib/utils/state_machine/state_machine.gd +++ b/lib/utils/state_machine/state_machine.gd @@ -30,3 +30,9 @@ func change_to(new_state: String) -> void: add_child(current_state) current_state._on_enter() changed.emit(new_state, old_state) + +func get_state(state_name: String) -> Node: + if states.has(state_name) == false: + return null + + return states[state_name] \ No newline at end of file diff --git a/lib/utils/variant_serializer.gd b/lib/utils/variant_serializer.gd index e811246..bad1869 100644 --- a/lib/utils/variant_serializer.gd +++ b/lib/utils/variant_serializer.gd @@ -11,7 +11,7 @@ static func stringify_value(value): return value.map(func(item): return stringify_value(item) ) - TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING: + TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_NIL: return value TYPE_VECTOR2: return { diff --git a/project.godot b/project.godot index fcaa8d9..e9cdb56 100644 --- a/project.godot +++ b/project.godot @@ -18,12 +18,11 @@ config/icon="res://assets/logo.png" [autoload] XRToolsUserSettings="*res://addons/godot-xr-tools/user_settings/user_settings.gd" -ConfigData="*res://lib/globals/config_data.gd" Request="*res://lib/globals/request.gd" HomeApi="*res://lib/globals/home_api.gd" AudioPlayer="*res://lib/globals/audio_player.gd" EventSystem="*res://lib/globals/event_system.gd" -SaveSystem="*res://lib/globals/save_system.gd" +Store="*res://lib/globals/main_store.gd" House="*res://lib/globals/house_body.gd" [editor_plugins]