diff --git a/generator/edit/examples/shapurr.json b/generator/edit/examples/shapurr.json
index cde6fadd..d7d8fedd 100644
--- a/generator/edit/examples/shapurr.json
+++ b/generator/edit/examples/shapurr.json
@@ -1 +1 @@
-{"buffers":[{"byteLength":0,"uri":"data:application/octet-stream;base64,"}],"data":{"metadata":{"nodes":{"Node-1":{"position":{"x":727,"y":-188}},"Node-10":{"position":{"x":-616,"y":349}},"Node-12":{"position":{"x":-367,"y":-76}},"Node-13":{"position":{"x":1218,"y":-142}},"Node-14":{"position":{"x":-620,"y":564}},"Node-15":{"position":{"x":-368,"y":341}},"Node-16":{"position":{"x":-594,"y":793}},"Node-17":{"position":{"x":170,"y":-223}},"Node-18":{"position":{"x":37,"y":1674}},"Node-19":{"position":{"x":1560,"y":911}},"Node-2":{"position":{"x":1572,"y":-254}},"Node-20":{"position":{"x":656,"y":1738}},"Node-21":{"position":{"x":1235,"y":-704}},"Node-22":{"position":{"x":249,"y":1732}},"Node-23":{"position":{"x":1030,"y":-622}},"Node-24":{"position":{"x":1033,"y":-514}},"Node-25":{"position":{"x":1615,"y":1208}},"Node-26":{"position":{"x":1143,"y":1109}},"Node-27":{"position":{"x":840,"y":1202}},"Node-28":{"position":{"x":1356,"y":1512}},"Node-29":{"position":{"x":1109,"y":1527}},"Node-3":{"position":{"x":2304,"y":783}},"Node-30":{"position":{"x":1143,"y":922}},"Node-31":{"position":{"x":621,"y":901}},"Node-32":{"position":{"x":852,"y":998}},"Node-33":{"position":{"x":1130,"y":432}},"Node-34":{"position":{"x":871,"y":578}},"Node-35":{"position":{"x":860,"y":825}},"Node-36":{"position":{"x":387,"y":1378}},"Node-37":{"position":{"x":139,"y":1317}},"Node-38":{"position":{"x":-296,"y":2395}},"Node-39":{"position":{"x":-896,"y":2409}},"Node-40":{"position":{"x":-618,"y":2797}},"Node-41":{"position":{"x":-654,"y":2427}},"Node-42":{"position":{"x":-605,"y":2609}},"Node-43":{"position":{"x":-815,"y":2628}},"Node-44":{"position":{"x":-587,"y":1995}},"Node-45":{"position":{"x":-786,"y":2061}},"Node-46":{"position":{"x":-776,"y":2224}},"Node-47":{"position":{"x":1855,"y":2339}},"Node-48":{"position":{"x":1481,"y":2209}},"Node-49":{"position":{"x":1244,"y":2358}},"Node-5":{"position":{"x":482,"y":196}},"Node-50":{"position":{"x":1252,"y":2453}},"Node-51":{"position":{"x":1530,"y":2671}},"Node-52":{"position":{"x":1350,"y":2686}},"Node-53":{"position":{"x":1614,"y":2905}},"Node-54":{"position":{"x":-350,"y":-417}},"Node-55":{"position":{"x":-631,"y":-343}},"Node-56":{"position":{"x":-630,"y":-550}},"Node-57":{"position":{"x":-638,"y":-72}},"Node-58":{"position":{"x":-468,"y":-1075}},"Node-59":{"position":{"x":-718,"y":-844}},"Node-6":{"position":{"x":313,"y":-560}},"Node-60":{"position":{"x":-749,"y":-1053}},"Node-61":{"position":{"x":-743,"y":-1151}},"Node-62":{"position":{"x":-8,"y":1871}},"Node-63":{"position":{"x":-198,"y":1686}},"Node-64":{"position":{"x":982,"y":-150}},"Node-65":{"position":{"x":700,"y":59}},"Node-66":{"position":{"x":433,"y":1747}},"Node-67":{"position":{"x":-551,"y":1066}},"Node-68":{"position":{"x":-742,"y":1054}},"Node-69":{"position":{"x":-743,"y":1162}},"Node-70":{"position":{"x":476,"y":-72}},"Node-9":{"position":{"x":-29,"y":-228}}}},"nodes":{"Node-1":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/marching.MarchNode]","assignedInput":{"Domain":{"id":"Node-6","port":"Value"},"Field":{"id":"Node-70","port":"Union"},"Resolution":{"id":"Node-5","port":"Value"}}},"Node-10":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.24,"y":0.32919907960352635,"z":0.051100203537421535}}},"Node-12":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundCubeNode]","assignedInput":{"Size":{"id":"Node-57","port":"Value"}}},"Node-13":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.SmoothNormalsImplicitWeldNode]","assignedInput":{"Mesh":{"id":"Node-64","port":"Out"}}},"Node-14":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.24,"y":0.7514109011273641,"z":0.2}}},"Node-15":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundedConeNode]","assignedInput":{"A":{"id":"Node-10","port":"Value"},"B":{"id":"Node-14","port":"Value"},"Radius 1":{"id":"Node-16","port":"Value"}}},"Node-16":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.3}},"Node-17":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.MirrorNode]","assignedInput":{"Field":{"id":"Node-9","port":"Union"}}},"Node-18":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundCubeNode]","assignedInput":{"Size":{"id":"Node-63","port":"Value"}}},"Node-19":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Children.0":{"id":"Node-38","port":"Out"},"Material":{"id":"Node-33","port":"Out"},"Mesh":{"id":"Node-20","port":"Out"},"Rotation":{"id":"Node-36","port":"Out"},"Scale":{"id":"Node-28","port":"Out"},"Translation":{"id":"Node-30","port":"Out"}}},"Node-2":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Material":{"id":"Node-21","port":"Out"},"Mesh":{"id":"Node-13","port":"Out"}}},"Node-20":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.SmoothNormalsImplicitWeldNode]","assignedInput":{"Mesh":{"id":"Node-66","port":"Out"}}},"Node-21":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Color":{"id":"Node-23","port":"Value"},"Metallic Factor":{"id":"Node-24","port":"Float 64"}}},"Node-22":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/marching.MarchNode]","assignedInput":{"Field":{"id":"Node-18","port":"Field"},"Resolution":{"id":"Node-62","port":"Value"}}},"Node-23":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[github.com/EliCDavis/polyform/drawing/coloring.Color]","variable":"Fur Color"},"Node-24":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-25":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Children.0":{"id":"Node-38","port":"Out"},"Material":{"id":"Node-33","port":"Out"},"Mesh":{"id":"Node-20","port":"Out"},"Rotation":{"id":"Node-36","port":"Out"},"Scale":{"id":"Node-28","port":"Out"},"Translation":{"id":"Node-26","port":"Out"}}},"Node-26":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-32","port":"Additive"},"Y":{"id":"Node-35","port":"Value"},"Z":{"id":"Node-27","port":"Value"}}},"Node-27":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.4}},"Node-28":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-29","port":"Value"},"Y":{"id":"Node-29","port":"Value"},"Z":{"id":"Node-29","port":"Value"}}},"Node-29":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Eye Size"},"Node-3":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ManifestNode]","assignedInput":{"Models.0":{"id":"Node-2","port":"Out"},"Models.1":{"id":"Node-19","port":"Out"},"Models.2":{"id":"Node-25","port":"Out"},"Models.3":{"id":"Node-47","port":"Out"}}},"Node-30":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-31","port":"Value"},"Y":{"id":"Node-35","port":"Value"},"Z":{"id":"Node-27","port":"Value"}}},"Node-31":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.3}},"Node-32":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.InverseNode[float64]]","assignedInput":{"In":{"id":"Node-31","port":"Value"}}},"Node-33":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Metallic Factor":{"id":"Node-34","port":"Float 64"}}},"Node-34":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-35":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.15}},"Node-36":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/quaternion.FromEulerAngleNode]","assignedInput":{"Angle":{"id":"Node-37","port":"Value"}}},"Node-37":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":0,"z":0.785}}},"Node-38":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Material":{"id":"Node-44","port":"Out"},"Mesh":{"id":"Node-41","port":"Out"},"Scale":{"id":"Node-42","port":"Out"},"Translation":{"id":"Node-40","port":"Value"}}},"Node-39":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/primitives.UvSphereNode]"},"Node-40":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":0,"z":0.5}}},"Node-41":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.SmoothNormalsImplicitWeldNode]","assignedInput":{"Mesh":{"id":"Node-39","port":"Out"}}},"Node-42":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-43","port":"Value"},"Y":{"id":"Node-43","port":"Value"},"Z":{"id":"Node-43","port":"Value"}}},"Node-43":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Pupil Size"},"Node-44":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Color":{"id":"Node-45","port":"Value"},"Metallic Factor":{"id":"Node-46","port":"Float 64"}}},"Node-45":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[github.com/EliCDavis/polyform/drawing/coloring.Color]","variable":"Pupil Color"},"Node-46":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-47":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Material":{"id":"Node-48","port":"Out"},"Mesh":{"id":"Node-20","port":"Out"},"Rotation":{"id":"Node-36","port":"Out"},"Scale":{"id":"Node-51","port":"Out"},"Translation":{"id":"Node-53","port":"Value"}}},"Node-48":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Color":{"id":"Node-49","port":"Value"},"Metallic Factor":{"id":"Node-50","port":"Float 64"}}},"Node-49":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[github.com/EliCDavis/polyform/drawing/coloring.Color]","variable":"Nose Color"},"Node-5":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":20}},"Node-50":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-51":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-52","port":"Value"},"Y":{"id":"Node-52","port":"Value"},"Z":{"id":"Node-52","port":"Value"}}},"Node-52":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Nose Size"},"Node-53":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":-0.06736102152474865,"z":0.5169884495676479}}},"Node-54":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.SphereNode]","assignedInput":{"Position":{"id":"Node-56","port":"Value"},"Radius":{"id":"Node-55","port":"Value"}}},"Node-55":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.4}},"Node-56":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":-0.8523327784602677,"z":0}}},"Node-57":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":1,"y":0.8,"z":0.75}}},"Node-58":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundedCylinderNode]","assignedInput":{"Body Height":{"id":"Node-61","port":"Value"},"Position":{"id":"Node-60","port":"Value"},"Radius":{"id":"Node-59","port":"Value"}}},"Node-59":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Leg Radius"},"Node-6":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/math/geometry.AABB]","data":{"name":"","description":"","currentValue":{"center":{"x":-1.0356143171090082,"y":0.06823937224703247,"z":0.3083300025606852},"extents":{"x":5.282742610160934,"y":2.477320071507882,"z":2.6306260493933773}}}},"Node-60":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.2037284400531858,"y":-1.057394398143054,"z":0}}},"Node-61":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Leg Length"},"Node-62":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":15}},"Node-63":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.7,"y":0.7,"z":0.7}}},"Node-64":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.LaplacianSmoothNode]","assignedInput":{"Mesh":{"id":"Node-1","port":"Mesh"},"Smoothing Factor":{"id":"Node-65","port":"Value"}}},"Node-65":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.3}},"Node-66":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.LaplacianSmoothNode]","assignedInput":{"Mesh":{"id":"Node-22","port":"Mesh"}}},"Node-67":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.LinesNode]","assignedInput":{"Points":{"id":"Node-68","port":"Value"},"Radius":{"id":"Node-69","port":"Value"}}},"Node-68":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[[]github.com/EliCDavis/vector/vector3.Vector[float64]]","variable":"Tail Path"},"Node-69":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Tail Width"},"Node-70":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.UnionNode]","assignedInput":{"Fields.0":{"id":"Node-17","port":"X"},"Fields.1":{"id":"Node-67","port":"Field"}}},"Node-9":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.UnionNode]","assignedInput":{"Fields.0":{"id":"Node-12","port":"Field"},"Fields.1":{"id":"Node-15","port":"Field"},"Fields.2":{"id":"Node-54","port":"Field"},"Fields.3":{"id":"Node-58","port":"Field"}}}},"producers":{},"variables":{"subgroups":{},"variables":{"Eye Size":{"description":"","data":{"type":"float64","value":0.45}},"Fur Color":{"description":"","data":{"type":"coloring.Color","value":"#01222b"}},"Leg Length":{"description":"","data":{"type":"float64","value":0.3}},"Leg Radius":{"description":"","data":{"type":"float64","value":0.05}},"Nose Color":{"description":"","data":{"type":"coloring.Color","value":"#9d5858"}},"Nose Size":{"description":"","data":{"type":"float64","value":0.15}},"Pupil Color":{"description":"","data":{"type":"coloring.Color","value":"#00000000"}},"Pupil Size":{"description":"","data":{"type":"float64","value":0.3}},"Tail Path":{"description":"","data":{"type":"[]vector3.Vector[float64]","value":[{"x":0,"y":-0.9095471764721286,"z":-0.4138844298990092},{"x":0.1589582469921156,"y":-1.2125666442807685,"z":-0.4169380236078606},{"x":0.3154083426128448,"y":-1.3141459237244673,"z":-0.26698896507187303},{"x":0.4750132103915383,"y":-1.3680494406048345,"z":0.11321283332987897}]}},"Tail Width":{"description":"","data":{"type":"float64","value":0.09}}}}}}
\ No newline at end of file
+{"buffers":[{"byteLength":0,"uri":"data:application/octet-stream;base64,"}],"data":{"metadata":{"nodes":{"Node-1":{"position":{"x":727,"y":-188}},"Node-10":{"position":{"x":-616,"y":349}},"Node-12":{"position":{"x":-367,"y":-76}},"Node-14":{"position":{"x":-620,"y":564}},"Node-15":{"position":{"x":-368,"y":341}},"Node-16":{"position":{"x":-594,"y":793}},"Node-17":{"position":{"x":164,"y":-226}},"Node-18":{"position":{"x":15,"y":1638}},"Node-19":{"position":{"x":1617,"y":859}},"Node-2":{"position":{"x":1572,"y":-254}},"Node-21":{"position":{"x":1235,"y":-704}},"Node-22":{"position":{"x":223,"y":1645}},"Node-23":{"position":{"x":1030,"y":-622}},"Node-24":{"position":{"x":1033,"y":-514}},"Node-25":{"position":{"x":1615,"y":1208}},"Node-26":{"position":{"x":1143,"y":1109}},"Node-28":{"position":{"x":1356,"y":1512}},"Node-29":{"position":{"x":1109,"y":1527}},"Node-3":{"position":{"x":2304,"y":783}},"Node-30":{"position":{"x":1143,"y":922}},"Node-32":{"position":{"x":835,"y":1003}},"Node-33":{"position":{"x":1130,"y":432}},"Node-34":{"position":{"x":871,"y":578}},"Node-35":{"position":{"x":860,"y":825}},"Node-36":{"position":{"x":310,"y":1077}},"Node-37":{"position":{"x":87,"y":1064}},"Node-38":{"position":{"x":-430,"y":2545}},"Node-39":{"position":{"x":-1099,"y":2643}},"Node-40":{"position":{"x":-821,"y":3031}},"Node-41":{"position":{"x":-857,"y":2661}},"Node-42":{"position":{"x":-808,"y":2843}},"Node-43":{"position":{"x":-1018,"y":2862}},"Node-44":{"position":{"x":-790,"y":2229}},"Node-45":{"position":{"x":-989,"y":2295}},"Node-46":{"position":{"x":-979,"y":2458}},"Node-47":{"position":{"x":1845,"y":2212}},"Node-48":{"position":{"x":1481,"y":2209}},"Node-49":{"position":{"x":1298,"y":2214}},"Node-5":{"position":{"x":454,"y":96}},"Node-50":{"position":{"x":1285,"y":2370}},"Node-51":{"position":{"x":1530,"y":2671}},"Node-52":{"position":{"x":1350,"y":2686}},"Node-53":{"position":{"x":1521,"y":2888}},"Node-54":{"position":{"x":-388,"y":-548}},"Node-55":{"position":{"x":-631,"y":-343}},"Node-56":{"position":{"x":-630,"y":-550}},"Node-57":{"position":{"x":-599,"y":-68}},"Node-58":{"position":{"x":-475,"y":-1150}},"Node-59":{"position":{"x":-718,"y":-844}},"Node-6":{"position":{"x":440,"y":-517}},"Node-60":{"position":{"x":-749,"y":-1053}},"Node-61":{"position":{"x":-743,"y":-1151}},"Node-62":{"position":{"x":11,"y":1864}},"Node-63":{"position":{"x":-210,"y":1773}},"Node-64":{"position":{"x":934,"y":-189}},"Node-65":{"position":{"x":700,"y":59}},"Node-66":{"position":{"x":-216,"y":1638}},"Node-67":{"position":{"x":163,"y":215}},"Node-68":{"position":{"x":-23,"y":214}},"Node-69":{"position":{"x":-24,"y":322}},"Node-70":{"position":{"x":476,"y":-72}},"Node-71":{"position":{"x":1166,"y":-192}},"Node-72":{"position":{"x":417,"y":1631}},"Node-73":{"position":{"x":656,"y":912}},"Node-74":{"position":{"x":802,"y":1180}},"Node-9":{"position":{"x":-29,"y":-228}}},"notes":{"1":{"position":{"x":-727,"y":-1241},"text":"# Legs\n\nTheir just cylinders","width":438.26851432213545},"2":{"position":{"x":-605,"y":263},"text":"# Ears\n\nIt's a cone","width":391.7908391744534},"3":{"position":{"x":-621,"y":-628},"text":"# Body\n\nIt's a sphere","width":367.3857266533418},"4":{"position":{"x":-591,"y":-121},"text":"# Hey Block Head\n\n","width":370.2483437313183},"5":{"position":{"x":-47,"y":-370},"text":"# Mirror\n\nMake one side of the body mirror the other. This makes it so we only have to modify one leg/ear, and we get the other one perfectly symetrical without any extra work","width":378.04456608867923},"6":{"position":{"x":-16,"y":139},"text":"# Tail\n\nIt's just a multipoint line","width":335.108526613877},"7":{"position":{"x":-1099,"y":2138},"text":"# Pupils\n\nIt's just a smoothed sphere. Nothing to it","width":813.1722922940445},"8":{"position":{"x":-199,"y":1533},"text":"# Eye + Nose Mesh\n\nGenerate a round cube mesh to use for both the eyes and nose","width":778.9960333995564},"9":{"position":{"x":1484,"y":2123},"text":"# Nose\n\nJust a rotated cube","width":525.5221516640636}}},"nodes":{"Node-1":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/marching.MarchNode]","assignedInput":{"Domain":{"id":"Node-6","port":"Value"},"Field":{"id":"Node-70","port":"Union"},"Resolution":{"id":"Node-5","port":"Value"}}},"Node-10":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.24,"y":0.32919907960352635,"z":0.051100203537421535}}},"Node-12":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundCubeNode]","assignedInput":{"Size":{"id":"Node-57","port":"Value"}}},"Node-14":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.24,"y":0.7514109011273641,"z":0.2}}},"Node-15":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundedConeNode]","assignedInput":{"A":{"id":"Node-10","port":"Value"},"B":{"id":"Node-14","port":"Value"},"Radius 1":{"id":"Node-16","port":"Value"}}},"Node-16":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.3}},"Node-17":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.MirrorNode]","assignedInput":{"Field":{"id":"Node-9","port":"Union"}}},"Node-18":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundCubeNode]","assignedInput":{"Roundness":{"id":"Node-66","port":"Value"},"Size":{"id":"Node-63","port":"Value"}}},"Node-19":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Children.0":{"id":"Node-38","port":"Out"},"Material":{"id":"Node-33","port":"Out"},"Mesh":{"id":"Node-72","port":"Out"},"Rotation":{"id":"Node-36","port":"Out"},"Scale":{"id":"Node-28","port":"Out"},"Translation":{"id":"Node-30","port":"Out"}}},"Node-2":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Material":{"id":"Node-21","port":"Out"},"Mesh":{"id":"Node-71","port":"Out"}}},"Node-21":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Color":{"id":"Node-23","port":"Value"},"Metallic Factor":{"id":"Node-24","port":"Float 64"}}},"Node-22":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/marching.MarchNode]","assignedInput":{"Field":{"id":"Node-18","port":"Field"},"Resolution":{"id":"Node-62","port":"Value"}}},"Node-23":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[github.com/EliCDavis/polyform/drawing/coloring.Color]","variable":"Fur Color"},"Node-24":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-25":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Children.0":{"id":"Node-38","port":"Out"},"Material":{"id":"Node-33","port":"Out"},"Mesh":{"id":"Node-72","port":"Out"},"Rotation":{"id":"Node-36","port":"Out"},"Scale":{"id":"Node-28","port":"Out"},"Translation":{"id":"Node-26","port":"Out"}}},"Node-26":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-32","port":"Additive"},"Y":{"id":"Node-35","port":"Value"},"Z":{"id":"Node-74","port":"Value"}}},"Node-28":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-29","port":"Value"},"Y":{"id":"Node-29","port":"Value"},"Z":{"id":"Node-29","port":"Value"}}},"Node-29":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Eye Size"},"Node-3":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ManifestNode]","assignedInput":{"Models.0":{"id":"Node-2","port":"Out"},"Models.1":{"id":"Node-19","port":"Out"},"Models.2":{"id":"Node-25","port":"Out"},"Models.3":{"id":"Node-47","port":"Out"}}},"Node-30":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-73","port":"Value"},"Y":{"id":"Node-35","port":"Value"},"Z":{"id":"Node-74","port":"Value"}}},"Node-32":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.InverseNode[float64]]","assignedInput":{"In":{"id":"Node-73","port":"Value"}}},"Node-33":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Metallic Factor":{"id":"Node-34","port":"Float 64"}}},"Node-34":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-35":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.15}},"Node-36":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/quaternion.FromEulerAngleNode]","assignedInput":{"Angle":{"id":"Node-37","port":"Value"}}},"Node-37":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":0,"z":0.785}}},"Node-38":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Material":{"id":"Node-44","port":"Out"},"Mesh":{"id":"Node-41","port":"Out"},"Scale":{"id":"Node-42","port":"Out"},"Translation":{"id":"Node-40","port":"Value"}}},"Node-39":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/primitives.UvSphereNode]"},"Node-40":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":0,"z":0.4}}},"Node-41":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.SmoothNormalsImplicitWeldNode]","assignedInput":{"Mesh":{"id":"Node-39","port":"Out"}}},"Node-42":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-43","port":"Value"},"Y":{"id":"Node-43","port":"Value"},"Z":{"id":"Node-43","port":"Value"}}},"Node-43":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Pupil Size"},"Node-44":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Color":{"id":"Node-45","port":"Value"},"Metallic Factor":{"id":"Node-46","port":"Float 64"}}},"Node-45":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[github.com/EliCDavis/polyform/drawing/coloring.Color]","variable":"Pupil Color"},"Node-46":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-47":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.ModelNode]","assignedInput":{"Material":{"id":"Node-48","port":"Out"},"Mesh":{"id":"Node-72","port":"Out"},"Rotation":{"id":"Node-36","port":"Out"},"Scale":{"id":"Node-51","port":"Out"},"Translation":{"id":"Node-53","port":"Value"}}},"Node-48":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/formats/gltf.MaterialNode]","assignedInput":{"Color":{"id":"Node-49","port":"Value"},"Metallic Factor":{"id":"Node-50","port":"Float 64"}}},"Node-49":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[github.com/EliCDavis/polyform/drawing/coloring.Color]","variable":"Nose Color"},"Node-5":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":20}},"Node-50":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math.ZeroNode]"},"Node-51":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/vector3.NewNode[float64]]","assignedInput":{"X":{"id":"Node-52","port":"Value"},"Y":{"id":"Node-52","port":"Value"},"Z":{"id":"Node-52","port":"Value"}}},"Node-52":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Nose Size"},"Node-53":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":-0.06736102152474865,"z":0.5169884495676479}}},"Node-54":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.SphereNode]","assignedInput":{"Position":{"id":"Node-56","port":"Value"},"Radius":{"id":"Node-55","port":"Value"}}},"Node-55":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.4}},"Node-56":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0,"y":-0.8523327784602677,"z":0}}},"Node-57":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":1,"y":0.8,"z":0.75}}},"Node-58":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.RoundedCylinderNode]","assignedInput":{"Body Height":{"id":"Node-61","port":"Value"},"Position":{"id":"Node-60","port":"Value"},"Radius":{"id":"Node-59","port":"Value"}}},"Node-59":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Leg Radius"},"Node-6":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/math/geometry.AABB]","data":{"name":"","description":"","currentValue":{"center":{"x":0.26869149581047447,"y":-0.21709643566139802,"z":0.31146070917968594},"extents":{"x":1.3528657227487297,"y":1.509920533927572,"z":1.121005529711819}}}},"Node-60":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.13355495773300535,"y":-1.057394398143054,"z":0}}},"Node-61":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Leg Length"},"Node-62":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":10}},"Node-63":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]","data":{"name":"","description":"","currentValue":{"x":0.5,"y":0.5,"z":0.5}}},"Node-64":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.LaplacianSmoothNode]","assignedInput":{"Mesh":{"id":"Node-1","port":"Mesh"},"Smoothing Factor":{"id":"Node-65","port":"Value"}}},"Node-65":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.05}},"Node-66":{"type":"github.com/EliCDavis/polyform/generator/parameter.Value[float64]","data":{"name":"","description":"","currentValue":0.15}},"Node-67":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.LinesNode]","assignedInput":{"Points":{"id":"Node-68","port":"Value"},"Radius":{"id":"Node-69","port":"Value"}}},"Node-68":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[[]github.com/EliCDavis/vector/vector3.Vector[float64]]","variable":"Tail Path"},"Node-69":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Tail Width"},"Node-70":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.UnionNode]","assignedInput":{"Fields.0":{"id":"Node-17","port":"X"},"Fields.1":{"id":"Node-67","port":"Field"}}},"Node-71":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.SmoothNormalsNode]","assignedInput":{"Mesh":{"id":"Node-64","port":"Out"}}},"Node-72":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/modeling/meshops.SmoothNormalsNode]","assignedInput":{"Mesh":{"id":"Node-22","port":"Mesh"}}},"Node-73":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Eye Spacing"},"Node-74":{"type":"github.com/EliCDavis/polyform/generator/variable.VariableReferenceNode[float64]","variable":"Eye Protrusion"},"Node-9":{"type":"github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/math/sdf.UnionNode]","assignedInput":{"Fields.0":{"id":"Node-12","port":"Field"},"Fields.1":{"id":"Node-15","port":"Field"},"Fields.2":{"id":"Node-54","port":"Field"},"Fields.3":{"id":"Node-58","port":"Field"}}}},"producers":{},"variables":{"subgroups":{},"variables":{"Eye Protrusion":{"description":"How far the eyeballs stick out of the head","data":{"type":"float64","value":0.35}},"Eye Size":{"description":"","data":{"type":"float64","value":0.5}},"Eye Spacing":{"description":"","data":{"type":"float64","value":0.275}},"Fur Color":{"description":"","data":{"type":"coloring.Color","value":"#01222b"}},"Leg Length":{"description":"","data":{"type":"float64","value":0.3}},"Leg Radius":{"description":"","data":{"type":"float64","value":0.05}},"Nose Color":{"description":"","data":{"type":"coloring.Color","value":"#9d5858"}},"Nose Size":{"description":"","data":{"type":"float64","value":0.15}},"Pupil Color":{"description":"","data":{"type":"coloring.Color","value":"#00000000"}},"Pupil Size":{"description":"","data":{"type":"float64","value":0.3}},"Tail Path":{"description":"","data":{"type":"[]vector3.Vector[float64]","value":[{"x":0,"y":-0.9095471764721286,"z":-0.3843793279856673},{"x":0.1589582469921156,"y":-1.2125666442807685,"z":-0.4169380236078606},{"x":0.3154083426128448,"y":-1.3141459237244673,"z":-0.26698896507187303},{"x":0.4750132103915383,"y":-1.3680494406048345,"z":0.11321283332987897}]}},"Tail Width":{"description":"","data":{"type":"float64","value":0.09}}}}}}
\ No newline at end of file
diff --git a/generator/edit/html/server.html b/generator/edit/html/server.html
index 5bd59899..2dbd1e2d 100644
--- a/generator/edit/html/server.html
+++ b/generator/edit/html/server.html
@@ -80,6 +80,22 @@
z-index: 1;
}
+ #running-message {
+ display: none;
+ position: absolute;
+ right: 20px;
+ bottom: 20px;
+ padding: 10px;
+ box-sizing: border-box;
+ text-align: center;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ pointer-events: none;
+ z-index: 1;
+ }
+
#messageContainer {
position: absolute;
left: 0px;
@@ -433,6 +449,9 @@
+
+ Running...
+
diff --git a/math/sdf/art.go b/math/sdf/art.go
new file mode 100644
index 00000000..0e319c8b
--- /dev/null
+++ b/math/sdf/art.go
@@ -0,0 +1,34 @@
+package sdf
+
+import (
+ "github.com/EliCDavis/polyform/math/sample"
+ "github.com/EliCDavis/vector/vector3"
+)
+
+// func displacementExample(p vector3.Float64) float64 {
+// return math.Sin(20*p.X()) * math.Sin(20*p.Y()) * math.Sin(20*p.Z())
+// }
+
+func Displace(primitive, displacement sample.Vec3ToFloat, p vector3.Float64) float64 {
+ d1 := primitive(p)
+ d2 := displacement(p)
+ return d1 + d2
+}
+
+// func opTwist(primitive sample.Vec3ToFloat, p vector3.Float64) float64 {
+// const k = 10.0 // or some other amount
+// c := math.Cos(k * p.Y())
+// s := math.Sin(k * p.Y())
+// m := math.mat2(c, -s, s, c)
+// q := vector3.New(m*p.xz, p.Y())
+// return primitive(q)
+// }
+
+// func opCheapBend(primitive sample.Vec3ToFloat, p vector3.Float64) float64 {
+// const k = 10.0 // or some other amount
+// c := math.Cos(k * p.X())
+// s := math.Sin(k * p.X())
+// m := math.mat2(c, -s, s, c)
+// q := vector3.New(m*p.xy, p.Z())
+// return primitive(q)
+// }
diff --git a/math/sdf/line.go b/math/sdf/line.go
index 3290cd26..e68cee6e 100644
--- a/math/sdf/line.go
+++ b/math/sdf/line.go
@@ -1,8 +1,6 @@
package sdf
import (
- "math"
-
"github.com/EliCDavis/polyform/math/geometry"
"github.com/EliCDavis/polyform/math/sample"
"github.com/EliCDavis/polyform/nodes"
@@ -25,9 +23,7 @@ func MultipointLine(points []vector3.Float64, radius float64) sample.Vec3ToFloat
switch len(points) {
case 0:
- return func(f vector3.Float64) float64 {
- return math.Inf(1)
- }
+ return nullField
case 1:
return Sphere(points[0], radius)
diff --git a/math/sdf/nodes.go b/math/sdf/nodes.go
index cb618a44..245f48ce 100644
--- a/math/sdf/nodes.go
+++ b/math/sdf/nodes.go
@@ -11,6 +11,7 @@ func init() {
refutil.RegisterType[nodes.Struct[TranslateNode]](factory)
refutil.RegisterType[nodes.Struct[TransformNode]](factory)
+ refutil.RegisterType[nodes.Struct[RepeatNode]](factory)
refutil.RegisterType[nodes.Struct[UnionNode]](factory)
refutil.RegisterType[nodes.Struct[IntersectionNode]](factory)
diff --git a/math/sdf/repeat.go b/math/sdf/repeat.go
new file mode 100644
index 00000000..8c32ef63
--- /dev/null
+++ b/math/sdf/repeat.go
@@ -0,0 +1,43 @@
+package sdf
+
+import (
+ "github.com/EliCDavis/polyform/math/sample"
+ "github.com/EliCDavis/polyform/math/trs"
+ "github.com/EliCDavis/polyform/nodes"
+ "github.com/EliCDavis/vector/vector3"
+)
+
+func Repeat(field sample.Vec3ToFloat, transforms []trs.TRS) sample.Vec3ToFloat {
+ if len(transforms) == 0 {
+ return nullField
+ }
+
+ invertedTRS := make([]trs.TRS, len(transforms))
+ for i, v := range transforms {
+ invertedTRS[i] = trs.FromMatrix(v.Matrix().Inverse())
+ }
+
+ return func(v vector3.Float64) float64 {
+ closestPoint := field(invertedTRS[0].Transform(v))
+ for i := 1; i < len(invertedTRS); i++ {
+ closestPoint = min(closestPoint, field(invertedTRS[i].Transform(v)))
+ }
+ return closestPoint
+ }
+}
+
+type RepeatNode struct {
+ Transforms nodes.Output[[]trs.TRS]
+ Field nodes.Output[sample.Vec3ToFloat]
+}
+
+func (cn RepeatNode) Result(out *nodes.StructOutput[sample.Vec3ToFloat]) {
+ if cn.Field == nil {
+ return
+ }
+
+ out.Set(Repeat(
+ nodes.GetOutputValue(out, cn.Field),
+ nodes.TryGetOutputValue(out, cn.Transforms, []trs.TRS{trs.Identity()}),
+ ))
+}
diff --git a/math/sdf/translate.go b/math/sdf/translate.go
index 31cfd1aa..8e01a426 100644
--- a/math/sdf/translate.go
+++ b/math/sdf/translate.go
@@ -32,8 +32,9 @@ func (cn TranslateNode) Result(out *nodes.StructOutput[sample.Vec3ToFloat]) {
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
func Transform(field sample.Vec3ToFloat, transformation trs.TRS) sample.Vec3ToFloat {
+ inverse := transformation.Inverse()
return func(v vector3.Float64) float64 {
- return field(transformation.Transform(v))
+ return field(inverse.Transform(v))
}
}
diff --git a/math/sdf/util.go b/math/sdf/util.go
new file mode 100644
index 00000000..f487be99
--- /dev/null
+++ b/math/sdf/util.go
@@ -0,0 +1,12 @@
+package sdf
+
+import (
+ "math"
+
+ "github.com/EliCDavis/vector/vector3"
+)
+
+// Field meant to represent "nothing".
+func nullField(f vector3.Float64) float64 {
+ return math.Inf(1)
+}
diff --git a/math/trs/trs.go b/math/trs/trs.go
index bc4621ad..7725149f 100644
--- a/math/trs/trs.go
+++ b/math/trs/trs.go
@@ -117,6 +117,10 @@ func (trs TRS) Multiply(other TRS) TRS {
return FromMatrix(trs.Matrix().Multiply(other.Matrix()))
}
+func (trs TRS) Inverse() TRS {
+ return FromMatrix(trs.Matrix().Inverse())
+}
+
// Transform an array of points by the TRS
func (trs TRS) TransformArray(in []vector3.Float64) []vector3.Float64 {
out := make([]vector3.Float64, len(in))
diff --git a/modeling/marching/canvas.go b/modeling/marching/canvas.go
index 14658745..ae5746c9 100644
--- a/modeling/marching/canvas.go
+++ b/modeling/marching/canvas.go
@@ -31,7 +31,8 @@ func interpolateV1(v1, v2, t float64) float64 {
func interpolateVerts(v1, v2 vector3.Float64, v1v, v2v, cutoff float64) vector3.Float64 {
t := interpolationValueFromCutoff(v1v, v2v, cutoff)
- return v2.Sub(v1).Scale(t).Add(v1)
+ // return v2.Sub(v1).Scale(t).Add(v1)
+ return v1.Scale(1 - t).Add(v2.Scale(t))
}
func lookupOrAdd(data *workingData, vert vector3.Float64) int {
@@ -628,17 +629,18 @@ func (d *MarchingCanvas) marchFloat1BlockPosition(
lookupIndex |= 128
}
- for i := 0; triangulation[lookupIndex][i] != -1; i += 3 {
+ tris := triangulation[lookupIndex]
+ for i := 0; tris[i] != -1; i += 3 {
// Get indices of corner points A and B for each of the three edges
// of the cube that need to be joined to form the triangle.
- a0 := cornerIndexAFromEdge[triangulation[lookupIndex][i]]
- b0 := cornerIndexBFromEdge[triangulation[lookupIndex][i]]
+ a0 := cornerIndexAFromEdge[tris[i]]
+ b0 := cornerIndexBFromEdge[tris[i]]
- a1 := cornerIndexAFromEdge[triangulation[lookupIndex][i+1]]
- b1 := cornerIndexBFromEdge[triangulation[lookupIndex][i+1]]
+ a1 := cornerIndexAFromEdge[tris[i+1]]
+ b1 := cornerIndexBFromEdge[tris[i+1]]
- a2 := cornerIndexAFromEdge[triangulation[lookupIndex][i+2]]
- b2 := cornerIndexBFromEdge[triangulation[lookupIndex][i+2]]
+ a2 := cornerIndexAFromEdge[tris[i+2]]
+ b2 := cornerIndexBFromEdge[tris[i+2]]
v1 := interpolateVerts(cubeCornerPositions[a0], cubeCornerPositions[b0], cubeCorners[a0], cubeCorners[b0], cutoff).Add(offset)
v2 := interpolateVerts(cubeCornerPositions[a1], cubeCornerPositions[b1], cubeCorners[a1], cubeCorners[b1], cutoff).Add(offset)
diff --git a/modeling/marching/march.go b/modeling/marching/march.go
index 20aaabbb..de186969 100644
--- a/modeling/marching/march.go
+++ b/modeling/marching/march.go
@@ -6,38 +6,45 @@ import (
"github.com/EliCDavis/polyform/math/geometry"
"github.com/EliCDavis/polyform/math/sample"
"github.com/EliCDavis/polyform/modeling"
+ "github.com/EliCDavis/polyform/modeling/meshops"
"github.com/EliCDavis/vector/vector3"
)
-func marchRecurse(field sample.Vec3ToFloat, bounds geometry.AABB, cubeSize float64, res map[vector3.Int]float64) {
- center := bounds.Center()
+func marchRecurse(field sample.Vec3ToFloat, bounds geometry.AABB, cubeSize, surface float64, res map[vector3.Int]float64) {
size := bounds.Size()
+ diagonal := size.Length()
+
+ center := bounds.Center()
+ centerIndex := center.DivByConstant(cubeSize).RoundToInt()
+ recentered := centerIndex.ToFloat64().Scale(cubeSize)
+
+ fieldResult := field(recentered) - surface
+ // TODO: WE THIS IS OUR BIGGEST SPEEDUP, FIGURE OUT HOW TO PRUNE HARDER
// The closest surface is not within the bounds
- fieldResult := field(center)
- if math.Abs(fieldResult) > (size.MaxComponent()/2)+(cubeSize*2) {
+ if math.Abs(fieldResult) > (diagonal/2)+(cubeSize)+center.Distance(recentered) {
return
}
- if size.MaxComponent() > cubeSize {
- halfSize := size.Scale(0.5)
- qs := halfSize.Scale(0.5)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), qs.Y(), qs.Z())), halfSize), cubeSize, res)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), qs.Y(), -qs.Z())), halfSize), cubeSize, res)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), -qs.Y(), qs.Z())), halfSize), cubeSize, res)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), -qs.Y(), -qs.Z())), halfSize), cubeSize, res)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), qs.Y(), qs.Z())), halfSize), cubeSize, res)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), qs.Y(), -qs.Z())), halfSize), cubeSize, res)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), -qs.Y(), qs.Z())), halfSize), cubeSize, res)
- marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), -qs.Y(), -qs.Z())), halfSize), cubeSize, res)
+ res[centerIndex] = fieldResult
+ if size.MaxComponent() < cubeSize {
return
}
- res[center.DivByConstant(cubeSize).FloorToInt()] = fieldResult
+ halfSize := size.Scale(0.5)
+ qs := halfSize.Scale(0.5)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), qs.Y(), qs.Z())), halfSize), cubeSize, surface, res)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), qs.Y(), -qs.Z())), halfSize), cubeSize, surface, res)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), -qs.Y(), qs.Z())), halfSize), cubeSize, surface, res)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(qs.X(), -qs.Y(), -qs.Z())), halfSize), cubeSize, surface, res)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), qs.Y(), qs.Z())), halfSize), cubeSize, surface, res)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), qs.Y(), -qs.Z())), halfSize), cubeSize, surface, res)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), -qs.Y(), qs.Z())), halfSize), cubeSize, surface, res)
+ marchRecurse(field, geometry.NewAABB(center.Add(vector3.New(-qs.X(), -qs.Y(), -qs.Z())), halfSize), cubeSize, surface, res)
}
func dedup(data *workingData, vert vector3.Float64, size float64) int {
- distritized := vert.ToInt()
+ distritized := modeling.Vector3ToInt(vert, 4)
if foundIndex, ok := data.vertLookup[distritized]; ok {
return foundIndex
@@ -49,20 +56,21 @@ func dedup(data *workingData, vert vector3.Float64, size float64) int {
return index
}
-func March(field sample.Vec3ToFloat, domain geometry.AABB, cubeSize float64) modeling.Mesh {
+func March(field sample.Vec3ToFloat, domain geometry.AABB, cubeSize, surface float64) modeling.Mesh {
results := make(map[vector3.Int]float64)
- marchRecurse(field, domain, cubeSize, results)
+ // sdfCompute := time.Now()
+ marchRecurse(field, domain, cubeSize, surface, results)
+ // log.Printf("Time To Compute SDFs %s", time.Since(sdfCompute))
+ // marchCompute := time.Now()
marchingWorkingData := &workingData{
tris: make([]int, 0),
verts: make([]vector3.Float64, 0),
vertLookup: make(map[vector3.Int]int),
}
- // tris := make([]int, 0)
- // verts := make([]vector3.Float64, 0)
-
cubeCorners := make([]float64, 8)
+ cubeCornerPositions := make([]vector3.Float64, 8)
for key, nnn := range results {
cubeCorners[0] = nnn
@@ -122,46 +130,40 @@ func March(field sample.Vec3ToFloat, domain geometry.AABB, cubeSize float64) mod
lookupIndex |= 128
}
+ if lookupIndex == 0 || lookupIndex == 255 {
+ continue
+ }
+
xf := float64(key.X())
yf := float64(key.Y())
zf := float64(key.Z())
- cubeCornerPositions := []vector3.Float64{
- vector3.New(xf, yf, zf),
- vector3.New(xf+1, yf, zf),
- vector3.New(xf+1, yf, zf+1),
- vector3.New(xf, yf, zf+1),
- vector3.New(xf, yf+1, zf),
- vector3.New(xf+1, yf+1, zf),
- vector3.New(xf+1, yf+1, zf+1),
- vector3.New(xf, yf+1, zf+1),
- }
-
- for i := 0; triangulation[lookupIndex][i] != -1; i += 3 {
+ cubeCornerPositions[0] = vector3.New(xf, yf, zf)
+ cubeCornerPositions[1] = vector3.New(xf+1, yf, zf)
+ cubeCornerPositions[2] = vector3.New(xf+1, yf, zf+1)
+ cubeCornerPositions[3] = vector3.New(xf, yf, zf+1)
+ cubeCornerPositions[4] = vector3.New(xf, yf+1, zf)
+ cubeCornerPositions[5] = vector3.New(xf+1, yf+1, zf)
+ cubeCornerPositions[6] = vector3.New(xf+1, yf+1, zf+1)
+ cubeCornerPositions[7] = vector3.New(xf, yf+1, zf+1)
+
+ tris := triangulation[lookupIndex]
+ for i := 0; tris[i] != -1; i += 3 {
// Get indices of corner points A and B for each of the three edges
// of the cube that need to be joined to form the triangle.
- a0 := cornerIndexAFromEdge[triangulation[lookupIndex][i]]
- b0 := cornerIndexBFromEdge[triangulation[lookupIndex][i]]
+ a0 := cornerIndexAFromEdge[tris[i]]
+ b0 := cornerIndexBFromEdge[tris[i]]
- a1 := cornerIndexAFromEdge[triangulation[lookupIndex][i+1]]
- b1 := cornerIndexBFromEdge[triangulation[lookupIndex][i+1]]
+ a1 := cornerIndexAFromEdge[tris[i+1]]
+ b1 := cornerIndexBFromEdge[tris[i+1]]
- a2 := cornerIndexAFromEdge[triangulation[lookupIndex][i+2]]
- b2 := cornerIndexBFromEdge[triangulation[lookupIndex][i+2]]
+ a2 := cornerIndexAFromEdge[tris[i+2]]
+ b2 := cornerIndexBFromEdge[tris[i+2]]
v1 := interpolateVerts(cubeCornerPositions[a0], cubeCornerPositions[b0], cubeCorners[a0], cubeCorners[b0], 0)
v2 := interpolateVerts(cubeCornerPositions[a1], cubeCornerPositions[b1], cubeCorners[a1], cubeCorners[b1], 0)
v3 := interpolateVerts(cubeCornerPositions[a2], cubeCornerPositions[b2], cubeCorners[a2], cubeCorners[b2], 0)
- // verts = append(
- // verts,
- // v1.Scale(cubeSize),
- // v2.Scale(cubeSize),
- // v3.Scale(cubeSize),
- // )
-
- // tris = append(tris, len(tris), len(tris)+1, len(tris)+2)
-
marchingWorkingData.tris = append(
marchingWorkingData.tris,
dedup(marchingWorkingData, v1, cubeSize),
@@ -171,6 +173,14 @@ func March(field sample.Vec3ToFloat, domain geometry.AABB, cubeSize float64) mod
}
}
- return modeling.NewMesh(modeling.TriangleTopology, marchingWorkingData.tris).
+ m := modeling.NewMesh(modeling.TriangleTopology, marchingWorkingData.tris).
SetFloat3Attribute(modeling.PositionAttribute, marchingWorkingData.verts)
+
+ if len(marchingWorkingData.tris) == 0 {
+ return m
+ }
+
+ // log.Printf("Time To March Mesh %s", time.Since(marchCompute))
+
+ return meshops.RemoveNullFaces3D(m, modeling.PositionAttribute, 0)
}
diff --git a/modeling/marching/nodes.go b/modeling/marching/nodes.go
index 2209a6d2..63579222 100644
--- a/modeling/marching/nodes.go
+++ b/modeling/marching/nodes.go
@@ -1,6 +1,8 @@
package marching
import (
+ "fmt"
+
"github.com/EliCDavis/polyform/generator"
"github.com/EliCDavis/polyform/math/geometry"
"github.com/EliCDavis/polyform/math/sample"
@@ -19,9 +21,10 @@ func init() {
}
type MarchNode struct {
- Field nodes.Output[sample.Vec3ToFloat]
- Resolution nodes.Output[float64]
- Domain nodes.Output[geometry.AABB]
+ Field nodes.Output[sample.Vec3ToFloat] `description:"The SDF to tesselate"`
+ Resolution nodes.Output[float64] `description:"Number of marching cube voxels contained in a single 'unit'"`
+ Surface nodes.Output[float64] `description:"value of the SDF that represents the surface (default: 0)"`
+ Domain nodes.Output[geometry.AABB] `description:"The region in which the marching cubes algorithm runs"`
}
func (cn MarchNode) Mesh(out *nodes.StructOutput[modeling.Mesh]) {
@@ -30,30 +33,23 @@ func (cn MarchNode) Mesh(out *nodes.StructOutput[modeling.Mesh]) {
return
}
- // canvas := NewMarchingCanvas(
- // nodes.TryGetOutputValue(out, cn.Resolution, 1.),
- // )
-
- // canvas.AddField(Field{
- // Domain: nodes.TryGetOutputValue(
- // out,
- // cn.Domain,
- // geometry.NewAABB(vector3.Zero[float64](), vector3.One[float64]()),
- // ),
- // Float1Functions: map[string]sample.Vec3ToFloat{
- // modeling.PositionAttribute: nodes.GetOutputValue(out, cn.Field),
- // },
- // })
-
- // out.Set(canvas.March(0))
+ resolution := nodes.TryGetOutputValue(out, cn.Resolution, 1.)
+ if resolution <= 0 {
+ out.CaptureError(nodes.InvalidInputError{
+ Input: cn.Resolution,
+ Message: fmt.Sprintf("value must be greater than 0 (recieved %f)", resolution),
+ })
+ return
+ }
out.Set(March(
nodes.GetOutputValue(out, cn.Field),
nodes.TryGetOutputValue(
out,
cn.Domain,
- geometry.NewAABB(vector3.Zero[float64](), vector3.One[float64]()),
+ geometry.NewAABB(vector3.Zero[float64](), vector3.Fill(10.)),
),
- 1/nodes.TryGetOutputValue(out, cn.Resolution, 1.),
+ 1/resolution,
+ nodes.TryGetOutputValue(out, cn.Surface, 0.),
))
}
diff --git a/modeling/meshops/smooth_normals.go b/modeling/meshops/smooth_normals.go
index 1a65b8ff..b9423ff9 100644
--- a/modeling/meshops/smooth_normals.go
+++ b/modeling/meshops/smooth_normals.go
@@ -2,7 +2,6 @@ package meshops
import (
"fmt"
- "math"
"github.com/EliCDavis/polyform/modeling"
"github.com/EliCDavis/polyform/nodes"
@@ -29,9 +28,6 @@ func SmoothNormals(m modeling.Mesh) modeling.Mesh {
vertices := m.Float3Attribute(modeling.PositionAttribute)
normals := make([]vector3.Float64, vertices.Len())
- for i := range normals {
- normals[i] = vector3.Zero[float64]()
- }
tris := m.Indices()
for triIndex := 0; triIndex < tris.Len(); triIndex += 3 {
@@ -42,7 +38,7 @@ func SmoothNormals(m modeling.Mesh) modeling.Mesh {
normalized := vertices.At(p2).Sub(vertices.At(p1)).Cross(vertices.At(p3).Sub(vertices.At(p1)))
// This occurs whenever the given tri is actually just a line
- if math.IsNaN(normalized.X()) {
+ if normalized.ContainsNaN() {
continue
}
diff --git a/nodes/errors.go b/nodes/errors.go
new file mode 100644
index 00000000..ff9d377e
--- /dev/null
+++ b/nodes/errors.go
@@ -0,0 +1,12 @@
+package nodes
+
+import "fmt"
+
+type InvalidInputError struct {
+ Input OutputPort
+ Message string
+}
+
+func (nie InvalidInputError) Error() string {
+ return fmt.Sprintf("invalid input %q: %s", nie.Input.Name(), nie.Message)
+}
diff --git a/website/ProducerView/producer_view_manager.ts b/website/ProducerView/producer_view_manager.ts
index e8c12d87..78d807a1 100644
--- a/website/ProducerView/producer_view_manager.ts
+++ b/website/ProducerView/producer_view_manager.ts
@@ -1,489 +1,550 @@
-import { Box3, EquirectangularReflectionMapping, Group, Mesh, MeshStandardMaterial, PerspectiveCamera, Scene, SRGBColorSpace, TextureLoader, WebGLRenderer } from "three";
+import {
+ Box3,
+ EquirectangularReflectionMapping,
+ Group,
+ Mesh,
+ MeshStandardMaterial,
+ PerspectiveCamera,
+ Scene,
+ SRGBColorSpace,
+ TextureLoader,
+ WebGLRenderer,
+} from "three";
import { ErrorManager } from "../error_manager";
import { InfoManager } from "../info_manager";
import { GraphInstance, Manifest, NodeDefinition } from "../schema";
import { getFileExtension } from "../utils";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
-import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
-import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js';
+import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
+import { PLYLoader } from "three/examples/jsm/loaders/PLYLoader.js";
import { RequestManager } from "../requests";
-import * as GaussianSplats3D from '@mkkellogg/gaussian-splats-3d';
+import * as GaussianSplats3D from "@mkkellogg/gaussian-splats-3d";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { ThreeApp } from "../three_app";
-import { SchemaManager } from '../schema_manager';
-
+import { SchemaManager } from "../schema_manager";
type ProducerRefreshCallback = (string: string, thing: any) => void;
-
const textureLoader = new TextureLoader();
-const textureEquirec = textureLoader.load('https://i.imgur.com/Ev4X4yY_d.webp?maxwidth=1520&fidelity=grand');
+const textureEquirec = textureLoader.load(
+ "https://i.imgur.com/Ev4X4yY_d.webp?maxwidth=1520&fidelity=grand"
+);
textureEquirec.mapping = EquirectangularReflectionMapping;
textureEquirec.colorSpace = SRGBColorSpace;
-
export class ProducerViewManager {
+ loadingCount: number;
- loadingCount: number;
+ wireframe: boolean;
- wireframe: boolean;
+ producerItemSubscriber: Array
;
- producerItemSubscriber: Array;
+ completeRefreshSubscriber: Array<() => void>;
- completeRefreshSubscriber: Array<() => void>;
+ cachedSchema: GraphInstance;
- cachedSchema: GraphInstance;
+ firstTimeLoadingScene: boolean;
- firstTimeLoadingScene: boolean;
+ renderer: WebGLRenderer;
- renderer: WebGLRenderer;
+ requestManager: RequestManager;
- requestManager: RequestManager;
+ camera: PerspectiveCamera;
- camera: PerspectiveCamera;
+ guassianSplatViewer: GaussianSplats3D.Viewer;
- guassianSplatViewer: GaussianSplats3D.Viewer;
+ producerScene: Group;
- producerScene: Group;
+ orbitControls: OrbitControls;
- orbitControls: OrbitControls;
+ viewerContainer: Group;
- viewerContainer: Group;
+ scene: Scene;
- scene: Scene;
+ nodeTypeManifestPorts: Map;
- nodeTypeManifestPorts: Map;
+ modelVersion: number;
- modelVersion: number;
-
- schemaManager: SchemaManager;
-
- constructor(
- app: ThreeApp,
- requestManager: RequestManager,
- nodeTypes: Array,
- schemaManager: SchemaManager
- ) {
- this.completeRefreshSubscriber = [];
- this.modelVersion = -1;
- this.schemaManager = schemaManager;
- this.nodeTypeManifestPorts = new Map();
- for (let i = 0; i < nodeTypes.length; i++) {
- const nodeType = nodeTypes[i];
- if (!nodeType.outputs) {
- continue;
- }
- for (const [outputName, output] of Object.entries(nodeType.outputs)) {
- if (output.type === "github.com/EliCDavis/polyform/generator/manifest.Manifest") {
- this.nodeTypeManifestPorts.set(nodeType.type, outputName);
- }
- }
- }
+ readonly schemaManager: SchemaManager;
- this.requestManager = requestManager;
- this.renderer = app.Renderer;
- this.camera = app.Camera;
- this.orbitControls = app.OrbitControls;
- this.viewerContainer = app.ViewerScene;
- this.scene = app.Scene;
-
- this.producerScene = null;
- this.wireframe = false;
- this.firstTimeLoadingScene = true;
- this.loadingCount = 0;
- this.cachedSchema = null;
- this.producerItemSubscriber = [];
- }
+ readonly runningMessage: HTMLElement | undefined;
- setModelVersion(newModelVersion: number): void {
- if (newModelVersion === this.modelVersion) {
- return;
+ constructor(
+ app: ThreeApp,
+ requestManager: RequestManager,
+ nodeTypes: Array,
+ schemaManager: SchemaManager
+ ) {
+ this.runningMessage = document.getElementById("running-message");
+ this.completeRefreshSubscriber = [];
+ this.modelVersion = -1;
+ this.schemaManager = schemaManager;
+ this.nodeTypeManifestPorts = new Map();
+ for (let i = 0; i < nodeTypes.length; i++) {
+ const nodeType = nodeTypes[i];
+ if (!nodeType.outputs) {
+ continue;
+ }
+ for (const [outputName, output] of Object.entries(nodeType.outputs)) {
+ if (
+ output.type ===
+ "github.com/EliCDavis/polyform/generator/manifest.Manifest"
+ ) {
+ this.nodeTypeManifestPorts.set(nodeType.type, outputName);
}
- this.modelVersion = newModelVersion;
- this.schemaManager.refreshSchema("Model version change");
+ }
}
- SubscribeToProducerRefresh(callback: ProducerRefreshCallback): void {
- this.producerItemSubscriber.push(callback);
+ this.requestManager = requestManager;
+ this.renderer = app.Renderer;
+ this.camera = app.Camera;
+ this.orbitControls = app.OrbitControls;
+ this.viewerContainer = app.ViewerScene;
+ this.scene = app.Scene;
+
+ this.producerScene = null;
+ this.wireframe = false;
+ this.firstTimeLoadingScene = true;
+ this.loadingCount = 0;
+ this.cachedSchema = null;
+ this.producerItemSubscriber = [];
+ }
+
+ setModelVersion(newModelVersion: number): void {
+ if (newModelVersion === this.modelVersion) {
+ return;
}
-
- // Called whenever
- SubscribeToCompleteRefresh(subsriber: () => void): void {
- this.completeRefreshSubscriber.push(subsriber);
+ this.modelVersion = newModelVersion;
+ this.schemaManager.refreshSchema("Model version change");
+ }
+
+ SubscribeToProducerRefresh(callback: ProducerRefreshCallback): void {
+ this.producerItemSubscriber.push(callback);
+ }
+
+ // Called whenever
+ SubscribeToCompleteRefresh(subsriber: () => void): void {
+ this.completeRefreshSubscriber.push(subsriber);
+ }
+
+ private showRunningMessage() {
+ if (this.runningMessage) {
+ this.runningMessage.style.display = "block";
}
+ }
- AddLoading(): void {
- this.loadingCount += 1;
+ private hideRunningMessage() {
+ if (this.runningMessage) {
+ this.runningMessage.style.display = "none";
}
+ }
- RemoveLoading(): void {
- if (this.loadingCount === 0) {
- throw new Error("loading count already 0");
- }
- this.loadingCount -= 1;
+ AddLoading(): void {
+ this.loadingCount += 1;
+ }
- if (this.loadingCount === 0 && this.cachedSchema) {
- this.Refresh(this.cachedSchema)
- this.cachedSchema = null;
- } else {
- // We're all done loading!!!
- for (let i = 0; i < this.completeRefreshSubscriber.length; i++) {
- this.completeRefreshSubscriber[i]();
- }
- }
- }
-
- CurrentlyLoading(): boolean {
- return this.loadingCount > 0;
+ RemoveLoading(): void {
+ if (this.loadingCount === 0) {
+ throw new Error("loading count already 0");
}
+ this.loadingCount -= 1;
- NewSchema(schema: GraphInstance): void {
- if (this.CurrentlyLoading()) {
- this.cachedSchema = schema;
- return;
+ if (this.loadingCount === 0) {
+ if (this.cachedSchema) {
+ this.Refresh(this.cachedSchema);
+ this.cachedSchema = null;
+ } else {
+ // We're all done loading!!!
+ this.hideRunningMessage();
+ for (let i = 0; i < this.completeRefreshSubscriber.length; i++) {
+ this.completeRefreshSubscriber[i]();
}
- this.Refresh(schema);
+ }
}
+ }
- Render(): void {
- if (this.guassianSplatViewer) {
- this.guassianSplatViewer.update();
- this.guassianSplatViewer.render();
- }
- }
+ CurrentlyLoading(): boolean {
+ return this.loadingCount > 0;
+ }
- loadText(producerURL: string) {
- this.AddLoading();
- this.requestManager.fetchText(
- producerURL,
- (data) => {
- InfoManager.ShowInfo(data);
- this.RemoveLoading();
- this.UpdateSubscribers(producerURL, data);
- },
- (error) => {
- this.RemoveLoading();
- console.error("unable to load text", producerURL, error);
- ErrorManager.ShowError(producerURL, JSON.parse(error).error);
- }
- );
+ NewSchema(schema: GraphInstance): void {
+ if (this.CurrentlyLoading()) {
+ this.cachedSchema = schema;
+ return;
}
+ this.Refresh(schema);
+ }
- loadImage(producerURL: string) {
- this.AddLoading();
- this.requestManager.fetchImage(
- producerURL,
- (data) => {
- this.RemoveLoading();
- this.UpdateSubscribers(producerURL, data);
- },
- (error) => {
- this.RemoveLoading();
- console.error("unable to load image", producerURL, error);
- ErrorManager.ShowError(producerURL, JSON.parse(error).error);
- }
- );
+ Render(): void {
+ if (this.guassianSplatViewer) {
+ this.guassianSplatViewer.update();
+ this.guassianSplatViewer.render();
}
-
- viewAABB(aabb: Box3): void {
- const aabbDepth = (aabb.max.z - aabb.min.z)
- const aabbWidth = (aabb.max.x - aabb.min.x)
- const aabbHeight = (aabb.max.y - aabb.min.y)
- const aabbHalfHeight = aabbHeight / 2
- const mid = (aabb.max.y + aabb.min.y) / 2
-
- if (this.firstTimeLoadingScene && isFinite(aabbWidth) && isFinite(aabbDepth) && isFinite(aabbHeight)) {
- // console.log("Camera position intialized", aabbWidth, aabbDepth, aabbHeight);
- this.firstTimeLoadingScene = false;
-
- this.camera.position.y = (- mid + aabbHalfHeight) * (3 / 2);
- this.camera.position.z = Math.sqrt(
- (aabbWidth * aabbWidth) +
- (aabbDepth * aabbDepth) +
- (aabbHeight * aabbHeight)
- ) / 2;
-
- this.orbitControls.target.set(
- (aabb.max.x + aabb.min.x) / 2,
- - mid + aabbHalfHeight,
- (aabb.max.z + aabb.min.z) / 2
- );
- this.orbitControls.update();
- }
+ }
+
+ loadText(producerURL: string) {
+ this.AddLoading();
+ this.requestManager.fetchText(
+ producerURL,
+ (data) => {
+ InfoManager.ShowInfo(data);
+ this.RemoveLoading();
+ this.UpdateSubscribers(producerURL, data);
+ },
+ (error) => {
+ this.RemoveLoading();
+ console.error("unable to load text", producerURL, error);
+ ErrorManager.ShowError(producerURL, JSON.parse(error).error);
+ }
+ );
+ }
+
+ loadImage(producerURL: string) {
+ this.AddLoading();
+ this.requestManager.fetchImage(
+ producerURL,
+ (data) => {
+ this.RemoveLoading();
+ this.UpdateSubscribers(producerURL, data);
+ },
+ (error) => {
+ this.RemoveLoading();
+ console.error("unable to load image", producerURL, error);
+ ErrorManager.ShowError(producerURL, JSON.parse(error).error);
+ }
+ );
+ }
+
+ viewAABB(aabb: Box3): void {
+ const aabbDepth = aabb.max.z - aabb.min.z;
+ const aabbWidth = aabb.max.x - aabb.min.x;
+ const aabbHeight = aabb.max.y - aabb.min.y;
+ const aabbHalfHeight = aabbHeight / 2;
+ const mid = (aabb.max.y + aabb.min.y) / 2;
+
+ if (
+ this.firstTimeLoadingScene &&
+ isFinite(aabbWidth) &&
+ isFinite(aabbDepth) &&
+ isFinite(aabbHeight)
+ ) {
+ // console.log("Camera position intialized", aabbWidth, aabbDepth, aabbHeight);
+ this.firstTimeLoadingScene = false;
+
+ this.camera.position.y = (-mid + aabbHalfHeight) * (3 / 2);
+ this.camera.position.z =
+ Math.sqrt(
+ aabbWidth * aabbWidth +
+ aabbDepth * aabbDepth +
+ aabbHeight * aabbHeight
+ ) / 2;
+
+ this.orbitControls.target.set(
+ (aabb.max.x + aabb.min.x) / 2,
+ -mid + aabbHalfHeight,
+ (aabb.max.z + aabb.min.z) / 2
+ );
+ this.orbitControls.update();
}
+ }
+
+ loadObj(objLoader: OBJLoader, key: string, producerURL: string): void {
+ this.AddLoading();
+
+ objLoader.load(
+ producerURL,
+ (obj) => {
+ this.RemoveLoading();
+ this.cleanProducerScene();
+
+ const aabb = new Box3();
+ aabb.setFromObject(obj);
+ const aabbHeight = aabb.max.y - aabb.min.y;
+ const aabbHalfHeight = aabbHeight / 2;
+ const mid = (aabb.max.y + aabb.min.y) / 2;
+
+ this.producerScene.add(obj);
+
+ // We have to do this weird thing because the pivot of the scene
+ // Isn't always the center of the AABB
+ this.viewerContainer.position.set(0, -mid + aabbHalfHeight, 0);
+
+ this.viewAABB(aabb);
+ },
+ undefined,
+ (err) => {
+ this.cleanProducerScene();
+ console.error(err);
+ this.RemoveLoading();
+ }
+ );
+ }
+
+ loadGltf(gltfLoader: GLTFLoader, key: string, producerURL: string) {
+ this.AddLoading();
+ gltfLoader.load(
+ producerURL,
+ ((gltf) => {
+ this.cleanProducerScene();
+
+ const aabb = new Box3();
+ aabb.setFromObject(gltf.scene);
+ const aabbHeight = aabb.max.y - aabb.min.y;
+ const aabbHalfHeight = aabbHeight / 2;
+ const mid = (aabb.max.y + aabb.min.y) / 2;
+
+ this.producerScene.add(gltf.scene);
+
+ // We have to do this weird thing because the pivot of the scene
+ // Isn't always the center of the AABB
+ this.viewerContainer.position.set(0, -mid + aabbHalfHeight + 0.001, 0);
+
+ const objects = [];
+
+ gltf.scene.traverse((object) => {
+ if (object.isMesh) {
+ object.castShadow = true;
+ object.receiveShadow = true;
+ object.material.wireframe = this.wireframe;
+ object.material.envMap = textureEquirec;
+ object.material.needsUpdate = true;
+ // object.material.transparent = true;
+
+ objects.push(object);
+ } else if (object.isPoints) {
+ object.material.size = 2;
+ }
+ });
- loadObj(objLoader: OBJLoader, key: string, producerURL: string): void {
- this.AddLoading();
+ // progressiveSurfacemap.addObjectsToLightMap(objects);
- objLoader.load(
- producerURL,
- ((obj) => {
- this.RemoveLoading();
-
- const aabb = new Box3();
- aabb.setFromObject(obj);
- const aabbHeight = (aabb.max.y - aabb.min.y)
- const aabbHalfHeight = aabbHeight / 2
- const mid = (aabb.max.y + aabb.min.y) / 2
-
- this.producerScene.add(obj);
-
- // We have to do this weird thing because the pivot of the scene
- // Isn't always the center of the AABB
- this.viewerContainer.position.set(0, - mid + aabbHalfHeight, 0);
-
- this.viewAABB(aabb);
- }),
- undefined,
- (err) => {
- console.error(err);
- this.RemoveLoading();
- }
- )
- }
+ this.viewAABB(aabb);
- loadGltf(gltfLoader: GLTFLoader, key: string, producerURL: string) {
- this.AddLoading();
- gltfLoader.load(
- producerURL,
- ((gltf) => {
-
- const aabb = new Box3();
- aabb.setFromObject(gltf.scene);
- const aabbHeight = (aabb.max.y - aabb.min.y)
- const aabbHalfHeight = aabbHeight / 2
- const mid = (aabb.max.y + aabb.min.y) / 2
-
- this.producerScene.add(gltf.scene);
-
- // We have to do this weird thing because the pivot of the scene
- // Isn't always the center of the AABB
- this.viewerContainer.position.set(0, - mid + aabbHalfHeight + 0.001, 0)
-
- const objects = [];
-
- gltf.scene.traverse((object) => {
- if (object.isMesh) {
- object.castShadow = true;
- object.receiveShadow = true;
- object.material.wireframe = this.wireframe;
- object.material.envMap = textureEquirec;
- object.material.needsUpdate = true;
- // object.material.transparent = true;
-
- objects.push(object)
- } else if (object.isPoints) {
- object.material.size = 2;
- }
- });
-
- // progressiveSurfacemap.addObjectsToLightMap(objects);
-
- this.viewAABB(aabb);
-
- this.UpdateSubscribers(producerURL, gltf);
-
- this.RemoveLoading();
- }).bind(this),
- undefined,
- (error) => {
- this.RemoveLoading();
-
- if (typeof error === 'object' && "response" in error) {
- var resp = error.response as any;
- resp.json().then(x => {
- ErrorManager.ShowError(key, x.error);
- })
- } else {
- console.error("Unkown error type from gltf loading", error)
- }
-
- });
- }
+ this.UpdateSubscribers(producerURL, gltf);
- loadPly(plyLoader: PLYLoader, key: string, producerURL: string): void {
- this.AddLoading();
+ this.RemoveLoading();
+ }).bind(this),
+ undefined,
+ (error) => {
+ this.RemoveLoading();
+ this.cleanProducerScene();
- plyLoader.load(
- producerURL,
- ((geometry) => {
- this.RemoveLoading();
- geometry.computeVertexNormals();
-
- const material = new MeshStandardMaterial({});
- const mesh = new Mesh(geometry, material);
- mesh.castShadow = true;
- mesh.receiveShadow = true;
-
- const aabb = new Box3();
- aabb.setFromObject(mesh);
- const aabbHeight = (aabb.max.y - aabb.min.y)
- const aabbHalfHeight = aabbHeight / 2
- const mid = (aabb.max.y + aabb.min.y) / 2
-
- this.producerScene.add(mesh);
-
- // We have to do this weird thing because the pivot of the scene
- // Isn't always the center of the AABB
- this.viewerContainer.position.set(0, - mid + aabbHalfHeight, 0);
-
- this.viewAABB(aabb);
- }),
- undefined,
- (err) => {
- console.error(err);
- this.RemoveLoading();
- }
- )
- }
-
- loadSplat(key: string, producerURL: string): void {
- this.AddLoading();
- if (this.guassianSplatViewer) {
- this.guassianSplatViewer.dispose();
+ if (typeof error === "object" && "response" in error) {
+ var resp = error.response as any;
+ resp.json().then((x) => {
+ ErrorManager.ShowError(key, x.error);
+ });
+ } else {
+ console.error("Unkown error type from gltf loading", error);
}
+ }
+ );
+ }
+
+ loadPly(plyLoader: PLYLoader, key: string, producerURL: string): void {
+ this.AddLoading();
+
+ plyLoader.load(
+ producerURL,
+ (geometry) => {
+ this.cleanProducerScene();
+
+ this.RemoveLoading();
+ geometry.computeVertexNormals();
+
+ const material = new MeshStandardMaterial({});
+ const mesh = new Mesh(geometry, material);
+ mesh.castShadow = true;
+ mesh.receiveShadow = true;
+
+ const aabb = new Box3();
+ aabb.setFromObject(mesh);
+ const aabbHeight = aabb.max.y - aabb.min.y;
+ const aabbHalfHeight = aabbHeight / 2;
+ const mid = (aabb.max.y + aabb.min.y) / 2;
+
+ this.producerScene.add(mesh);
+
+ // We have to do this weird thing because the pivot of the scene
+ // Isn't always the center of the AABB
+ this.viewerContainer.position.set(0, -mid + aabbHalfHeight, 0);
+
+ this.viewAABB(aabb);
+ },
+ undefined,
+ (err) => {
+ console.error(err);
+ this.cleanProducerScene();
+ this.RemoveLoading();
+ }
+ );
+ }
+
+ loadSplat(key: string, producerURL: string): void {
+ this.cleanProducerScene();
+
+ this.AddLoading();
+ if (this.guassianSplatViewer) {
+ this.guassianSplatViewer.dispose();
+ }
- this.renderer.setPixelRatio(1);
-
- const wasm = true;
- // https://github.com/mkkellogg/GaussianSplats3D/blob/main/src/Viewer.js
- const splatViewerOptions = {
- selfDrivenMode: false,
- // 'cameraUp': [0, -1, 0],
- sphericalHarmonicsDegree: 2,
- useBuiltInControls: false,
- rootElement: this.renderer.domElement.parentElement,
- renderer: this.renderer,
- threeScene: this.scene,
- camera: this.camera,
- // 'sceneRevealMode': GaussianSplats3D.SceneRevealMode.Instant,
-
- gpuAcceleratedSort: !wasm,
- sharedMemoryForWorkers: !wasm
+ this.renderer.setPixelRatio(1);
+
+ const wasm = true;
+ // https://github.com/mkkellogg/GaussianSplats3D/blob/main/src/Viewer.js
+ const splatViewerOptions = {
+ selfDrivenMode: false,
+ // 'cameraUp': [0, -1, 0],
+ sphericalHarmonicsDegree: 2,
+ useBuiltInControls: false,
+ rootElement: this.renderer.domElement.parentElement,
+ renderer: this.renderer,
+ threeScene: this.scene,
+ camera: this.camera,
+ // 'sceneRevealMode': GaussianSplats3D.SceneRevealMode.Instant,
+
+ gpuAcceleratedSort: !wasm,
+ sharedMemoryForWorkers: !wasm,
+ };
+
+ this.guassianSplatViewer = new GaussianSplats3D.Viewer(splatViewerOptions);
+
+ // getSplatCenter
+ this.guassianSplatViewer
+ .addSplatScene(producerURL, {
+ // rotation: [1, 0, 0, 0],
+ // scale: [-1, -1, 1, 0],
+ // streamView: false
+ // showLoadingUI: false,
+ // 'scale': [0.25, 0.25, 0.25],
+ })
+ .then(
+ (() => {
+ this.guassianSplatViewer.splatMesh.onSplatTreeReady((splatTree) => {
+ const tree = splatTree.subTrees[0];
+ const aabb = new Box3();
+ aabb.setFromPoints([tree.sceneMin, tree.sceneMax]);
+ const aabbHeight = aabb.max.y - aabb.min.y;
+ const aabbHalfHeight = aabbHeight / 2;
+ const mid = (aabb.max.y + aabb.min.y) / 2;
+
+ const shiftY = -mid + aabbHalfHeight;
+ this.guassianSplatViewer.splatMesh.position.set(0, shiftY, 0);
+ this.viewerContainer.position.set(0, shiftY, 0);
+
+ this.viewAABB(aabb);
+ });
+
+ this.RemoveLoading();
+ this.UpdateSubscribers(
+ producerURL,
+ this.guassianSplatViewer.splatMesh
+ );
+ }).bind(this)
+ )
+ .catch((x) => {
+ console.error(x);
+ this.RemoveLoading();
+ ErrorManager.ShowError(key, x.error);
+ });
+ }
+
+ SetWireframe(wireframe: boolean): void {
+ this.wireframe = wireframe;
+ this.producerScene.traverse((object) => {
+ // https://discourse.threejs.org/t/gltf-scene-traverse-property-ismesh-does-not-exist-on-type-object3d/27212
+ if (object instanceof Mesh) {
+ object.material.wireframe = wireframe;
+ }
+ });
+ }
+
+ ManifestLoaded(nodeId: string, portName: string, manifest: Manifest): void {
+ const manifestUrl: string = `./manifest/${nodeId}/${portName}/`;
+ const fileToLoad = manifest.main;
+ const fileToLoadMetadata = manifest.entries[fileToLoad].metadata;
+
+ ErrorManager.ClearError(manifest.main);
+ const fileExt = getFileExtension(manifest.main);
+
+ switch (fileExt) {
+ case "txt":
+ this.loadText(manifestUrl + fileToLoad);
+ break;
+
+ case "gltf":
+ case "glb":
+ const gltfLoader = new GLTFLoader().setPath(manifestUrl);
+ this.loadGltf(gltfLoader, fileToLoad, fileToLoad);
+ break;
+
+ case "obj":
+ const objLoader = new OBJLoader().setPath(manifestUrl);
+ this.loadObj(objLoader, fileToLoad, fileToLoad);
+ break;
+
+ case "splat":
+ this.loadSplat(fileToLoad, manifestUrl + fileToLoad);
+ break;
+
+ case "ply":
+ if (
+ fileToLoadMetadata &&
+ fileToLoadMetadata["gaussianSplat"] === true
+ ) {
+ this.loadSplat(fileToLoad, manifestUrl + fileToLoad);
+ } else {
+ const plyLoader = new PLYLoader().setPath(manifestUrl);
+ this.loadPly(plyLoader, fileToLoad, fileToLoad);
}
+ break;
- this.guassianSplatViewer = new GaussianSplats3D.Viewer(splatViewerOptions);
-
- // getSplatCenter
- this.guassianSplatViewer.addSplatScene(producerURL, {
- // rotation: [1, 0, 0, 0],
- // scale: [-1, -1, 1, 0],
- // streamView: false
- // showLoadingUI: false,
- // 'scale': [0.25, 0.25, 0.25],
- }).then((() => {
-
- this.guassianSplatViewer.splatMesh.onSplatTreeReady((splatTree) => {
- const tree = splatTree.subTrees[0]
- const aabb = new Box3();
- aabb.setFromPoints([tree.sceneMin, tree.sceneMax]);
- const aabbHeight = (aabb.max.y - aabb.min.y)
- const aabbHalfHeight = aabbHeight / 2
- const mid = (aabb.max.y + aabb.min.y) / 2
-
- const shiftY = - mid + aabbHalfHeight
- this.guassianSplatViewer.splatMesh.position.set(0, shiftY, 0)
- this.viewerContainer.position.set(0, shiftY, 0);
-
- this.viewAABB(aabb);
- });
-
- this.RemoveLoading();
- this.UpdateSubscribers(producerURL, this.guassianSplatViewer.splatMesh);
-
- }).bind(this)).catch(x => {
- console.error(x)
- this.RemoveLoading();
- ErrorManager.ShowError(key, x.error);
- })
+ // case "png":
+ // this.loadImage(manifestUrl + fileToLoad);
+ // break;
}
+ }
- SetWireframe(wireframe: boolean): void {
- this.wireframe = wireframe;
- this.producerScene.traverse((object) => {
- // https://discourse.threejs.org/t/gltf-scene-traverse-property-ismesh-does-not-exist-on-type-object3d/27212
- if (object instanceof Mesh) {
- object.material.wireframe = wireframe;
- }
- });
+ private cleanProducerScene() {
+ if (this.producerScene != null) {
+ this.viewerContainer.remove(this.producerScene);
}
- ManifestLoaded(nodeId: string, portName: string, manifest: Manifest): void {
-
- const manifestUrl: string = `./manifest/${nodeId}/${portName}/`;
- const fileToLoad = manifest.main;
- const fileToLoadMetadata = manifest.entries[fileToLoad].metadata
-
- ErrorManager.ClearError(manifest.main);
- const fileExt = getFileExtension(manifest.main);
-
- switch (fileExt) {
- case "txt":
- this.loadText(manifestUrl + fileToLoad);
- break;
-
- case "gltf":
- case "glb":
- const gltfLoader = new GLTFLoader().setPath(manifestUrl);
- this.loadGltf(gltfLoader, fileToLoad, fileToLoad);
- break;
-
- case "obj":
- const objLoader = new OBJLoader().setPath(manifestUrl);
- this.loadObj(objLoader, fileToLoad, fileToLoad);
- break;
-
- case "splat":
- this.loadSplat(fileToLoad, manifestUrl + fileToLoad)
- break;
-
- case "ply":
- if (fileToLoadMetadata && fileToLoadMetadata["gaussianSplat"] === true) {
- this.loadSplat(fileToLoad, manifestUrl + fileToLoad)
- } else {
- const plyLoader = new PLYLoader().setPath(manifestUrl);
- this.loadPly(plyLoader, fileToLoad, fileToLoad)
- }
- break;
-
- // case "png":
- // this.loadImage(manifestUrl + fileToLoad);
- // break;
- }
- }
+ this.producerScene = new Group();
+ this.viewerContainer.add(this.producerScene);
+ }
- Refresh(schema: GraphInstance) {
- InfoManager.ClearInfo();
+ Refresh(schema: GraphInstance) {
+ // this.cleanProducerScene();
+ InfoManager.ClearInfo();
- if (this.producerScene != null) {
- this.viewerContainer.remove(this.producerScene)
- }
-
- this.producerScene = new Group();
- this.viewerContainer.add(this.producerScene);
+ this.showRunningMessage();
- for (const [nodeID, nodeInstance] of Object.entries(schema.nodes)) {
+ let hasManifests = false;
- // This node doesn't have a manifest output, continue
- if (!this.nodeTypeManifestPorts.has(nodeInstance.type)) {
- continue;
- }
+ for (const [nodeID, nodeInstance] of Object.entries(schema.nodes)) {
+ // This node doesn't have a manifest output, continue
+ if (!this.nodeTypeManifestPorts.has(nodeInstance.type)) {
+ continue;
+ }
- const portName = this.nodeTypeManifestPorts.get(nodeInstance.type)
- this.requestManager.getManifest(nodeID, portName, (manifest) => {
- this.ManifestLoaded(nodeID, portName, manifest)
- });
- }
+ hasManifests = true;
+ const portName = this.nodeTypeManifestPorts.get(nodeInstance.type);
+ this.requestManager.getManifest(nodeID, portName, (manifest) => {
+ this.ManifestLoaded(nodeID, portName, manifest);
+ });
}
-
- UpdateSubscribers(url: string, thing: any) {
- this.producerItemSubscriber
- .forEach(sub => {
- if (!sub) {
- return;
- }
- sub(url, thing);
- })
+ if (hasManifests) {
+ this.showRunningMessage();
}
+ }
+
+ UpdateSubscribers(url: string, thing: any) {
+ this.producerItemSubscriber.forEach((sub) => {
+ if (!sub) {
+ return;
+ }
+ sub(url, thing);
+ });
+ }
}
-
diff --git a/website/nodes/node.ts b/website/nodes/node.ts
index aab501f3..53e560d5 100644
--- a/website/nodes/node.ts
+++ b/website/nodes/node.ts
@@ -344,7 +344,6 @@ export class PolyNodeController {
}
return;
}
- console.log("Woo", outputPort.connections().length)
}
let found = false;