-
Notifications
You must be signed in to change notification settings - Fork 18
/
scene.go
203 lines (146 loc) · 6.74 KB
/
scene.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package tetra3d
// Scene represents a world of sorts, and can contain a variety of Meshes and Nodes, which organize the scene into a
// graph of parents and children. Models (visual instances of Meshes), Cameras, and "empty" NodeBases all are kinds of Nodes.
type Scene struct {
Name string // The name of the Scene. Set automatically to the scene name in your 3D modeler if the DAE file exports it.
library *Library // The library from which this Scene was created. If the Scene was instantiated through code, this will be nil.
// Root indicates the root node for the scene hierarchy. For visual Models to be displayed, they must be added to the
// scene graph by simply adding them into the tree via parenting anywhere under the Root. For them to be removed from rendering,
// they simply need to be removed from the tree.
// See this page for more information on how a scene graph works: https://webglfundamentals.org/webgl/lessons/webgl-scene-graph.html
Root *Node
World *World
props Properties
data any
View3DCameras []*Camera // Any 3D view cameras that were exported from Blender
updateAutobatch bool
autobatchDynamicMap map[*Material]*Model
autobatchStaticMap map[*Material]*Model
}
// NewScene creates a new Scene by the name given.
func NewScene(name string) *Scene {
scene := &Scene{
Name: name,
Root: NewNode("Root"),
World: NewWorld("World"),
props: NewProperties(),
autobatchDynamicMap: map[*Material]*Model{},
autobatchStaticMap: map[*Material]*Model{},
}
scene.Root.scene = scene
scene.Root.cachedSceneRootNode = scene.Root
return scene
}
// Clone clones the Scene, returning a copy. Models and Meshes are shared between them.
func (scene *Scene) Clone() *Scene {
newScene := NewScene(scene.Name)
newScene.library = scene.library
newScene.Root = scene.Root.Clone().(*Node)
newScene.Root.scene = newScene
newScene.Root.cachedSceneRootNode = newScene.Root
newScene.World = scene.World // Here, we simply reference the same world; we don't clone it, since a single world can be shared across multiple Scenes
newScene.props = scene.props.Clone()
newScene.updateAutobatch = true
// Update sectors after cloning the scene
models := newScene.Root.SearchTree().bySectors().Models()
for _, n := range models {
n.sector.Neighbors.Clear()
}
for _, n := range models {
n.sector.UpdateNeighbors(models...)
}
for _, cam := range scene.View3DCameras {
newScene.View3DCameras = append(newScene.View3DCameras, cam.Clone().(*Camera))
}
newScene.data = scene.data
newScene.Root.SearchTree().ForEach(func(node INode) bool {
if node.RegisteredForSceneTreeCallbacks() && Callbacks.OnSceneClone != nil {
Callbacks.OnSceneClone(node, newScene)
}
return true
})
return newScene
}
// Data returns the Scene's user-customizeable data.
func (scene *Scene) Data() any {
return scene.data
}
// SetData sets the Scene's user-customizeable data pointer to whatever you specify (i.e. a backing "Level" instance or something, for example).
func (scene *Scene) SetData(data any) {
scene.data = data
}
// Library returns the Library from which this Scene was loaded. If it was created through code and not associated with a Library, this function will return nil.
func (scene *Scene) Library() *Library {
return scene.library
}
func (scene *Scene) Properties() Properties {
return scene.props
}
var autobatchBlankMat = NewMaterial("autobatch null material")
func (scene *Scene) HandleAutobatch() {
if scene.updateAutobatch {
for _, node := range scene.Root.SearchTree().INodes() {
if model, ok := node.(*Model); ok {
if !model.autoBatched {
mat := autobatchBlankMat
if mats := model.Mesh.Materials(); len(mats) > 0 {
mat = mats[0]
}
if model.AutoBatchMode == AutoBatchDynamic {
if _, exists := scene.autobatchDynamicMap[mat]; !exists {
mesh := NewMesh("auto dynamic batch")
mesh.AddMeshPart(mat)
m := NewModel("auto dynamic batch", mesh)
m.FrustumCulling = false
m.sectorType = SectorTypeStandalone
scene.autobatchDynamicMap[mat] = m
scene.Root.AddChildren(m)
}
scene.autobatchDynamicMap[mat].DynamicBatchAdd(scene.autobatchDynamicMap[mat].Mesh.MeshParts[0], model)
} else if model.AutoBatchMode == AutoBatchStatic {
if _, exists := scene.autobatchStaticMap[mat]; !exists {
m := NewModel("auto static merge", NewMesh("auto static merge"))
m.sectorType = SectorTypeStandalone
scene.autobatchStaticMap[mat] = m
scene.Root.AddChildren(scene.autobatchStaticMap[mat])
}
scene.autobatchStaticMap[mat].StaticMerge(model)
}
model.autoBatched = true
}
}
}
for _, dyn := range scene.autobatchDynamicMap {
for _, models := range dyn.DynamicBatchModels {
modelList := append(make([]*Model, 0, len(models)), models...)
for _, model := range modelList {
if model.Root() == nil {
dyn.DynamicBatchRemove(model)
}
}
}
}
scene.updateAutobatch = false
}
}
// Get searches a node's hierarchy using a string to find a specified node. The path is in the format of names of nodes, separated by forward
// slashes ('/'), and is relative to the node you use to call Get. As an example of Get, if you had a cup parented to a desk, which was
// parented to a room, that was finally parented to the root of the scene, it would be found at "Room/Desk/Cup". Note also that you can use "../" to
// "go up one" in the hierarchy (so cup.Get("../") would return the Desk node).
// Since Get uses forward slashes as path separation, it would be good to avoid using forward slashes in your Node names. Also note that Get()
// trims the extra spaces from the beginning and end of Node Names, so avoid using spaces at the beginning or end of your Nodes' names.
// Syntactic sugar for Scene.Root.Get().
func (scene *Scene) Get(nodePath string) INode {
return scene.Root.Get(nodePath)
}
// FindNode searches through a Node's tree for the node by name exactly. This is mostly syntactic sugar for
// Node.SearchTree().ByName(nodeName).First().
func (scene *Scene) FindNode(nodeName string) INode {
return scene.Root.SearchTree().ByName(nodeName).First()
}
//////////////
type SceneTreeCallbacks struct {
OnNodeReparent func(node, oldParent, newParent INode) // A callback to be called whenever a Node is reparented.
OnSceneClone func(node INode, newScene *Scene) // A callback function to be called whenever a Node is registered and is part of a Scene that is cloned.
}
var Callbacks = SceneTreeCallbacks{}