diff --git a/.gitignore b/.gitignore index 3d6cd30..e6dcc6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Godot 4+ specific ignores .godot/ android/build/ +builds/ # Godot-specific ignores .import/ diff --git a/assets/design.afdesign b/assets/design.afdesign index 768cfee..c636778 100644 --- a/assets/design.afdesign +++ b/assets/design.afdesign @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e54002ccb9b04b9b4fb4642cc3b5fd6512f910e65ecb52e41c117a2345d1d22b -size 17810864 +oid sha256:b117dc5a7537a60ff341010636802131c9aa3b80982130e63d5d94902c002d07 +size 13505995 diff --git a/assets/materials/pointer.png b/assets/materials/pointer.png index 0ba72b0..5a92f4d 100644 --- a/assets/materials/pointer.png +++ b/assets/materials/pointer.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68e2b9d5e723a2009f341e45e2968893dd2a1dd5077fe1d9ba970f01d0d75e02 -size 56989 +oid sha256:9738010a57d2157a53e4f537afbcfb50dd28e9de40bae510621d0ea8c94c5844 +size 51682 diff --git a/content/system/room/room.gd b/content/system/room/room.gd index aa1e171..2c20233 100644 --- a/content/system/room/room.gd +++ b/content/system/room/room.gd @@ -1,172 +1,21 @@ extends Node3D -const wall_corner_scene = preload("./wall_corner.tscn") -const wall_edge_scene = preload("./wall_edge.tscn") - -@onready var wall_corners = $WallCorners -@onready var wall_edges = $WallEdges +@onready var wall_corners = $Ceiling/WallCorners +@onready var wall_edges = $Ceiling/WallEdges @onready var wall_mesh = $WallMesh @onready var wall_collisions = $WallCollisions -@onready var ground = $Ground/Clickable +@onready var room_floor = $Floor +@onready var room_ceiling = $Ceiling -var moving = null -var editable := false: +@onready var state_machine = $StateMachine + +var editable: bool = false: set(value): - if value == editable: - return - - editable = value if value: - _start_edit_mode() + state_machine.change_to("Edit") else: - _end_edit_mode() -var ground_plane = Plane(Vector3.UP, Vector3.ZERO) - - -func _ready(): - ground.on_click.connect(func(event): - if !editable: - return - - add_corner(event.ray.get_collision_point()) - ) - -func _start_edit_mode(): - wall_corners.visible = true - wall_edges.visible = true - wall_mesh.visible = false - wall_mesh.mesh = null - - for old_coll in wall_collisions.get_children(): - old_coll.queue_free() - -func _end_edit_mode(): - wall_corners.visible = false - wall_edges.visible = false - wall_mesh.mesh = generate_mesh() - - if wall_mesh.mesh == null: - return - - var collisions = generate_collision(wall_mesh.mesh) - - for collision in collisions: - var static_body = StaticBody3D.new() - static_body.set_collision_layer_value(4, true) - static_body.set_collision_layer_value(5, true) - static_body.collision_mask = 0 - static_body.add_child(collision) - wall_collisions.add_child(static_body) - - wall_mesh.visible = true - -func generate_mesh(): - var corner_count = wall_corners.get_child_count() - - if corner_count < 3: - return - - var st = SurfaceTool.new() - var wall_up = Vector3.UP * 3 - - st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP) - - for i in range(corner_count): - var corner = get_corner(i) - - st.add_vertex(corner.position) - st.add_vertex(corner.position + wall_up) - - var first_corner = 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_collision(mesh: ArrayMesh): - var corner_count = wall_corners.get_child_count() - - if corner_count < 3: - return - - var collision_shapes: Array[CollisionShape3D] = [] - - for i in range(corner_count): - var corner = get_corner(i) - var next_corner = get_corner(i + 1) - - var shape = BoxShape3D.new() - shape.size = Vector3((next_corner.position - corner.position).length(), 3, 0.04) - - var transform = Transform3D() - var back_vector = (corner.position - next_corner.position).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 - - var collision_shape = CollisionShape3D.new() - - collision_shape.shape = shape - collision_shape.transform = transform - - collision_shapes.append(collision_shape) - - return collision_shapes - -func add_corner(position: Vector3): - var corner = wall_corner_scene.instantiate() - corner.position = position - - corner.get_node("Clickable").on_grab_down.connect(func(event): - if !editable: - return - - moving = event.target - ) - - corner.get_node("Clickable").on_grab_move.connect(func(event): - if moving == null: - return - - var direction = -event.ray.global_transform.basis.z - var new_position = ground_plane.intersects_ray(event.ray.global_position, direction) - - if new_position == null: - return - - moving.position = new_position - var moving_index = moving.get_index() - - get_edge(moving_index).transform = corners_to_edge_transform(new_position, get_corner(moving_index + 1).position) - get_edge(moving_index - 1).transform = corners_to_edge_transform(get_corner(moving_index - 1).position, new_position) - ) - - corner.get_node("Clickable").on_grab_up.connect(func(_event): - moving = null - ) - - wall_corners.add_child(corner) - - - var num_corners = wall_corners.get_child_count() - var edge - - if num_corners > 1: - edge = add_edge(wall_corners.get_child(num_corners - 2).position, position) - - if num_corners > 2: - if num_corners != wall_edges.get_child_count(): - add_edge(position, wall_corners.get_child(0).position) - else: - wall_edges.move_child(edge, num_corners - 2) - get_edge(-1).transform = corners_to_edge_transform(position, get_corner(0).position) + state_machine.change_to("View") func get_corner(index: int) -> MeshInstance3D: return wall_corners.get_child(index % wall_corners.get_child_count()) @@ -175,22 +24,6 @@ func get_edge(index: int) -> MeshInstance3D: return wall_edges.get_child(index % wall_edges.get_child_count()) -func add_edge(from_pos: Vector3, to_pos: Vector3): - var edge: MeshInstance3D = wall_edge_scene.instantiate() - edge.transform = corners_to_edge_transform(from_pos, to_pos) - wall_edges.add_child(edge) - return edge - -func corners_to_edge_transform(from_pos: Vector3, to_pos: Vector3) -> Transform3D: - var diff = to_pos - from_pos - var direction = diff.normalized() - - var edge_position = from_pos + diff / 2 - var edge_basis = Basis(Vector3.UP, diff, direction.cross(Vector3.UP)) - - var edge_transform = Transform3D(edge_basis, edge_position) - return edge_transform - func _save(): return { "corners": wall_corners.get_children().map(func(corner): return corner.position), @@ -198,8 +31,11 @@ func _save(): func _load(data): await ready + return + + state_machine.change_to("Edit") for corner in data["corners"]: - add_corner(corner) + state_machine.current_state.add_corner(corner) - _end_edit_mode() + state_machine.change_to("View") diff --git a/content/system/room/room.tscn b/content/system/room/room.tscn index 6270f6a..25eda74 100644 --- a/content/system/room/room.tscn +++ b/content/system/room/room.tscn @@ -1,42 +1,60 @@ -[gd_scene load_steps=7 format=3 uid="uid://bswgmclohuqui"] +[gd_scene load_steps=10 format=3 uid="uid://bswgmclohuqui"] [ext_resource type="Script" path="res://content/system/room/room.gd" id="1_fccq0"] [ext_resource type="Script" path="res://content/functions/clickable.gd" id="1_ugebq"] [ext_resource type="Material" uid="uid://bbx6fv7jq50tr" path="res://content/system/room/walls.tres" id="3_al1ev"] +[ext_resource type="Script" path="res://lib/utils/state_machine/state_machine.gd" id="4_nbbo6"] +[ext_resource type="Script" path="res://content/system/room/states/view.gd" id="6_g066t"] +[ext_resource type="Script" path="res://content/system/room/states/edit.gd" id="7_ap14h"] [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) [node name="Room" type="Node3D"] script = ExtResource("1_fccq0") -[node name="Ground" type="StaticBody3D" parent="."] +[node name="Floor" type="StaticBody3D" parent="."] collision_layer = 24 collision_mask = 0 -[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"] +[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor"] shape = SubResource("WorldBoundaryShape3D_08sv0") -[node name="Clickable" type="Node" parent="Ground"] +[node name="Clickable" type="Node" parent="Floor"] script = ExtResource("1_ugebq") -[node name="WallCorners" type="Node3D" parent="."] - -[node name="WallEdges" type="Node3D" parent="."] - [node name="WallMesh" type="MeshInstance3D" parent="."] material_override = ExtResource("3_al1ev") mesh = SubResource("ArrayMesh_7dibq") [node name="WallCollisions" type="Node3D" parent="."] -[node name="Celing" type="StaticBody3D" parent="."] -transform = Transform3D(-1, 8.74228e-08, 0, -8.74228e-08, -1, 0, 0, 0, 1, 0, 3.07, 0) -collision_layer = 0 +[node name="Ceiling" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) +collision_layer = 24 collision_mask = 0 -[node name="CollisionShape3D" type="CollisionShape3D" parent="Celing"] +[node name="CollisionShape3D" type="CollisionShape3D" parent="Ceiling"] shape = SubResource("WorldBoundaryShape3D_ap613") +disabled = true + +[node name="Clickable" type="Node" parent="Ceiling"] +script = ExtResource("1_ugebq") + +[node name="WallCorners" type="Node3D" parent="Ceiling"] + +[node name="WallEdges" type="Node3D" parent="Ceiling"] + +[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("current_state")] +script = ExtResource("4_nbbo6") +current_state = NodePath("View") + +[node name="View" type="Node" parent="StateMachine"] +script = ExtResource("6_g066t") + +[node name="Edit" type="Node" parent="StateMachine"] +script = ExtResource("7_ap14h") diff --git a/content/system/room/states/edit.gd b/content/system/room/states/edit.gd new file mode 100644 index 0000000..330ab10 --- /dev/null +++ b/content/system/room/states/edit.gd @@ -0,0 +1,213 @@ +extends RoomState + +const wall_corner_scene = preload("../wall_corner.tscn") +const wall_edge_scene = preload("../wall_edge.tscn") +const RoomState = preload("./room_state.gd") + +var moving = null +var floor_corner: StaticBody3D = null +var height_corner: StaticBody3D = null +var height_edge: MeshInstance3D = null + +func _on_enter(): + room.wall_corners.visible = true + room.wall_edges.visible = true + + if floor_corner != null: + floor_corner.visible = true + height_corner.visible = true + height_edge.visible = true + + room.room_ceiling.get_node("Clickable").on_click.connect(_on_click_ceiling) + 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 + + if floor_corner != null: + floor_corner.visible = false + height_corner.visible = false + height_edge.visible = false + + 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 _on_click_floor(event): + if floor_corner != null && height_corner != null: + return + + add_floor_corner(event.ray.get_collision_point()) + add_height_corner(event.ray.get_collision_point()) + room.room_ceiling.get_node("CollisionShape3D").disabled = false + +func _on_click_ceiling(event): + if floor_corner == null || height_corner == null: + return + + var pos = event.ray.get_collision_point() + pos.y = 0 + + add_corner(pos) + +func add_floor_corner(position: Vector3): + floor_corner = wall_corner_scene.instantiate() + floor_corner.position = position + + height_edge = wall_edge_scene.instantiate() + height_edge.transform = corners_to_edge_transform(position, position + Vector3.UP * room.room_ceiling.global_position.y) + + floor_corner.get_node("Clickable").on_grab_down.connect(func(event): + if !is_active(): + return + + moving = event.target + ) + + floor_corner.get_node("Clickable").on_grab_move.connect(func(event): + if moving == null: + return + + var direction = -event.ray.global_transform.basis.z + var new_position = room.room_floor.get_node("CollisionShape3D").shape.plane.intersects_ray(event.ray.global_position, direction) + + if new_position == null: + # mark for deletion + return + + moving.position = new_position + var moving_index = height_corner.get_index() + + height_edge.transform = corners_to_edge_transform(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 + + if room.wall_edges.get_child_count() == 0: + return + + room.get_edge(moving_index).transform = corners_to_edge_transform(new_position, room.get_corner(moving_index + 1).position) + room.get_edge(moving_index - 1).transform = corners_to_edge_transform(room.get_corner(moving_index - 1).position, new_position) + ) + + floor_corner.get_node("Clickable").on_grab_up.connect(func(_event): + moving = null + ) + + room.add_child(floor_corner) + room.add_child(height_edge) + +func add_height_corner(position: Vector3): + height_corner = wall_corner_scene.instantiate() + height_corner.position.x = position.x + height_corner.position.z = position.z + + height_corner.get_node("Clickable").on_grab_down.connect(func(event): + if !is_active(): + return + + moving = event.target + ) + + height_corner.get_node("Clickable").on_grab_move.connect(func(event): + if moving == null: + return + + var direction = -event.ray.global_transform.basis.z + var plane_direction = direction + plane_direction.y = 0 + plane_direction = plane_direction.normalized() * -1 + + var plane = Plane(plane_direction, moving.position) + + var new_position = plane.intersects_ray(event.ray.global_position, direction) + + if new_position == null: + return + + room.room_ceiling.position.y = new_position.y + height_edge.transform = corners_to_edge_transform(floor_corner.global_position, height_corner.global_position) + + ) + + height_corner.get_node("Clickable").on_grab_up.connect(func(_event): + moving = null + ) + + room.wall_corners.add_child(height_corner) + +func add_corner(position: Vector3): + var corner = wall_corner_scene.instantiate() + corner.position.x = position.x + corner.position.z = position.z + + corner.get_node("Clickable").on_grab_down.connect(func(event): + if !is_active(): + return + + moving = event.target + ) + + corner.get_node("Clickable").on_grab_move.connect(func(event): + if moving == null: + return + + var direction = -event.ray.global_transform.basis.z + var ceiling_plane = Plane(Vector3.DOWN, room.room_ceiling.global_position) + var new_position = ceiling_plane.intersects_ray(event.ray.global_position, direction) + + if new_position == null: + return + + new_position.y = 0 + + moving.position = new_position + var moving_index = moving.get_index() + + if room.wall_edges.get_child_count() == 0: + return + + room.get_edge(moving_index).transform = corners_to_edge_transform(new_position, room.get_corner(moving_index + 1).position) + room.get_edge(moving_index - 1).transform = corners_to_edge_transform(room.get_corner(moving_index - 1).position, new_position) + ) + + corner.get_node("Clickable").on_grab_up.connect(func(_event): + moving = null + ) + + room.wall_corners.add_child(corner) + + + var num_corners = room.wall_corners.get_child_count() + var edge + + if num_corners > 1: + edge = add_edge(room.wall_corners.get_child(num_corners - 2).position, position) + + if num_corners > 2: + if num_corners != room.wall_edges.get_child_count(): + add_edge(position, room.wall_corners.get_child(0).position) + else: + room.wall_edges.move_child(edge, num_corners - 2) + room.get_edge(-1).transform = corners_to_edge_transform(position, room.get_corner(0).position) + +func add_edge(from_pos: Vector3, to_pos: Vector3): + var edge: MeshInstance3D = wall_edge_scene.instantiate() + edge.transform = corners_to_edge_transform(from_pos, to_pos) + room.wall_edges.add_child(edge) + return edge + +func corners_to_edge_transform(from_pos: Vector3, to_pos: Vector3) -> Transform3D: + var diff = to_pos - from_pos + var direction = diff.normalized() + var tangent = Vector3(direction.z, 0, -direction.x).normalized() + + if tangent == Vector3.ZERO: + tangent = Vector3(1, 0, 0) + + var edge_position = from_pos + diff / 2 + + var edge_basis = Basis(tangent, diff, tangent.cross(direction)) + + var edge_transform = Transform3D(edge_basis, edge_position) + return edge_transform diff --git a/content/system/room/states/room_state.gd b/content/system/room/states/room_state.gd new file mode 100644 index 0000000..fa136b4 --- /dev/null +++ b/content/system/room/states/room_state.gd @@ -0,0 +1,7 @@ +extends State +const Room = preload("res://content/system/room/room.gd") + +var room: Room + +func _ready(): + room = get_parent().get_parent() diff --git a/content/system/room/states/view.gd b/content/system/room/states/view.gd new file mode 100644 index 0000000..e236d1e --- /dev/null +++ b/content/system/room/states/view.gd @@ -0,0 +1,86 @@ +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() + + if corner_count < 3: + return + + room_height = room.get_corner(0).global_position.y + + room.wall_mesh.mesh = generate_mesh() + + if room.wall_mesh.mesh == null: + return + + var collisions = generate_collision() + + for collision in collisions: + var static_body = StaticBody3D.new() + static_body.set_collision_layer_value(4, true) + static_body.set_collision_layer_value(5, true) + static_body.collision_mask = 0 + static_body.add_child(collision) + room.wall_collisions.add_child(static_body) + + room.wall_mesh.visible = true + +func _on_leave(): + room.wall_mesh.mesh = null + + 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_collision(): + 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 shape = BoxShape3D.new() + shape.size = Vector3((next_corner.position - corner.position).length(), room_height, 0.04) + + var transform = Transform3D() + var back_vector = (corner.position - next_corner.position).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 + + var collision_shape = CollisionShape3D.new() + + collision_shape.shape = shape + collision_shape.transform = transform + + collision_shapes.append(collision_shape) + + return collision_shapes diff --git a/export_presets.cfg b/export_presets.cfg index 5c00108..6d05a01 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -248,7 +248,7 @@ architectures/armeabi-v7a=false architectures/arm64-v8a=true architectures/x86=false architectures/x86_64=false -version/code=6 +version/code=7 version/name="v0.2.0" package/unique_name="de.nitwel.$genname" package/name="Immersive Home" diff --git a/lib/utils/state_machine/state.gd b/lib/utils/state_machine/state.gd new file mode 100644 index 0000000..521d0ea --- /dev/null +++ b/lib/utils/state_machine/state.gd @@ -0,0 +1,13 @@ +extends Node +class_name State + +var state_machine: StateMachine + +func is_active(): + return state_machine.current_state == self + +func _on_enter(): + pass + +func _on_leave(): + pass \ No newline at end of file diff --git a/lib/utils/state_machine/state_machine.gd b/lib/utils/state_machine/state_machine.gd new file mode 100644 index 0000000..6fb7f7a --- /dev/null +++ b/lib/utils/state_machine/state_machine.gd @@ -0,0 +1,32 @@ +class_name StateMachine +extends Node + +signal changed(state_name: String, old_state: String) + +@export var current_state: Node +var states: Dictionary = {} + +func _ready() -> void: + for state in get_children(): + states[state.get_name()] = state + state.state_machine = self + + if state != current_state: + remove_child(state) + + await get_parent().ready + + current_state._on_enter() + +func change_to(new_state: String) -> void: + if states.has(new_state) == false: + return + + var old_state = current_state.get_name() + + current_state._on_leave() + remove_child(current_state) + current_state = states[new_state] + add_child(current_state) + current_state._on_enter() + changed.emit(new_state, old_state) \ No newline at end of file