diff --git a/app/addons/godot-xr-tools/hands/animations/left/hand_blend_tree.tres b/app/addons/godot-xr-tools/hands/animations/left/hand_blend_tree.tres index ae6f20a..011abdb 100644 --- a/app/addons/godot-xr-tools/hands/animations/left/hand_blend_tree.tres +++ b/app/addons/godot-xr-tools/hands/animations/left/hand_blend_tree.tres @@ -29,4 +29,4 @@ nodes/OpenHand/node = SubResource("4") nodes/OpenHand/position = Vector2(-600, 100) nodes/Trigger/node = SubResource("5") nodes/Trigger/position = Vector2(-360, 20) -node_connections = [&"output", 0, &"Grip", &"Grip", 0, &"Trigger", &"Grip", 1, &"ClosedHand2", &"Trigger", 0, &"OpenHand", &"Trigger", 1, &"ClosedHand1"] +node_connections = [&"Grip", 0, &"Trigger", &"Grip", 1, &"ClosedHand2", &"Trigger", 0, &"OpenHand", &"Trigger", 1, &"ClosedHand1", &"output", 0, &"Grip"] diff --git a/app/addons/godot-xr-tools/hands/animations/right/hand_blend_tree.tres b/app/addons/godot-xr-tools/hands/animations/right/hand_blend_tree.tres index f56fb58..f1d41a1 100644 --- a/app/addons/godot-xr-tools/hands/animations/right/hand_blend_tree.tres +++ b/app/addons/godot-xr-tools/hands/animations/right/hand_blend_tree.tres @@ -29,4 +29,4 @@ nodes/OpenHand/node = SubResource("3") nodes/OpenHand/position = Vector2(-600, 100) nodes/Trigger/node = SubResource("5") nodes/Trigger/position = Vector2(-360, 40) -node_connections = [&"output", 0, &"Grip", &"Grip", 0, &"Trigger", &"Grip", 1, &"ClosedHand2", &"Trigger", 0, &"OpenHand", &"Trigger", 1, &"ClosedHand1"] +node_connections = [&"Grip", 0, &"Trigger", &"Grip", 1, &"ClosedHand2", &"Trigger", 0, &"OpenHand", &"Trigger", 1, &"ClosedHand1", &"output", 0, &"Grip"] diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Glove_L.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Glove_L.gltf.import index 1314ffb..ac26307 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Glove_L.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Glove_L.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Glove_R.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Glove_R.gltf.import index 8c12245..6bb5437 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Glove_R.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Glove_R.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_L.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_L.gltf.import index 6fb44fe..1daeb3d 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_L.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_L.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_R.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_R.gltf.import index 31c5355..61667fe 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_R.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Glove_low_R.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Nails_L.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Nails_L.gltf.import index fb4f1ce..b845079 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Nails_L.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Nails_L.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Nails_R.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Nails_R.gltf.import index d198397..b30506a 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Nails_R.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Nails_R.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_L.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_L.gltf.import index 40d4e92..ddd426c 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_L.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_L.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_R.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_R.gltf.import index 53fea66..561edee 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_R.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_Nails_low_R.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_low_L.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_low_L.gltf.import index 44b7650..db27d98 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_low_L.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_low_L.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/Hand_low_R.gltf.import b/app/addons/godot-xr-tools/hands/model/Hand_low_R.gltf.import index dcf37b5..a35d186 100644 --- a/app/addons/godot-xr-tools/hands/model/Hand_low_R.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/Hand_low_R.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/hand_l.gltf.import b/app/addons/godot-xr-tools/hands/model/hand_l.gltf.import index 326d084..90a575c 100644 --- a/app/addons/godot-xr-tools/hands/model/hand_l.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/hand_l.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/godot-xr-tools/hands/model/hand_r.gltf.import b/app/addons/godot-xr-tools/hands/model/hand_r.gltf.import index 1f91a8d..32df37f 100644 --- a/app/addons/godot-xr-tools/hands/model/hand_r.gltf.import +++ b/app/addons/godot-xr-tools/hands/model/hand_r.gltf.import @@ -22,6 +22,7 @@ meshes/generate_lods=true meshes/create_shadow_meshes=true meshes/light_baking=1 meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false skins/use_named_skins=true animation/import=true animation/fps=30 @@ -29,4 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/addons/rdot/Rdot.gd b/app/addons/rdot/Rdot.gd new file mode 100644 index 0000000..6428a86 --- /dev/null +++ b/app/addons/rdot/Rdot.gd @@ -0,0 +1,201 @@ +class_name R + +static func state(value: Variant, options: Dictionary={}): + if value is Dictionary: + return store(value) + + return RdotState.new(value, options) + +static func store(value: Dictionary): + return RdotStore.new(value) + +static func computed(computation: Callable, options: Dictionary={}): + return RdotComputed.new(computation, options) + +## DIY overloading of +## bind(target, prop, value) +## bind(target, prop, value, watch_signal) +## bind(target, prop, store, key, watch_signal) +static func bind(target, prop, value, arg1=null, arg2=null): + if value is RdotStore: + return _bind_store(target, prop, value, arg1, arg2) + + if value is RdotState or value is RdotComputed: + return _bind_state(target, prop, value, arg1) + + assert(false, "Invalid arguments to bind, value must be a RdotState, a RdotComputed or a RdotStore") + +static func _bind_store(target, prop, store: RdotStore, key, watch_signal=null): + store._access_property(key) + + return _bind_state(target, prop, store._proxied_value[key], watch_signal) + +static func _bind_state(target, prop, value, watch_signal=null): + var graph = RdotGraph.getInstance() + + var watch_c = func(new_value): + value.value = new_value + + var c = computed(func(_arg): + var oldValue=target.get(prop) + + if oldValue != value.value: + target.set(prop, value.value) + ) + + if watch_signal: + watch_signal.connect(watch_c) + + graph.watcher.watch([c]) + c.do_get() + + return func(): + graph.watcher.unwatch([c]) + if watch_signal: + watch_signal.disconnect(watch_c) + +static func effect(callback: Callable): + var graph = RdotGraph.getInstance() + + var deconstructor := Callable() + var c = computed(func(_arg): + if !deconstructor.is_null(): + deconstructor.call() + + var result=callback.call(_arg) + + if result is Callable: + deconstructor=result + ) + + graph.watcher.watch([c]) + c.do_get() + + return func(): + if !deconstructor.is_null(): + deconstructor.call() + + graph.watcher.unwatch([c]) + +class RdotState: + var node: RdotStateInternal + var value = null: + get: + return do_get() + set(value): + do_set(value) + + func _init(value: Variant, options: Dictionary={}): + var ref = RdotStateInternal.createSignal(value) + var node = ref[1] + self.node = node + node.wrapper = self + + if !options.is_empty(): + var equals = options.get("equals") + + if !equals.is_null(): + node.equals = equals + + func do_get(): + return RdotStateInternal.signalGetFn.call(self.node) + + func do_set(value: Variant): + var graph = RdotGraph.getInstance() + + assert(graph.isInNotificationPhase() == false, "Writes to signals not permitted during Watcher callback") + + var ref = self.node + + RdotStateInternal.signalSetFn.call(ref, value) + +class RdotComputed: + var node: RdotComputedInternal + var value = null: + get: + return do_get() + set(value): + pass + + func _init(computation: Callable, options: Dictionary={}): + var ref = RdotComputedInternal.createdComputed(computation) + var node = ref[1] + node.consumerAllowSignalWrites = true + self.node = node + node.wrapper = self + + if options: + var equals = options.get("equals") + + if !equals.is_null(): + node.equals = equals + + func do_get(): + return RdotComputedInternal.computedGet(node) + +class Watcher: + var node: RdotNode + + func _init(notify: Callable): + var node = RdotNode.new() + node.wrapper = self + node.consumerMarkedDirty = notify + node.consumerIsAlwaysLive = true + node.consumerAllowSignalWrites = false + node.producerNode = [] + self.node = node + + ## signals: Array[RdotState | RComputed] + func _assertSignals(signals: Array): + for s in signals: + assert(s is RdotState or s is RdotComputed, "Watcher expects signals to be RdotState or RdotComputed") + + func watch(signals:=[]): + _assertSignals(signals) + + var graph = RdotGraph.getInstance() + + var node = self.node + node.dirty = false + var prev = graph.setActiveConsumer(node) + for s in signals: + graph.producerAccessed(s.node) + + graph.setActiveConsumer(prev) + + func unwatch(signals: Array): + _assertSignals(signals) + + var graph = RdotGraph.getInstance() + + var node = self.node + graph.assertConsumerNode(node) + + var indicesToShift = [] + for i in range(node.producerNode.size()): + if signals.has(node.producerNode[i].wrapper): + graph.producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]) + indicesToShift.append(i) + + for idx in indicesToShift: + var lastIdx = node.producerNode.size() - 1 + node.producerNode[idx] = node.producerNode[lastIdx] + node.producerIndexOfThis[idx] = node.producerIndexOfThis[lastIdx] + + node.producerNode.pop_back() + node.producerIndexOfThis.pop_back() + node.nextProducerIndex -= 1 + + if idx < node.producerNode.size(): + var idxConsumer = node.producerNode[idx] + var producer = node.producerNode[idx] + + graph.assertProducerNode(producer) + + producer.liveConsumerIndexOfThis[idxConsumer] = idx + + ## Returns Array[RComputed] + func getPending() -> Array: + var node = self.node + + return node.producerNode.filter(func(n): return n.dirty).map(func(n): return n.wrapper) diff --git a/app/addons/rdot/array.gd b/app/addons/rdot/array.gd new file mode 100644 index 0000000..5638d60 --- /dev/null +++ b/app/addons/rdot/array.gd @@ -0,0 +1,13 @@ +class_name RdotArray + +static func do_set(array: Array, index: int, value): + if index >= array.size(): + array.resize(index + 1) + + array[index] = value + +static func do_get(array: Array, index: int): + if index >= array.size(): + return null + + return array[index] \ No newline at end of file diff --git a/app/addons/rdot/computed.gd b/app/addons/rdot/computed.gd new file mode 100644 index 0000000..bb72b41 --- /dev/null +++ b/app/addons/rdot/computed.gd @@ -0,0 +1,64 @@ +extends RdotNode +class_name RdotComputedInternal + +enum State { + SET = 0, + UNSET = 1, + COMPUTING = 2, + ERRORED = 3 +} + +var value: Variant = null +var state: State = State.UNSET +var error = null +var computation := Callable() +var equal := func(this, a, b): return a == b + +static func computedGet(node: RdotComputedInternal) -> Variant: + var graph := RdotGraph.getInstance() + + graph.producerUpdateValueVersion(node) + graph.producerAccessed(node) + + assert(node.state != State.ERRORED, "Error in computed.") + + return node.value + +static func createdComputed(computation: Callable): + var node = RdotComputedInternal.new() + node.computation = computation + + var computed = func(): + return computedGet(node) + + return [computed, node] + +func producerMustRecompute(node: RdotNode) -> bool: + return node.state == State.UNSET or node.state == State.COMPUTING + +func _init(): + self.dirty = true + self.producerRecomputeValue = func(node: RdotNode): + assert(node.state != State.COMPUTING, "Detected cycle in computations.") + + var graph := RdotGraph.getInstance() + + var oldState = node.state + var oldValue = node.value + node.state = State.COMPUTING + + var prevConsumer = graph.consumerBeforeComputation(node) + var newValue = node.computation.call(node.wrapper) + var oldOk = oldState != State.UNSET&&oldState != State.ERRORED + var wasEqual = oldOk&&node.equal.call(node.wrapper, oldValue, newValue) + + graph.consumerAfterComputation(node, prevConsumer) + + if wasEqual: + node.value = oldValue + node.state = oldState + return + + node.value = newValue + node.state = State.SET + node.version += 1 \ No newline at end of file diff --git a/app/addons/rdot/graph.gd b/app/addons/rdot/graph.gd new file mode 100644 index 0000000..6fde0d0 --- /dev/null +++ b/app/addons/rdot/graph.gd @@ -0,0 +1,225 @@ +extends RefCounted +class_name RdotGraph + +static var instance: RdotGraph = null + +static func getInstance() -> RdotGraph: + if instance == null: + instance = RdotGraph.new() + return instance + +var activeConsumer: RdotNode = null +var inNotificationPhase := false + +var epoch := 1 + +var postSignalSetFn := Callable() +var watcherPending := false + +var watcher = R.Watcher.new(func(_arg): + if watcherPending: + return + + watcherPending=true + var endOfFrame=func(): + + watcherPending=false + for s in watcher.getPending(): + s.do_get() + + watcher.watch() + + endOfFrame.call_deferred() +) + +func setActiveConsumer(consumer: RdotNode) -> RdotNode: + var prev = activeConsumer + activeConsumer = consumer + return prev + +func getActiveConsumer() -> RdotNode: + return activeConsumer + +func isInNotificationPhase() -> bool: + return inNotificationPhase + +func producerAccessed(node: RdotNode): + assert(inNotificationPhase == false, "Signal read during notification phase") + + if activeConsumer == null: + return + + if activeConsumer.consumerOnSignalRead.is_null() == false: + activeConsumer.consumerOnSignalRead.call(node) + + var idx = activeConsumer.nextProducerIndex; + activeConsumer.nextProducerIndex += 1 + + assertConsumerNode(activeConsumer) + + if idx < activeConsumer.producerNode.size()&&activeConsumer.producerNode[idx] != node: + if consumerIsLive(activeConsumer): + var staleProducer = activeConsumer.producerNode[idx] + producerRemoveLiveConsumerAtIndex(staleProducer, activeConsumer.producerIndexOfThis[idx]) + + if RdotArray.do_get(activeConsumer.producerNode, idx) != node: + RdotArray.do_set(activeConsumer.producerNode, idx, node) + RdotArray.do_set(activeConsumer.producerIndexOfThis, idx, producerAddLiveConsumer(node, activeConsumer, idx) if consumerIsLive(activeConsumer) else 0) + + RdotArray.do_set(activeConsumer.producerLastReadVersion, idx, node.version) + +func producerIncrementEpoch(): + epoch += 1 + +func producerUpdateValueVersion(node: RdotNode): + if consumerIsLive(node)&&!node.dirty: + return + + if !node.dirty&&node.lastCleanEpoch == epoch: + return + + if !node.producerMustRecompute(node)&&!consumerPollProducersForChange(node): + node.dirty = false; + node.lastCleanEpoch = epoch + return + + if node.producerRecomputeValue.is_null() == false: + node.producerRecomputeValue.call(node) + + node.dirty = false + node.lastCleanEpoch = epoch + +func producerNotifyConsumers(node: RdotNode): + if node.liveConsumerNode == null: + return + + var prev = inNotificationPhase + inNotificationPhase = true + + for consumer in node.liveConsumerNode: + if !consumer.dirty: + consumerMarkDirty(consumer) + + inNotificationPhase = prev + +func producerUpdatesAllowed() -> bool: + return activeConsumer == null||activeConsumer.consumerAllowSignalWrites != false + +func consumerMarkDirty(node: RdotNode): + node.dirty = true + producerNotifyConsumers(node) + + if node.consumerMarkedDirty.is_null() == false: + node.consumerMarkedDirty.call(node) + +func consumerBeforeComputation(node: RdotNode) -> RdotNode: + if node: + node.nextProducerIndex = 0 + + return setActiveConsumer(node) + +func consumerAfterComputation(node: RdotNode, prevConsumer: RdotNode): + setActiveConsumer(prevConsumer) + + if node == null||node.producerNode == null||node.producerIndexOfThis == null||node.producerLastReadVersion == null: + return + + if consumerIsLive(node): + for i in range(node.nextProducerIndex, node.producerNode.size()): + producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]) + + while node.producerNode.size() > node.nextProducerIndex: + node.producerNode.pop_back() + node.producerLastReadVersion.pop_back() + node.producerIndexOfThis.pop_back() + +func consumerPollProducersForChange(node: RdotNode) -> bool: + assertConsumerNode(node) + + for i in range(node.producerNode.size()): + var producer = node.producerNode[i] + var seenVersion = node.producerLastReadVersion[i] + + if seenVersion != producer.version: + return true + + producerUpdateValueVersion(producer) + + if seenVersion != producer.version: + return true + + return false + +func consumerDestroy(node: RdotNode): + assertConsumerNode(node) + + if consumerIsLive(node): + for i in range(node.producerNode.size()): + producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]) + + node.producerNode.clear() + node.producerLastReadVersion.clear() + node.producerIndexOfThis.clear() + + if node.liveConsumerNode: + node.liveConsumerNode.clear() + node.liveConsumerIndexOfThis.clear() + +static func producerAddLiveConsumer(node: RdotNode, consumer: RdotNode, indexOfThis: int) -> int: + assertProducerNode(node) + assertConsumerNode(node) + + if node.liveConsumerNode.size() == 0: + if node.watched.is_null() == false: + node.watched.call(node.wrapper) + + for i in range(node.producerNode.size()): + node.producerIndexOfThis[i] = producerAddLiveConsumer(node.producerNode[i], node, i) + + node.liveConsumerIndexOfThis.push_back(indexOfThis) + node.liveConsumerNode.push_back(consumer) + + return node.liveConsumerNode.size() - 1 + +static func producerRemoveLiveConsumerAtIndex(node: RdotNode, idx: int): + assertProducerNode(node) + assertConsumerNode(node) + + assert(idx < node.liveConsumerNode.size(), "active consumer index %s is out of bounds of %s consumers)" % [idx, node.liveConsumerNode.size()]) + + if node.liveConsumerNode.size() == 1: + if node.unwatched.is_null() == false: + node.unwatched.call(node.wrapper) + + for i in range(node.producerNode.size()): + producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]) + + var lastIdx = node.liveConsumerNode.size() - 1 + node.liveConsumerNode[idx] = node.liveConsumerNode[lastIdx] + node.liveConsumerIndexOfThis[idx] = node.liveConsumerIndexOfThis[lastIdx] + + node.liveConsumerNode.pop_back() + node.liveConsumerIndexOfThis.pop_back() + + if idx < node.liveConsumerNode.size(): + var idxProducer = node.liveConsumerIndexOfThis[idx] + var consumer = node.liveConsumerNode[idx] + assertConsumerNode(consumer) + consumer.producerIndexOfThis[idxProducer] = idx + +static func consumerIsLive(node: RdotNode) -> bool: + return node.consumerIsAlwaysLive||(node.liveConsumerNode != null&&node.liveConsumerNode.size() > 0) + +static func assertConsumerNode(node: RdotNode): + if node.producerNode == null: + node.producerNode = [] + if node.producerIndexOfThis == null: + node.producerIndexOfThis = [] + if node.producerLastReadVersion == null: + node.producerLastReadVersion = [] + +static func assertProducerNode(node: RdotNode): + if node.liveConsumerNode == null: + node.liveConsumerNode = [] + if node.liveConsumerIndexOfThis == null: + node.liveConsumerIndexOfThis = [] \ No newline at end of file diff --git a/app/addons/rdot/node.gd b/app/addons/rdot/node.gd new file mode 100644 index 0000000..f8b273e --- /dev/null +++ b/app/addons/rdot/node.gd @@ -0,0 +1,38 @@ +extends RefCounted +class_name RdotNode + +var version := 0 +var lastCleanEpoch := 0 +var dirty := false + +## Array[RdotNode] | null +var producerNode = null + +## Array[int] | null +var producerLastReadVersion = null + +## Array[int] | null +var producerIndexOfThis = null +var nextProducerIndex := 0 + +## Array[RdotNode] | null +var liveConsumerNode = null + +## Array[int] | null +var liveConsumerIndexOfThis = null +var consumerAllowSignalWrites := false +var consumerIsAlwaysLive := false + +var watched: Callable = Callable() +var unwatched: Callable = Callable() +var wrapper + +func producerMustRecompute(node: RdotNode) -> bool: + return false + +var producerRecomputeValue: Callable = Callable() +var consumerMarkedDirty: Callable = Callable() +var consumerOnSignalRead: Callable = Callable() + +# func _to_string(): +# return "RdotNode {\n" + ",\n\t".join(get_property_list().map(func(dict): return dict.name + ": " + str(get(dict.name)))) + "\n}" diff --git a/app/addons/rdot/state.gd b/app/addons/rdot/state.gd new file mode 100644 index 0000000..35a6bc0 --- /dev/null +++ b/app/addons/rdot/state.gd @@ -0,0 +1,50 @@ +extends RdotNode +class_name RdotStateInternal + +var equal: Callable = func(this, a, b): a == b +var value: Variant = null + +static func createSignal(initialValue: Variant): + var node = RdotStateInternal.new() + node.value = initialValue + + var getter = func(): + RdotGraph.getInstance().producerAccessed(node) + return node.value + + return [getter, node] + +static func setPostSignalSetFn(fn: Callable) -> Callable: + var graph := RdotGraph.getInstance() + var prev = graph.postSignalSetFn + graph.postSignalSetFn = fn + return prev + +static func signalGetFn(this: RdotStateInternal): + RdotGraph.getInstance().producerAccessed(this) + return this.value + +static func signalSetFn(node: RdotStateInternal, newValue: Variant): + var graph := RdotGraph.getInstance() + + assert(graph.producerUpdatesAllowed()) + + if !node.equal.call(node.wrapper, node.value, newValue): + node.value = newValue + signalValueChanged(node) + +static func signalUpdateFn(node: RdotStateInternal, updater: Callable): + var graph := RdotGraph.getInstance() + + assert(graph.producerUpdatesAllowed()) + + signalSetFn(node, updater.call(node.value)) + +static func signalValueChanged(node: RdotStateInternal): + var graph := RdotGraph.getInstance() + + node.version += 1 + graph.producerIncrementEpoch() + graph.producerNotifyConsumers(node) + if !graph.postSignalSetFn.is_null(): + graph.postSignalSetFn.call() diff --git a/app/addons/rdot/store.gd b/app/addons/rdot/store.gd new file mode 100644 index 0000000..abee4d1 --- /dev/null +++ b/app/addons/rdot/store.gd @@ -0,0 +1,37 @@ +extends Object +class_name RdotStore + +var _proxied_value = {} +var _property_list = [] + +func _init(initial_value: Dictionary={}): + _proxied_value = initial_value + + _property_list = _proxied_value.keys().map(func(key): + return { + "name": key, + "type": typeof(_proxied_value[key]) + } + ) + +func _get(property): + _access_property(property) + + if _proxied_value[property] is RdotStore: + return _proxied_value[property] + + return _proxied_value[property].value + +func _set(property, value): + _access_property(property) + + _proxied_value[property].value = value + + return true + +func _access_property(property): + if (_proxied_value[property] is R.RdotState) == false: + _proxied_value[property] = R.state(_proxied_value[property]) + +func _get_property_list(): + return _property_list diff --git a/app/assets/models/chat_bubble/chat_bubble-flipped.glb.import b/app/assets/models/chat_bubble/chat_bubble-flipped.glb.import index 3161c82..5d18a94 100644 --- a/app/assets/models/chat_bubble/chat_bubble-flipped.glb.import +++ b/app/assets/models/chat_bubble/chat_bubble-flipped.glb.import @@ -30,5 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} -gltf/naming_version=1 +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/assets/models/chat_bubble/chat_bubble.glb.import b/app/assets/models/chat_bubble/chat_bubble.glb.import index f0166c8..fa93cd6 100644 --- a/app/assets/models/chat_bubble/chat_bubble.glb.import +++ b/app/assets/models/chat_bubble/chat_bubble.glb.import @@ -30,5 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} -gltf/naming_version=1 +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/assets/models/sky_dome/scene.gltf.import b/app/assets/models/sky_dome/scene.gltf.import index 6efbee2..1f37b52 100644 --- a/app/assets/models/sky_dome/scene.gltf.import +++ b/app/assets/models/sky_dome/scene.gltf.import @@ -30,5 +30,5 @@ animation/trimming=false animation/remove_immutable_tracks=true import_script/path="" _subresources={} -gltf/naming_version=1 +gltf/naming_version=0 gltf/embedded_image_handling=1 diff --git a/app/assets/models/sky_dome/scene_0.png b/app/assets/models/sky_dome/scene_0.png new file mode 100644 index 0000000..269272e --- /dev/null +++ b/app/assets/models/sky_dome/scene_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2941c9ce2676249f899aea64a13012d3b2f52dcf239d5f0c09570f14a4433348 +size 7954301 diff --git a/app/assets/models/sky_dome/scene_0.png.import b/app/assets/models/sky_dome/scene_0.png.import new file mode 100644 index 0000000..322fac0 --- /dev/null +++ b/app/assets/models/sky_dome/scene_0.png.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0vi854404eve" +path.s3tc="res://.godot/imported/scene_0.png-9a0770c34bd6836b2bfe1d70dcace53b.s3tc.ctex" +path.etc2="res://.godot/imported/scene_0.png-9a0770c34bd6836b2bfe1d70dcace53b.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} +generator_parameters={} + +[deps] + +source_file="res://assets/models/sky_dome/scene_0.png" +dest_files=["res://.godot/imported/scene_0.png-9a0770c34bd6836b2bfe1d70dcace53b.s3tc.ctex", "res://.godot/imported/scene_0.png-9a0770c34bd6836b2bfe1d70dcace53b.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/app/assets/models/sky_dome/scene_4.png b/app/assets/models/sky_dome/scene_4.png new file mode 100644 index 0000000..882790f --- /dev/null +++ b/app/assets/models/sky_dome/scene_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e93ccbdefa9cb54244f602dd3361a296a51c1cf1b4ca69344408b3f81853c6d7 +size 7587197 diff --git a/app/assets/models/sky_dome/scene_4.png.import b/app/assets/models/sky_dome/scene_4.png.import new file mode 100644 index 0000000..39d249f --- /dev/null +++ b/app/assets/models/sky_dome/scene_4.png.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ds78ylaicfb3u" +path.s3tc="res://.godot/imported/scene_4.png-a9e7d42c59bc553f60827e5915c54b69.s3tc.ctex" +path.etc2="res://.godot/imported/scene_4.png-a9e7d42c59bc553f60827e5915c54b69.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} +generator_parameters={} + +[deps] + +source_file="res://assets/models/sky_dome/scene_4.png" +dest_files=["res://.godot/imported/scene_4.png-a9e7d42c59bc553f60827e5915c54b69.s3tc.ctex", "res://.godot/imported/scene_4.png-a9e7d42c59bc553f60827e5915c54b69.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/app/content/entities/sensor/sensor.gd b/app/content/entities/sensor/sensor.gd index 998c0e6..cb82054 100644 --- a/app/content/entities/sensor/sensor.gd +++ b/app/content/entities/sensor/sensor.gd @@ -5,6 +5,9 @@ const Entity = preload ("../entity.gd") @onready var label: Label3D = $Label @onready var collision_shape = $CollisionShape3D +var sensor_data = {} +var unit = null + # Called when the node enters the scene tree for the first time. func _ready(): super() @@ -17,14 +20,21 @@ func _ready(): ) func set_text(stateInfo): + if stateInfo == null: + return + var text = stateInfo["state"] if stateInfo["attributes"]["friendly_name"] != null: text = stateInfo["attributes"]["friendly_name"] + "\n" + text if stateInfo["attributes"].has("unit_of_measurement")&&stateInfo["attributes"]["unit_of_measurement"] != null: + unit = stateInfo["attributes"]["unit_of_measurement"] text += " " + stateInfo["attributes"]["unit_of_measurement"] + if stateInfo["attributes"].has("device_class"): + sensor_data[stateInfo["attributes"]["device_class"]] = stateInfo["state"] + label.text = text var font = label.get_font() @@ -35,3 +45,15 @@ func set_text(stateInfo): collision_shape.shape.size.x = size.x * label.pixel_size * 0.5 collision_shape.shape.size.y = size.y * label.pixel_size * 0.25 + +func get_sensor_data(type: String): + if sensor_data.has(type) == false: + return null + + return sensor_data[type] + +func get_sensor_unit(type: String): + if sensor_data.has(type) == false: + return null + + return unit diff --git a/app/content/main.gd b/app/content/main.gd index dbf478b..86af213 100644 --- a/app/content/main.gd +++ b/app/content/main.gd @@ -22,7 +22,7 @@ func _ready(): else: RenderingServer.set_debug_generate_wireframes(true) - update_voice_assistant() + create_voice_assistant() controller_left.button_pressed.connect(func(name): _emit_action(name, true, false) @@ -47,7 +47,7 @@ func _ready(): if action.name == "menu_button": toggle_menu() elif action.name == "by_button": - House.body.mini_view=!House.body.mini_view + House.body.mini_view.small.value=!House.body.mini_view.small.value ) EventSystem.on_focus_in.connect(func(event): @@ -65,16 +65,20 @@ func _ready(): remove_child(keyboard) ) -func update_voice_assistant(): +func create_voice_assistant(): if Store.settings.is_loaded() == false: await Store.settings.on_loaded - if Store.settings.voice_assistant&&voice_assistant == null: - voice_assistant = VoiceAssistant.instantiate() - add_child(voice_assistant) - elif !Store.settings.voice_assistant&&voice_assistant != null: - remove_child(voice_assistant) - voice_assistant.queue_free() + var settings_store = Store.settings.state + + R.effect(func(_arg): + if settings_store.voice_assistant == true&&voice_assistant == null: + voice_assistant=VoiceAssistant.instantiate() + add_child(voice_assistant) + elif settings_store.voice_assistant == false&&voice_assistant != null: + remove_child(voice_assistant) + voice_assistant.queue_free() + ) func toggle_menu(): if menu.show_menu == false: diff --git a/app/content/system/house/align_reference.gd b/app/content/system/house/align_reference.gd index 0118f58..c4e1242 100644 --- a/app/content/system/house/align_reference.gd +++ b/app/content/system/house/align_reference.gd @@ -48,8 +48,8 @@ func get_new_transform(): return marker.global_transform func update_align_reference(): - corner1.global_position = Store.house.align_position1 - corner2.global_position = Store.house.align_position2 + corner1.global_position = Store.house.state.align_position1 + corner2.global_position = Store.house.state.align_position2 corner2.look_at(corner1.global_position, Vector3.UP) corner2.rotate(Vector3.UP, deg_to_rad( - 90)) @@ -57,5 +57,5 @@ func update_align_reference(): 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 + Store.house.state.align_position1 = corner1.global_position + Store.house.state.align_position2 = corner2.global_position \ No newline at end of file diff --git a/app/content/system/house/house.gd b/app/content/system/house/house.gd index 2edfeb5..92b2877 100644 --- a/app/content/system/house/house.gd +++ b/app/content/system/house/house.gd @@ -2,17 +2,16 @@ extends Node3D const Room = preload ("./room/room.tscn") const RoomType = preload ("./room/room.gd") +const Miniature = preload ("./mini/miniature.gd") +const AlignReference = preload ("./align_reference.gd") @onready var levels = $Levels @onready var collision_shape = $Levels/CollisionShape3D -@onready var align_reference = $AlignReference +@onready var align_reference: AlignReference = $AlignReference +@onready var mini_view: Miniature = $Levels/Miniature var fixing_reference: bool = false var editing_room: RoomType = null -var mini_view: bool = false: - set(value): - mini_view = value - update_mini_view() func _ready(): Store.house.on_loaded.connect(func(): @@ -26,18 +25,18 @@ func update_house(): align_reference.update_align_reference() - for index in range(Store.house.rooms.size() - 1, -1, -1): - var new_room = Store.house.rooms[index] + for index in range(Store.house.state.rooms.size()): + var new_room = Store.house.state.rooms[index] if new_room.corners.size() == 0: - Store.house.rooms.remove_at(index) + Store.house.state.rooms.remove_at(index) Store.house.save_local() continue create_room(new_room.name, 0) - for entity_index in range(Store.house.entities.size()): - var entity = Store.house.entities[entity_index] + for entity_index in range(Store.house.state.entities.size()): + var entity = Store.house.state.entities[entity_index] var entity_instance = create_entity_in(entity.id, entity.room) @@ -51,7 +50,7 @@ func create_room(room_name: String, level: int) -> RoomType: var existing_room = Store.house.get_room(room_name) if existing_room == null: - Store.house.rooms.append({ + Store.house.state.rooms.append({ "name": room_name, "height": 2.0, "corners": [], @@ -101,7 +100,7 @@ func delete_room(room_name): var store_room = Store.house.get_room(room_name) if store_room != null: - Store.house.rooms.erase(store_room) + Store.house.state.rooms.erase(store_room) Store.house.save_local() @@ -198,44 +197,6 @@ func create_entity_in(entity_id: String, room_name: String): return entity -func update_mini_view(): - collision_shape.disabled = !mini_view - - var tween = create_tween() - tween.set_parallel(true) - tween.set_trans(Tween.TRANS_CUBIC) - - if mini_view: - var aabb = get_level_aabb(0) - aabb.position.y = -0.03 - aabb.size.y = 0.06 - var center = aabb.position + aabb.size / 2.0 - - collision_shape.global_position = center - collision_shape.shape.size = aabb.size - - var camera = get_node("/root/Main/XROrigin3D/XRCamera3D") - var camera_position = camera.global_position - var camera_direction = -camera.global_transform.basis.z - - camera_position.y *= 0.5 - camera_direction.y = 0.0 - - var target_position = camera_position + camera_direction.normalized() * 0.2 - var new_position = target_position - center * 0.1 - tween.tween_property(levels, "global_position", new_position, 0.5) - tween.tween_property(levels, "scale", Vector3(0.1, 0.1, 0.1), 0.5) - - for room in get_rooms(0): - room.state_machine.change_to("Mini") - else: - tween.tween_property(levels, "global_position", Vector3(0, 0, 0), 0.5) - tween.tween_property(levels, "scale", Vector3(1.0, 1.0, 1.0), 0.5) - await tween.finished - - for room in get_rooms(0): - room.state_machine.change_to("View") - func edit_reference(): fixing_reference = false align_reference.visible = true @@ -270,7 +231,7 @@ func save_reference(): Store.house.save_local() func save_all_entities(): - Store.house.entities.clear() + Store.house.state.entities.clear() for room in get_rooms(0): for entity in room.get_node("Entities").get_children(): @@ -281,6 +242,6 @@ func save_all_entities(): "room": String(room.name) } - Store.house.entities.append(entity_data) + Store.house.state.entities.append(entity_data) Store.house.save_local() diff --git a/app/content/system/house/house.tscn b/app/content/system/house/house.tscn index b17ab66..17f7d78 100644 --- a/app/content/system/house/house.tscn +++ b/app/content/system/house/house.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=5 format=3 uid="uid://cbemihbxkd4ll"] +[gd_scene load_steps=6 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://ds60i5n211hi3" path="res://content/system/house/mini/miniature.tscn" id="4_qjbly"] [sub_resource type="BoxShape3D" id="BoxShape3D_x81up"] @@ -22,6 +23,8 @@ script = ExtResource("2_w1auk") restricted = true lock_rotation = true +[node name="Miniature" parent="Levels" instance=ExtResource("4_qjbly")] + [node name="AlignReference" parent="." instance=ExtResource("3_e1tcn")] visible = false disabled = true diff --git a/app/content/system/house/mini/humid_gradient.tres b/app/content/system/house/mini/humid_gradient.tres new file mode 100644 index 0000000..ac40fcb --- /dev/null +++ b/app/content/system/house/mini/humid_gradient.tres @@ -0,0 +1,11 @@ +[gd_resource type="GradientTexture1D" load_steps=2 format=3 uid="uid://gfemvrlwowg7"] + +[sub_resource type="Gradient" id="Gradient_2jwis"] +interpolation_color_space = 2 +offsets = PackedFloat32Array(0, 0.333333, 0.5, 1) +colors = PackedColorArray(0.999396, 0, 0.058647, 1, 1, 0.85, 0, 1, 0, 0.95, 1, 1, 0.0333333, 0, 1, 1) +metadata/_snap_enabled = true +metadata/_snap_count = 6 + +[resource] +gradient = SubResource("Gradient_2jwis") diff --git a/app/content/system/house/mini/mini_view_options.gd b/app/content/system/house/mini/mini_view_options.gd new file mode 100644 index 0000000..ab7dc6b --- /dev/null +++ b/app/content/system/house/mini/mini_view_options.gd @@ -0,0 +1,3 @@ +extends Node3D + +const mini_wall_shader: ShaderMaterial = preload ("./mini_wall.tres") diff --git a/app/content/system/house/mini/mini_wall.tres b/app/content/system/house/mini/mini_wall.tres new file mode 100644 index 0000000..d8f483f --- /dev/null +++ b/app/content/system/house/mini/mini_wall.tres @@ -0,0 +1,12 @@ +[gd_resource type="ShaderMaterial" load_steps=3 format=3 uid="uid://bcfcough6ucvc"] + +[ext_resource type="Shader" path="res://content/system/house/mini/mini_wall_shader.gdshader" id="1_sbr3e"] +[ext_resource type="Texture2D" uid="uid://bbuq4wn7e5o2q" path="res://content/system/house/mini/temp_gradient.tres" id="2_3lwi8"] + +[resource] +render_priority = 0 +shader = ExtResource("1_sbr3e") +shader_parameter/data = PackedFloat32Array() +shader_parameter/data_size = 0 +shader_parameter/alpha = 0.3 +shader_parameter/color_gradient = ExtResource("2_3lwi8") diff --git a/app/content/system/house/mini/mini_wall_shader.gdshader b/app/content/system/house/mini/mini_wall_shader.gdshader new file mode 100644 index 0000000..b11c4e6 --- /dev/null +++ b/app/content/system/house/mini/mini_wall_shader.gdshader @@ -0,0 +1,63 @@ +shader_type spatial; +render_mode blend_mix, depth_draw_opaque, cull_disabled, diffuse_lambert, specular_schlick_ggx, unshaded; + +uniform vec4 data[100]; +uniform int data_size: hint_range(0, 100, 1); +uniform float alpha: hint_range(0.0, 1.0, 0.1) = 0.3; +uniform sampler2D color_gradient; + +varying vec3 color; + +float simple_weight(int index, vec3 world_pos, float p) { + return 1.0 / pow(distance(data[index].xyz, world_pos), p); +} + +float sphere_weight(int index, vec3 world_pos, float r) { + float dist = distance(data[index].xyz, world_pos); + return pow(max(0, r - dist) / (r * dist), 2); +} + +void vertex() { + color = vec3(1.0, 1.0, 1.0); + + if(data_size > 0) { + float distances[100]; + float dist_sum = 0.0; + float data_sum = 0.0; + + float closest_dist = -1.0; + int closest_index = 0; + + // Calculate Global Coordinates + //vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + + closest_dist = distance(data[0].xyz, VERTEX); + + // Inverse distance weighting using Shepard's method + for(int i = 0; i < data_size; i++) { + distances[i] = sphere_weight(i, VERTEX, 5.0); + dist_sum += distances[i]; + data_sum += distances[i] * data[i].w; + + float dist = distance(data[i].xyz, VERTEX); + + if(dist < closest_dist) { + closest_dist = dist; + closest_index = i; + } + } + + float value = (1.0 / dist_sum) * data_sum; + + if( value > 0.0 || value < 1.0) { + color.xyz = texture(color_gradient, vec2(value, 0)).xyz; + } else { + color.xyz = texture(color_gradient, vec2(data[closest_index].w, 0)).xyz; + } + } +} + +void fragment() { + ALBEDO = vec3(color.xyz); + ALPHA = alpha; +} diff --git a/app/content/system/house/mini/miniature.gd b/app/content/system/house/mini/miniature.gd new file mode 100644 index 0000000..688e2fe --- /dev/null +++ b/app/content/system/house/mini/miniature.gd @@ -0,0 +1,188 @@ +extends Node3D + +const ConstructRoomMesh = preload ("res://lib/utils/mesh/construct_room_mesh.gd") +const wall_material = preload ("./mini_wall.tres") + +const humidity_gradient = preload ("./humid_gradient.tres") +const temperature_gradient = preload ("./temp_gradient.tres") + +@onready var body = $Body +@onready var model = $Body/Model +@onready var collision_shape = $Body/CollisionShape3D +@onready var toggle_heatmap = $Body/HeatmapButton + +enum HeatmapType { + NONE = 0, + TEMPERATURE = 1, + HUMIDITY = 2 +} + +var heatmap_type_strings = { + HeatmapType.NONE: "none", + HeatmapType.TEMPERATURE: "temperature", + HeatmapType.HUMIDITY: "humidity" +} + +var base_scale = { + HeatmapType.NONE: Vector2(0.0, 1.0), + HeatmapType.TEMPERATURE: Vector2( - 20.0, 60.0), + HeatmapType.HUMIDITY: Vector2(0.0, 100.0) +} +var selected_scale = R.state(Vector2(0.0, 1.0)) +var opacity = R.state(30) +var heatmap_type = R.state(HeatmapType.NONE) +var small = R.state(false) + +func _ready(): + wall_material.set_shader_parameter("data", []) + wall_material.set_shader_parameter("data_size", 0) + + if Store.house.is_loaded() == false: + await Store.house.on_loaded + + # Update Room Mesh + R.effect(func(_arg): + if Store.house.state.rooms.size() == 0: + return + + if heatmap_type.value == HeatmapType.NONE&&small.value == false: + return + + for child in model.get_children(): + model.remove_child(child) + child.free() + + for room in Store.house.state.rooms: + var walls_mesh=MeshInstance3D.new() + var floor_mesh=MeshInstance3D.new() + + model.add_child(walls_mesh) + model.add_child(floor_mesh) + + walls_mesh.mesh=ConstructRoomMesh.generate_wall_mesh_grid(room.corners, room.height) + floor_mesh.mesh=ConstructRoomMesh.generate_ceiling_mesh_grid(room.corners) + + walls_mesh.material_override=wall_material + floor_mesh.material_override=wall_material + ) + + # Update Size + R.effect(func(_arg): + collision_shape.disabled=small.value == false + + var tween:=create_tween() + tween.set_parallel(true) + if small.value: + + var aabb=House.body.get_level_aabb(0) + aabb.position.y=- 0.03 + aabb.size.y=0.06 + + var center=aabb.position + aabb.size / 2 + + collision_shape.shape.size=aabb.size * 0.1 + + var camera=$"/root/Main/XROrigin3D/XRCamera3D" + var camera_position=camera.global_position + var camera_direction=- camera.global_transform.basis.z + + camera_position.y *= 0.5 + camera_direction.y=0 + + var target_position=camera_position + camera_direction.normalized() * 0.2 + var new_position=target_position - center * 0.1 + + tween.tween_property(model, "scale", Vector3(0.1, 0.1, 0.1), 0.5) + tween.tween_property(body, "position", new_position, 0.5) + else: + tween.tween_property(model, "scale", Vector3.ONE, 0.5) + tween.tween_property(body, "position", Vector3.ZERO, 0.5) + tween.tween_property(body, "quaternion", Quaternion.IDENTITY, 0.5) + ) + + # Update Walls + R.effect(func(_arg): + var show_map=heatmap_type.value != HeatmapType.NONE + var show_small=small.value + + for child in model.get_children(): + child.visible=show_map||show_small + ) + + # Update Heatmap + R.effect(func(_arg): + if heatmap_type.value != HeatmapType.NONE: + EventSystem.on_slow_tick.connect(update_data) + if heatmap_type.value == HeatmapType.TEMPERATURE: + wall_material.set_shader_parameter("color_gradient", temperature_gradient) + else: + wall_material.set_shader_parameter("color_gradient", humidity_gradient) + else: + EventSystem.on_slow_tick.disconnect(update_data) + wall_material.set_shader_parameter("data", []) + wall_material.set_shader_parameter("data_size", 0) + ) + + R.effect(func(_arg): + wall_material.set_shader_parameter("alpha", opacity.value / 100.0) + ) + +const SensorEntity = preload ("res://content/entities/sensor/sensor.gd") + +func get_base_scale() -> Vector2: + return base_scale[heatmap_type.value] + +func get_sensor_data(): + var data_list = [] + + for room in House.body.get_rooms(0): + for entity in room.get_node("Entities").get_children(): + if entity is SensorEntity: + var sensor = entity as SensorEntity + var data = sensor.get_sensor_data(heatmap_type_strings[heatmap_type.value]) + if data == null: + continue + + var sensor_pos = House.body.to_local(sensor.global_position) + + data_list.append(Vector4(sensor_pos.x, sensor_pos.y, sensor_pos.z, float(data))) + + return data_list + +func get_sensor_unit(): + for room in House.body.get_rooms(0): + for entity in room.get_node("Entities").get_children(): + if entity is SensorEntity: + var sensor = entity as SensorEntity + var sensor_unit = sensor.get_sensor_unit(heatmap_type_strings[heatmap_type.value]) + if sensor_unit != null: + return sensor_unit + + return "" + +func get_sensor_scale(): + var data = get_sensor_data() + var minmax + + for sensor in data: + if minmax == null: + minmax = Vector2(sensor.w, sensor.w) + else: + minmax.x = min(sensor.w, minmax.x) + minmax.y = max(sensor.w, minmax.y) + + if minmax == null: + return Vector2(0.0, 1.0) + + return minmax + +func update_data(_delta: float) -> void: + var data_list = get_sensor_data() + + data_list = data_list.map(func(data: Vector4) -> Vector4: + data.w=clamp((data.w - selected_scale.value.x) / (selected_scale.value.y - selected_scale.value.x), 0.0, 1.0) + return data + ) + + wall_material.set_shader_parameter("data", data_list) + wall_material.set_shader_parameter("data_size", data_list.size()) diff --git a/app/content/system/house/mini/miniature.tscn b/app/content/system/house/mini/miniature.tscn new file mode 100644 index 0000000..885837a --- /dev/null +++ b/app/content/system/house/mini/miniature.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=4 format=3 uid="uid://ds60i5n211hi3"] + +[ext_resource type="Script" path="res://content/system/house/mini/miniature.gd" id="1_b53yn"] +[ext_resource type="Script" path="res://content/functions/movable.gd" id="2_x7oed"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_bckw3"] + +[node name="Miniature" type="Node3D"] +script = ExtResource("1_b53yn") + +[node name="Body" type="StaticBody3D" parent="."] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Body"] +shape = SubResource("BoxShape3D_bckw3") + +[node name="Movable" type="Node" parent="Body"] +script = ExtResource("2_x7oed") + +[node name="Model" type="Node3D" parent="Body"] diff --git a/app/content/system/house/mini/temp_gradient.tres b/app/content/system/house/mini/temp_gradient.tres new file mode 100644 index 0000000..c55abeb --- /dev/null +++ b/app/content/system/house/mini/temp_gradient.tres @@ -0,0 +1,11 @@ +[gd_resource type="GradientTexture1D" load_steps=2 format=3 uid="uid://bbuq4wn7e5o2q"] + +[sub_resource type="Gradient" id="Gradient_2jwis"] +interpolation_color_space = 2 +offsets = PackedFloat32Array(0.00840336, 0.1, 0.4375, 0.5625, 1) +colors = PackedColorArray(0.533333, 0, 1, 1, 0, 0.45, 1, 1, 0, 0.95, 1, 1, 0.983333, 1, 0, 1, 0.999396, 0, 0.058647, 1) +metadata/_snap_enabled = true +metadata/_snap_count = 16 + +[resource] +gradient = SubResource("Gradient_2jwis") diff --git a/app/content/system/house/room/room.gd b/app/content/system/house/room/room.gd index 7c76b8f..f2ed2f6 100644 --- a/app/content/system/house/room/room.gd +++ b/app/content/system/house/room/room.gd @@ -1,10 +1,12 @@ extends Node3D +const ConstructRoomMesh = preload ("res://lib/utils/mesh/construct_room_mesh.gd") + @onready var wall_corners = $Ceiling/WallCorners @onready var wall_edges = $Ceiling/WallEdges @onready var wall_mesh: MeshInstance3D = $WallMesh @onready var ceiling_mesh: MeshInstance3D = $CeilingMesh -@onready var wall_collisions = $WallCollisions +@onready var wall_collision = $WallCollision/CollisionShape3D @onready var room_floor = $Floor @onready var room_ceiling = $Ceiling @@ -55,75 +57,12 @@ 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): - if room_store.corners.size() < 2: - return null + var corners = room_store.corners + var height = room_store.height - 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 + return ConstructRoomMesh.generate_wall_mesh(corners, height) 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 ConstructRoomMesh.generate_ceiling_mesh(corners) diff --git a/app/content/system/house/room/room.tscn b/app/content/system/house/room/room.tscn index 36a4f70..e86326e 100644 --- a/app/content/system/house/room/room.tscn +++ b/app/content/system/house/room/room.tscn @@ -1,9 +1,8 @@ -[gd_scene load_steps=9 format=3 uid="uid://bswgmclohuqui"] +[gd_scene load_steps=8 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"] [ext_resource type="Script" path="res://lib/utils/state_machine/state_machine.gd" id="4_nbbo6"] -[ext_resource type="Script" path="res://content/system/house/room/states/mini.gd" id="6_g4qca"] [ext_resource type="Script" path="res://content/system/house/room/states/view.gd" id="6_g066t"] [ext_resource type="Script" path="res://content/system/house/room/states/edit.gd" id="7_ap14h"] @@ -29,7 +28,11 @@ script = ExtResource("1_ugebq") [node name="CeilingMesh" type="MeshInstance3D" parent="."] -[node name="WallCollisions" type="Node3D" parent="."] +[node name="WallCollision" type="StaticBody3D" parent="."] +collision_layer = 24 +collision_mask = 0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="WallCollision"] [node name="Ceiling" type="StaticBody3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) @@ -57,7 +60,4 @@ script = ExtResource("6_g066t") [node name="Edit" type="Node" parent="StateMachine"] script = ExtResource("7_ap14h") -[node name="Mini" type="Node" parent="StateMachine"] -script = ExtResource("6_g4qca") - [node name="Entities" type="Node3D" parent="."] diff --git a/app/content/system/house/room/states/edit.gd b/app/content/system/house/room/states/edit.gd index e17a000..b11fb4f 100644 --- a/app/content/system/house/room/states/edit.gd +++ b/app/content/system/house/room/states/edit.gd @@ -1,8 +1,8 @@ extends RoomState -const wall_corner_scene = preload("../wall_corner.tscn") -const wall_edge_scene = preload("../wall_edge.tscn") -const RoomState = preload("./room_state.gd") +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 deleting: bool = false @@ -27,7 +27,7 @@ func _on_enter(): for i in range(1, corners.size()): add_corner(room.to_local(Vector3(corners[i].x, 0, corners[i].y))) - room.room_ceiling.get_node("CollisionShape3D").disabled = (floor_corner == null && height_corner == null) + room.room_ceiling.get_node("CollisionShape3D").disabled = (floor_corner == null&&height_corner == null) room.room_floor.get_node("CollisionShape3D").disabled = false var ceiling_shape = WorldBoundaryShape3D.new() @@ -73,7 +73,7 @@ func remove_corner(index: int): get_edge(index).queue_free() func _on_click_floor(event): - if floor_corner != null && height_corner != null: + if floor_corner != null&&height_corner != null: return add_floor_corner(event.ray.get_collision_point()) @@ -81,7 +81,7 @@ func _on_click_floor(event): room.room_ceiling.get_node("CollisionShape3D").disabled = false func _on_click_ceiling(event): - if floor_corner == null || height_corner == null || event.target != room.room_ceiling: + if floor_corner == null||height_corner == null||event.target != room.room_ceiling: return var pos = event.ray.get_collision_point() @@ -97,39 +97,39 @@ func add_floor_corner(position: Vector3): 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: + if !is_active()||moving != null: return - moving = event.target + moving=event.target ) floor_corner.get_node("Clickable").on_grab_move.connect(func(event): if moving == null: return - var moving_index = height_corner.get_index() - 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) + var moving_index=height_corner.get_index() + 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: return - moving.position = new_position + moving.position=new_position height_edge.align_to_corners(new_position, new_position + Vector3.UP * room.room_ceiling.global_position.y) - get_corner(moving_index).position.x = new_position.x - 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 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) + 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): - moving = null + moving=null ) room.add_child(floor_corner) @@ -141,96 +141,95 @@ func add_height_corner(position: Vector3): height_corner.position.z = position.z height_corner.get_node("Clickable").on_grab_down.connect(func(event): - if !is_active() || moving != null: + if !is_active()||moving != null: return - moving = event.target + 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 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 plane=Plane(plane_direction, moving.position) - var new_position = plane.intersects_ray(event.ray.global_position, direction) + var new_position=plane.intersects_ray(event.ray.global_position, direction) if new_position == null: return - room.room_ceiling.position.y = new_position.y + room.room_ceiling.position.y=new_position.y height_edge.align_to_corners(floor_corner.position, floor_corner.position + Vector3.UP * room.room_ceiling.position.y) ) height_corner.get_node("Clickable").on_grab_up.connect(func(_event): - moving = null + moving=null ) room.wall_corners.add_child(height_corner) -func add_corner(position: Vector3, index: int = -1): +func add_corner(position: Vector3, index: int=- 1): 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() || moving != null: + if !is_active()||moving != null: return - moving = event.target + moving=event.target ) corner.get_node("Clickable").on_grab_move.connect(func(event): if moving == null: return - var moving_index = moving.get_index() - 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) + var moving_index=moving.get_index() + 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: - deleting = true + deleting=true - new_position = event.ray.global_position + direction + new_position=event.ray.global_position + direction - 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 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 + get_edge(moving_index - 1).transform=get_edge(moving_index).transform return - deleting = false + deleting=false - new_position.y = 0 + new_position.y=0 - moving.position = new_position - + moving.position=new_position if room.wall_edges.get_child_count() == 0: return 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) + 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() + var moving_index=moving.get_index() remove_corner(moving_index) - moving = null - deleting = false + moving=null + deleting=false ) room.wall_corners.add_child(corner) @@ -243,22 +242,20 @@ func add_corner(position: Vector3, index: int = -1): if num_corners > 2: if num_corners != room.wall_edges.get_child_count(): - add_edge(get_corner(-2).position, get_corner(-1).position, -2) + add_edge(get_corner( - 2).position, get_corner( - 1).position, -2) else: 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): +func add_edge(from_pos: Vector3, to_pos: Vector3, index: int=- 1): var edge: StaticBody3D = wall_edge_scene.instantiate() edge.align_to_corners(from_pos, to_pos) edge.get_node("Clickable").on_press_down.connect(func(event): - var point = event.ray.get_collision_point() - point.y = 0 + var point=event.ray.get_collision_point() + point.y=0 add_corner(point, edge.get_index() + 1) ) - room.wall_edges.add_child(edge) room.wall_edges.move_child(edge, index) return edge @@ -277,4 +274,7 @@ func update_store(): store_room.corners = corners store_room.height = room.room_ceiling.position.y + # Manually update the array + Store.house.state.rooms = Store.house.state.rooms + Store.house.save_local() diff --git a/app/content/system/house/room/states/mini.gd b/app/content/system/house/room/states/mini.gd deleted file mode 100644 index 394db7a..0000000 --- a/app/content/system/house/room/states/mini.gd +++ /dev/null @@ -1,41 +0,0 @@ -extends RoomState - -const RoomState = preload("./room_state.gd") -const walls_mini_material = preload("../walls_mini.tres") -const walls_material = preload("../walls.tres") - -func _on_enter(): - room.wall_mesh.visible = true - room.ceiling_mesh.visible = false - room.wall_mesh.material_override = walls_mini_material - room.room_ceiling.get_node("CollisionShape3D").disabled = true - room.room_floor.get_node("CollisionShape3D").disabled = true - - for collision in room.wall_collisions.get_children(): - collision.get_child(0).disabled = true - - for corner in room.wall_corners.get_children(): - corner.get_node("CollisionShape3D").disabled = true - - for edge in room.wall_edges.get_children(): - edge.get_node("CollisionShape3D").disabled = true - - - -func _on_leave(): - room.wall_mesh.visible = false - room.ceiling_mesh.visible = false - room.wall_mesh.material_override = walls_material - room.room_ceiling.get_node("CollisionShape3D").disabled = false - room.room_floor.get_node("CollisionShape3D").disabled = false - - for collision in room.wall_collisions.get_children(): - collision.get_child(0).disabled = false - - for corner in room.wall_corners.get_children(): - corner.get_node("CollisionShape3D").disabled = false - - for edge in room.wall_edges.get_children(): - edge.get_node("CollisionShape3D").disabled = false - - diff --git a/app/content/system/house/room/states/room_state.gd b/app/content/system/house/room/states/room_state.gd index 31e6877..567079b 100644 --- a/app/content/system/house/room/states/room_state.gd +++ b/app/content/system/house/room/states/room_state.gd @@ -1,5 +1,5 @@ -extends State -const Room = preload("res://content/system/house/room/room.gd") +extends MachineState +const Room = preload ("res://content/system/house/room/room.gd") var room: Room diff --git a/app/content/system/house/room/states/view.gd b/app/content/system/house/room/states/view.gd index ade276a..0bd1552 100644 --- a/app/content/system/house/room/states/view.gd +++ b/app/content/system/house/room/states/view.gd @@ -28,51 +28,13 @@ func _on_enter(): 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 - - 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) + var wall_collisions = room.wall_mesh.mesh.create_trimesh_shape() + wall_collisions.backface_collision = true + room.wall_collision.shape = wall_collisions func _on_leave(): room.room_ceiling.get_node("CollisionShape3D").disabled = true room.room_floor.get_node("CollisionShape3D").disabled = true - for collision in room.wall_collisions.get_children(): - collision.queue_free() - await collision.tree_exited - -func generate_collision(): - var room_store = Store.house.get_room(room.name) - - var collision_shapes: Array[CollisionShape3D] = [] - - 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 - corner).length(), room_store.height, 0.04) - - var transform = Transform3D() - var back_vector = (corner - next_corner).cross(Vector3.UP).normalized() * shape.size.z / 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() - - collision_shape.shape = shape - collision_shape.transform = transform - - collision_shapes.append(collision_shape) - - return collision_shapes + room.wall_collision.shape = null diff --git a/app/content/system/house/room/walls.tres b/app/content/system/house/room/wall.tres similarity index 100% rename from app/content/system/house/room/walls.tres rename to app/content/system/house/room/wall.tres diff --git a/app/content/system/house/room/walls_mini.tres b/app/content/system/house/room/walls_mini.tres deleted file mode 100644 index ead6994..0000000 --- a/app/content/system/house/room/walls_mini.tres +++ /dev/null @@ -1,6 +0,0 @@ -[gd_resource type="StandardMaterial3D" format=3 uid="uid://dxyuncqxagt28"] - -[resource] -transparency = 1 -cull_mode = 2 -albedo_color = Color(1, 1, 1, 0.47451) diff --git a/app/content/ui/components/button/button.gd b/app/content/ui/components/button/button.gd index 71ccca3..740f69e 100644 --- a/app/content/ui/components/button/button.gd +++ b/app/content/ui/components/button/button.gd @@ -5,6 +5,7 @@ class_name Button3D signal on_button_down() signal on_button_up() +signal on_toggled(active: bool) const IconFont = preload ("res://assets/icons/icons.tres") const ECHO_WAIT_INITIAL = 0.5 @@ -57,6 +58,10 @@ const ECHO_WAIT_REPEAT = 0.1 var active: bool = false: set(value): + if active == value: + return + + on_toggled.emit(value) active = value if !is_inside_tree(): return update_animation(1.0 if active else 0.0) diff --git a/app/content/ui/components/tabs/tabs.gd b/app/content/ui/components/tabs/tabs.gd index c31150d..21c6c95 100644 --- a/app/content/ui/components/tabs/tabs.gd +++ b/app/content/ui/components/tabs/tabs.gd @@ -3,29 +3,29 @@ class_name Tabs3D signal on_select(selected: int) -var selected: Node3D: - set(value): - if selected == value: - return +var selected = R.state(null) - if selected != null: - selected.active = false - - selected = value - selected.active = true - on_select.emit(selected.get_index()) @export var initial_selected: Node3D func _ready(): - if initial_selected != null: - selected = initial_selected + if initial_selected: + selected.value = initial_selected + + R.effect(func(_arg): + on_select.emit(selected.value) + ) for option in get_children(): if option is Button3D == false: continue option.on_button_down.connect(func(): - selected=option + selected.value=option + ) + + R.effect(func(_arg): + option.active=option == selected.value + option.disabled=option == selected.value ) option.toggleable = true diff --git a/app/content/ui/components/tabs/tabs_content.gd b/app/content/ui/components/tabs/tabs_content.gd index 73d3052..181d298 100644 --- a/app/content/ui/components/tabs/tabs_content.gd +++ b/app/content/ui/components/tabs/tabs_content.gd @@ -8,15 +8,14 @@ var children: Array = [] func _ready(): children = get_children() - for child in children: + for i in range(children.size()): + var child = children[i] child.visible = true - if tabs.selected != null && tabs.selected.get_index() == child.get_index(): - continue remove_child(child) - tabs.on_select.connect(func(index): - for child in get_children(): - remove_child(child) - - add_child(children[index]) - ) + R.effect(func(_arg): + if tabs.selected.value.get_index() == i: + add_child(child) + else: + remove_child(child) + ) diff --git a/app/content/ui/menu/edit/edit_menu.gd b/app/content/ui/menu/edit/edit_menu.gd index 9769d8c..84fa240 100644 --- a/app/content/ui/menu/edit/edit_menu.gd +++ b/app/content/ui/menu/edit/edit_menu.gd @@ -32,6 +32,15 @@ func _enter_tree(): func load_devices(): if devices.size() == 0: devices = await HomeApi.get_devices() + devices.sort_custom(func(a, b): + return a.values()[0]["name"].to_lower() < b.values()[0]["name"].to_lower() + ) + + for device in devices: + device.values()[0]["entities"].sort_custom(func(a, b): + return a.to_lower() < b.to_lower() + ) + render() HomeApi.on_disconnect.connect(func(): diff --git a/app/content/ui/menu/room/views/rooms.gd b/app/content/ui/menu/room/views/rooms.gd index 298e13c..803f87c 100644 --- a/app/content/ui/menu/room/views/rooms.gd +++ b/app/content/ui/menu/room/views/rooms.gd @@ -1,7 +1,7 @@ extends Node3D const Room = preload ("res://content/system/house/room/room.tscn") -const RoomType = preload ("res://content/system/house/room/room.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") @@ -102,7 +102,7 @@ func _on_click(event: EventPointer): selected_room = room_name func _generate_room_map(): - var rooms = Store.house.rooms + var rooms = Store.house.state.rooms var target_size = Vector2(0.2, 0.24) var target_offset = Vector2(0, 0.05) @@ -128,7 +128,7 @@ func _generate_room_map(): current_max.y = max(current_max.y, corner.y) for room in rooms: - var mesh = RoomType.generate_ceiling_mesh(room) + var mesh = ConstructRoomMesh.generate_ceiling_mesh(room.corners) if mesh == null: continue diff --git a/app/content/ui/menu/settings/settings_menu.gd b/app/content/ui/menu/settings/settings_menu.gd index 51e3afc..226f03f 100644 --- a/app/content/ui/menu/settings/settings_menu.gd +++ b/app/content/ui/menu/settings/settings_menu.gd @@ -3,7 +3,6 @@ extends Node3D const credits_scene = preload ("./credits.tscn") @onready var connection_status = $Content/ConnectionStatus -@onready var main = $"/root/Main" @onready var input_url = $Content/InputURL @onready var input_token = $Content/InputToken @@ -15,6 +14,8 @@ const credits_scene = preload ("./credits.tscn") @onready var voice_assist = $Content/VoiceAssist func _ready(): + var settings_store = Store.settings.state + background.visible = false credits.on_click.connect(func(_event): @@ -25,12 +26,12 @@ func _ready(): ) if Store.settings.is_loaded(): - input_url.text = Store.settings.url - input_token.text = Store.settings.token + input_url.text = settings_store.url + input_token.text = settings_store.token else: Store.settings.on_loaded.connect(func(): - input_url.text=Store.settings.url - input_token.text=Store.settings.token + input_url.text=settings_store.url + input_token.text=settings_store.token ) button_connect.on_button_down.connect(func(): @@ -39,8 +40,8 @@ func _ready(): HomeApi.start_adapter("hass_ws", url, token) - Store.settings.url=url - Store.settings.token=token + settings_store.url=url + settings_store.token=token Store.settings.save_local() ) @@ -63,8 +64,7 @@ func _ready(): voice_assist.label="mic" - Store.settings.voice_assistant=true - main.update_voice_assistant() + settings_store.voice_assistant=true Store.settings.save_local() ) @@ -74,8 +74,7 @@ func _ready(): voice_assist.label="mic_off" - Store.settings.voice_assistant=false - main.update_voice_assistant() + settings_store.voice_assistant=false Store.settings.save_local() ) @@ -91,5 +90,9 @@ func _ready(): if Store.settings.is_loaded() == false: await Store.settings.on_loaded - voice_assist.label = "mic_off" if Store.settings.voice_assistant == false else "mic" - voice_assist.active = Store.settings.voice_assistant + var button_label = R.computed(func(_arg): + return "mic_off" if settings_store.voice_assistant == false else "mic" + ) + + R.bind(voice_assist, "label", button_label) + R.bind(voice_assist, "active", settings_store, "voice_assistant") diff --git a/app/content/ui/menu/view/view_menu.gd b/app/content/ui/menu/view/view_menu.gd index 11fa1b9..2fb3dd5 100644 --- a/app/content/ui/menu/view/view_menu.gd +++ b/app/content/ui/menu/view/view_menu.gd @@ -1,12 +1,93 @@ extends Node3D -@onready var mini_view = $Content/MiniView +const Miniature = preload ("res://content/system/house/mini/miniature.gd") + +@onready var mini_view_button = $Content/MiniView +@onready var heat_map_button = $Content/HeatMap +@onready var humudity_map_button = $Content/HumidityMap +@onready var min_slider = $Content/MinSlider +@onready var max_slider = $Content/MaxSlider +@onready var opacity_slider = $Content/OpacitySlider @onready var background = $Background func _ready(): background.visible = false - mini_view.on_button_down.connect(func(): - House.body.mini_view = !House.body.mini_view + if !House.body.is_node_ready(): + await House.body.ready + + var mini_view = House.body.mini_view + + mini_view_button.on_toggled.connect(func(active): + mini_view.small.value=active ) - + + heat_map_button.on_toggled.connect(func(active): + if active == false: + if mini_view.heatmap_type.value == Miniature.HeatmapType.TEMPERATURE: + mini_view.heatmap_type.value=Miniature.HeatmapType.NONE + return + + mini_view.heatmap_type.value=Miniature.HeatmapType.TEMPERATURE + ) + + humudity_map_button.on_toggled.connect(func(active): + if active == false: + if mini_view.heatmap_type.value == Miniature.HeatmapType.HUMIDITY: + mini_view.heatmap_type.value=Miniature.HeatmapType.NONE + return + + mini_view.heatmap_type.value=Miniature.HeatmapType.HUMIDITY + ) + + R.effect(func(_arg): + heat_map_button.active=mini_view.heatmap_type.value == Miniature.HeatmapType.TEMPERATURE + humudity_map_button.active=mini_view.heatmap_type.value == Miniature.HeatmapType.HUMIDITY + ) + + min_slider.on_value_changed.connect(func(value): + if value >= mini_view.selected_scale.value.y: + min_slider.value=mini_view.selected_scale.value.y + return + + mini_view.selected_scale.value.x=value + ) + + max_slider.on_value_changed.connect(func(value): + if value <= mini_view.selected_scale.value.x: + max_slider.value=mini_view.selected_scale.value.x + return + + mini_view.selected_scale.value.y=value + ) + + R.effect(func(_arg): + min_slider.value=mini_view.selected_scale.value.x + max_slider.value=mini_view.selected_scale.value.y + ) + + # Update Slider + R.effect(func(_arg): + var minmax=mini_view.get_base_scale() + min_slider.min=minmax.x + min_slider.max=minmax.y + + max_slider.min=minmax.x + max_slider.max=minmax.y + + var sensor_minmax=mini_view.get_sensor_scale() + + sensor_minmax.x=floor(sensor_minmax.x) + sensor_minmax.y=ceil(sensor_minmax.y) + + mini_view.selected_scale.value=sensor_minmax + min_slider.value=sensor_minmax.x + max_slider.value=sensor_minmax.y + + var unit=mini_view.get_sensor_unit() + + min_slider.label_unit=unit + max_slider.label_unit=unit + ) + + R.bind(opacity_slider, "value", mini_view.opacity, opacity_slider.on_value_changed) diff --git a/app/content/ui/menu/view/view_menu.tscn b/app/content/ui/menu/view/view_menu.tscn index 986c50b..03cac72 100644 --- a/app/content/ui/menu/view/view_menu.tscn +++ b/app/content/ui/menu/view/view_menu.tscn @@ -1,7 +1,9 @@ -[gd_scene load_steps=4 format=3 uid="uid://ddpxthb414unp"] +[gd_scene load_steps=6 format=3 uid="uid://ddpxthb414unp"] [ext_resource type="Script" path="res://content/ui/menu/view/view_menu.gd" id="1_hxajx"] [ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="2_qan1b"] +[ext_resource type="Material" uid="uid://bujy3egn1oqac" path="res://assets/materials/pri-500.material" id="2_se6ic"] +[ext_resource type="PackedScene" uid="uid://pk5k1q8bx0rj" path="res://content/ui/components/slider/slider.tscn" id="4_d3xhb"] [sub_resource type="BoxMesh" id="BoxMesh_qr3bi"] size = Vector3(0.3, 0.01, 0.3) @@ -11,6 +13,7 @@ script = ExtResource("1_hxajx") [node name="Background" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, 0, 0.15) +material_override = ExtResource("2_se6ic") mesh = SubResource("BoxMesh_qr3bi") [node name="Content" type="Node3D" parent="."] @@ -19,3 +22,85 @@ mesh = SubResource("BoxMesh_qr3bi") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.03, 0, 0.03) label = "view_comfy" icon = true +toggleable = true + +[node name="MiniViewLabel" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0.07, 0.01, 0.03) +pixel_size = 0.001 +text = "Mini View" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 + +[node name="HeatMap" parent="Content" instance=ExtResource("2_qan1b")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.03, 0, 0.09) +label = "device_thermostat" +icon = true +toggleable = true + +[node name="HeatMapLabel" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0.07, 0.01, 0.09) +pixel_size = 0.001 +text = "Heat Map" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 + +[node name="HumidityMap" parent="Content" instance=ExtResource("2_qan1b")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.03, 0, 0.15) +label = "humidity_mid" +icon = true +toggleable = true + +[node name="HumidityMapLabel" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0.07, 0.01, 0.15) +pixel_size = 0.001 +text = "Humidity Map" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 + +[node name="MinSlider" parent="Content" instance=ExtResource("4_d3xhb")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0.01, 0.2) +step = 1.0 +show_label = true +size = Vector3(10, 0.4, 1) + +[node name="MinValue" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0.15, 0.01, 0.2) +pixel_size = 0.001 +text = "Min Value" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 + +[node name="MaxSlider" parent="Content" instance=ExtResource("4_d3xhb")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0.01, 0.24) +step = 1.0 +show_label = true +size = Vector3(10, 0.4, 1) + +[node name="MaxValue" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0.15, 0.01, 0.24) +pixel_size = 0.001 +text = "Max Value" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 + +[node name="OpacitySlider" parent="Content" instance=ExtResource("4_d3xhb")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0.01, 0.28) +max = 100.0 +value = 30.0 +step = 10.0 +show_label = true +label_unit = "%" +size = Vector3(10, 0.4, 1) + +[node name="OpacityLabel" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0.15, 0.01, 0.28) +pixel_size = 0.001 +text = "Opacity" +font_size = 18 +outline_size = 0 +horizontal_alignment = 0 diff --git a/app/content/ui/onboarding/onboarding.gd b/app/content/ui/onboarding/onboarding.gd index b30bf4d..d917341 100644 --- a/app/content/ui/onboarding/onboarding.gd +++ b/app/content/ui/onboarding/onboarding.gd @@ -9,7 +9,7 @@ func _ready(): if Store.settings.is_loaded() == false: await Store.settings.on_loaded - if (Store.settings.url != ""&&Store.settings.url != null)||Store.settings.onboarding_complete: + if (Store.settings.state.url != ""&&Store.settings.state.url != null)||Store.settings.state.onboarding_complete: close() return @@ -24,7 +24,7 @@ func _ready(): EventSystem.on_slow_tick.connect(_slow_tick) func close(): - Store.settings.onboarding_complete = true + Store.settings.state.onboarding_complete = true Store.settings.save_local() queue_free() diff --git a/app/lib/globals/home_api.gd b/app/lib/globals/home_api.gd index 1b37a7a..fbaa024 100644 --- a/app/lib/globals/home_api.gd +++ b/app/lib/globals/home_api.gd @@ -33,7 +33,8 @@ func _ready(): var success = Store.settings.load_local() if success: - start_adapter(Store.settings.type.to_lower(), Store.settings.url, Store.settings.token) + 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 func start_adapter(type: String, url: String, token: String): diff --git a/app/lib/globals/house_body.gd b/app/lib/globals/house_body.gd index 5ea5d3c..2339687 100644 --- a/app/lib/globals/house_body.gd +++ b/app/lib/globals/house_body.gd @@ -1,4 +1,6 @@ extends Node ## Shortcut to get the House node from the Main scene -@onready var body = get_node_or_null("/root/Main/House") \ No newline at end of file +const HouseClass = preload ("res://content/system/house/house.gd") + +@onready var body: HouseClass = get_node_or_null("/root/Main/House") \ No newline at end of file diff --git a/app/lib/stores/devices.gd b/app/lib/stores/devices.gd index df2a8e2..c3bf2ab 100644 --- a/app/lib/stores/devices.gd +++ b/app/lib/stores/devices.gd @@ -1,5 +1,8 @@ extends StoreClass -const StoreClass = preload("./store.gd") +const StoreClass = preload ("./store.gd") + +func _init(): + self.state = R.state({}) func clear(): pass \ No newline at end of file diff --git a/app/lib/stores/house.gd b/app/lib/stores/house.gd index d135c6c..57de618 100644 --- a/app/lib/stores/house.gd +++ b/app/lib/stores/house.gd @@ -3,29 +3,31 @@ 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" + self.state = R.store({ + ## Type Room + ## name: String + ## corners: Vec2[] + ## height: float + "rooms": [], + ## Type Entity + ## id: String + ## position: Vec3 + ## rotation: Vec3 + ## room: String + "entities": [], + "align_position1": Vector3(), + "align_position2": Vector3() + }) + func clear(): - rooms = [] - entities = [] + self.state.rooms = [] + self.state.entities = [] func get_room(name): - for room in rooms: + for room in self.state.rooms: if room.name == name: return room return null \ No newline at end of file diff --git a/app/lib/stores/settings.gd b/app/lib/stores/settings.gd index 84d50fd..4ec83c2 100644 --- a/app/lib/stores/settings.gd +++ b/app/lib/stores/settings.gd @@ -3,23 +3,23 @@ extends StoreClass const StoreClass = preload ("./store.gd") -## The adapter to use for connecting with a backend -var type: String = "HASS_WS" -var url: String = "" -var token: String = "" - -## If the voice assistant should be enabled -var voice_assistant: bool = false - -## If the onboarding process has been completed -var onboarding_complete: bool = false - func _init(): _save_path = "user://settings.json" + self.state = R.store({ + ## The adapter to use for connecting with a backend + "type": "HASS_WS", + "url": "", + "token": "", + ## If the voice assistant should be enabled + "voice_assistant": false, + ## If the onboarding process has been completed + "onboarding_complete": false + }) + func clear(): - type = "HASS_WS" - url = "" - token = "" - voice_assistant = false - onboarding_complete = false \ No newline at end of file + self.state.type = "HASS_WS" + self.state.url = "" + self.state.token = "" + self.state.voice_assistant = false + self.state.onboarding_complete = false diff --git a/app/lib/stores/store.gd b/app/lib/stores/store.gd index fb058b3..a85e1a0 100644 --- a/app/lib/stores/store.gd +++ b/app/lib/stores/store.gd @@ -9,6 +9,7 @@ signal on_loaded ## Signal emitted when the data is saved. signal on_saved +var state: RdotStore var _loaded = false var _save_path = null @@ -20,44 +21,42 @@ func is_loaded(): func clear(): pass -func create_dict(): - var data: Dictionary = {} +func sanitizeState(dict=state): + var data = {} - for prop_info in get_property_list(): - if prop_info.name.begins_with("_")||prop_info.hint_string != "": + for prop_info in state.get_property_list(): + var key = prop_info.name + + if key.begins_with("_")||(prop_info.has("hint_string")&&prop_info.hint_string != ""): continue - var prop = get(prop_info.name) - - if prop is Store: - data[prop_info.name] = prop.create_dict() + if dict[key] is Dictionary: + data[key] = sanitizeState(dict[key]) else: - data[prop_info.name] = VariantSerializer.stringify_value(prop) + data[key] = VariantSerializer.stringify_value(dict[key]) return data -func use_dict(dict: Dictionary): - for prop_info in get_property_list(): - if prop_info.name.begins_with("_")||prop_info.hint_string != "": +func use_dict(dict: Dictionary, target=state): + for prop_info in state.get_property_list(): + var key = prop_info.name + + if key.begins_with("_")||(prop_info.has("hint_string")&&prop_info.hint_string != ""): continue - var prop = get(prop_info.name) - - if dict.has(prop_info.name) == false: + if dict.has(key) == false: continue - var prop_value = dict[prop_info.name] - - if prop is Store: - prop.use_dict(prop_value) + if target[key] is Dictionary: + use_dict(dict[key], target[key]) else: - set(prop_info.name, prop_value) + target[key] = dict[key] func save_local(path=_save_path): if path == null: return false - var data = create_dict() + var data = sanitizeState() var save_file = FileAccess.open(path, FileAccess.WRITE) diff --git a/app/lib/utils/mesh/construct_room_mesh.gd b/app/lib/utils/mesh/construct_room_mesh.gd new file mode 100644 index 0000000..2ee9f5f --- /dev/null +++ b/app/lib/utils/mesh/construct_room_mesh.gd @@ -0,0 +1,208 @@ +static func generate_wall_mesh(corners, height): + if corners.size() < 3: + return null + + var st = SurfaceTool.new() + var wall_up = Vector3.UP * height + + st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP) + + for corner in corners: + var corner3D = Vector3(corner.x, 0, corner.y) + + st.add_vertex(corner3D + wall_up) + st.add_vertex(corner3D) + + var first_corner = Vector3(corners[0].x, 0, corners[0].y) + + st.add_vertex(first_corner + wall_up) + st.add_vertex(first_corner) + + st.index() + st.generate_normals() + st.generate_tangents() + var mesh = st.commit() + + return mesh + +static func generate_ceiling_mesh(corners): + var points: PackedVector2Array = PackedVector2Array() + var edges: PackedInt32Array = PackedInt32Array() + var triangles: PackedInt32Array + + 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() + + return _create_mesh(points, triangles) + +static func generate_wall_mesh_grid(corners, height, grid: Vector2=Vector2(0.1, 0.1)): + if corners.size() < 3: + return null + + var st = SurfaceTool.new() + + st.begin(Mesh.PRIMITIVE_TRIANGLES) + + for corner_i in range(corners.size()): + var corner = Vector3(corners[corner_i].x, 0, corners[corner_i].y) + var next_index = (corner_i + 1) % corners.size() + var next_corner = Vector3(corners[next_index].x, 0, corners[next_index].y) + + var steps = ceil(Vector2((next_corner - corner).length() / grid.x, height / grid.y)) + + var forward_dir = (next_corner - corner).normalized() * grid.x + var up_dir = Vector3.UP * grid.y + + var close_distance = Vector2(1, 1) + + for y in range(0, steps.y): + + close_distance.x = 1 + + if y == steps.y - 1: + close_distance.y = fmod(height, grid.y) / grid.y + + for x in range(0, steps.x): + var point = corner + forward_dir * x + Vector3.UP * grid.y * y + + if x == steps.x - 1: + close_distance.x = fmod(corner.distance_to(next_corner), grid.x) / grid.x + + st.add_vertex(point) + st.add_vertex(point + forward_dir * close_distance.x) + st.add_vertex(point + up_dir * close_distance.y) + + st.add_vertex(point + forward_dir * close_distance.x) + st.add_vertex(point + forward_dir * close_distance.x + up_dir * close_distance.y) + st.add_vertex(point + up_dir * close_distance.y) + + st.index() + st.generate_normals() + st.generate_tangents() + var mesh = st.commit() + + return mesh + +static func generate_ceiling_mesh_grid(corners, grid: Vector2=Vector2(0.1, 0.1)): + var points: PackedVector2Array = PackedVector2Array() + var edges: PackedInt32Array = PackedInt32Array() + var triangles: PackedInt32Array + + if corners.size() < 3: + return null + + var min_val = Vector2(corners[0]) + var max_val = Vector2(corners[0]) + + for i in range(corners.size()): + var corner = corners[i] + + min_val.x = min(min_val.x, corner.x) + min_val.y = min(min_val.y, corner.y) + max_val.x = max(max_val.x, corner.x) + max_val.y = max(max_val.y, corner.y) + + points.append(Vector2(corner.x, corner.y)) + edges.append(i) + edges.append((i + 1) % corners.size()) + + var size = max_val - min_val + + # Subdivide edges to grid + for i in range(corners.size()): + var corner = corners[i] + var next_index = (i + 1) % corners.size() + var next_corner = corners[next_index] + + var steps = ceil(Vector2((next_corner - corner).length() / grid.x, size.y / grid.y)) + + var forward_dir = (next_corner - corner).normalized() * grid.x + + for x in range(1, steps.x): + var point = corner + forward_dir * x + + points.append(Vector2(point.x, point.y)) + + ## Fill points insde the polygon + for y in range(1, int(size.y / grid.y)): + var x_intersections: Array[float] = [] + + var y_start = Vector2(min_val.x, min_val.y + y * grid.y) + var y_end = Vector2(max_val.x, min_val.y + y * grid.y) + + for i in range(corners.size()): + var a = corners[i] + var b = corners[(i + 1) % corners.size()] + + var result = Geometry2D.segment_intersects_segment(a, b, y_start, y_end) + + if result != null: + x_intersections.append(result.x) + + var intersection_counter = 0 + + x_intersections.sort() + + for x in range(1, int(size.x / grid.x)): + var point = min_val + Vector2(x * grid.x, y * grid.y) + + for i in range(intersection_counter, x_intersections.size()): + if x_intersections[i] < point.x: + intersection_counter += 1 + + var color = Color(1, 1, 0) + + if intersection_counter % 2 == 1: + color = Color(1, 0, 1) + points.append(point) + + 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() + + return _create_mesh(points, triangles) + +static func _create_mesh(points: PackedVector2Array, triangles: PackedInt32Array): + 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 diff --git a/app/lib/utils/state_machine/state.gd b/app/lib/utils/state_machine/state.gd index b2a998f..2f874b2 100644 --- a/app/lib/utils/state_machine/state.gd +++ b/app/lib/utils/state_machine/state.gd @@ -1,6 +1,6 @@ extends Node ## Base Class for all states -class_name State +class_name MachineState var state_machine: StateMachine