Merge pull request #123 from Nitwel/miniature-2

Make miniature view standalone
This commit is contained in:
Nitwel 2024-04-10 16:58:23 +02:00 committed by GitHub
commit 0cf4b932f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 1670 additions and 390 deletions

View File

@ -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"]

View File

@ -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"]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

201
app/addons/rdot/Rdot.gd Normal file
View File

@ -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)

13
app/addons/rdot/array.gd Normal file
View File

@ -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]

View File

@ -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

225
app/addons/rdot/graph.gd Normal file
View File

@ -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 = []

38
app/addons/rdot/node.gd Normal file
View File

@ -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}"

50
app/addons/rdot/state.gd Normal file
View File

@ -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()

37
app/addons/rdot/store.gd Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2941c9ce2676249f899aea64a13012d3b2f52dcf239d5f0c09570f14a4433348
size 7954301

View File

@ -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

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e93ccbdefa9cb54244f602dd3361a296a51c1cf1b4ca69344408b3f81853c6d7
size 7587197

View File

@ -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

View File

@ -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

View File

@ -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:
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 !Store.settings.voice_assistant&&voice_assistant != null:
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:

View File

@ -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
Store.house.state.align_position1 = corner1.global_position
Store.house.state.align_position2 = corner2.global_position

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -0,0 +1,3 @@
extends Node3D
const mini_wall_shader: ShaderMaterial = preload ("./mini_wall.tres")

View File

@ -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")

View File

@ -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;
}

View File

@ -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())

View File

@ -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"]

View File

@ -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")

View File

@ -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)

View File

@ -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="."]

View File

@ -216,7 +216,6 @@ func add_corner(position: Vector3, index: int = -1):
moving.position=new_position
if room.wall_edges.get_child_count() == 0:
return
@ -247,7 +246,6 @@ func add_corner(position: Vector3, index: int = -1):
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):
var edge: StaticBody3D = wall_edge_scene.instantiate()
edge.align_to_corners(from_pos, to_pos)
@ -258,7 +256,6 @@ func add_edge(from_pos: Vector3, to_pos: Vector3, index: int = -1):
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()

View File

@ -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

View File

@ -1,4 +1,4 @@
extends State
extends MachineState
const Room = preload ("res://content/system/house/room/room.gd")
var room: Room

View File

@ -29,50 +29,12 @@ func _on_enter():
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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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():
R.effect(func(_arg):
if tabs.selected.value.get_index() == i:
add_child(child)
else:
remove_child(child)
add_child(children[index])
)

View File

@ -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():

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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")
const HouseClass = preload ("res://content/system/house/house.gd")
@onready var body: HouseClass = get_node_or_null("/root/Main/House")

View File

@ -1,5 +1,8 @@
extends StoreClass
const StoreClass = preload ("./store.gd")
func _init():
self.state = R.state({})
func clear():
pass

View File

@ -3,29 +3,31 @@ extends StoreClass
const StoreClass = preload ("./store.gd")
func _init():
_save_path = "user://house.json"
self.state = R.store({
## Type Room
## name: String
## corners: Vec2[]
## height: float
var rooms = []
"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"
"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

View File

@ -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
self.state.type = "HASS_WS"
self.state.url = ""
self.state.token = ""
self.state.voice_assistant = false
self.state.onboarding_complete = false

View File

@ -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)

View File

@ -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

View File

@ -1,6 +1,6 @@
extends Node
## Base Class for all states
class_name State
class_name MachineState
var state_machine: StateMachine