-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRenderTree.js
More file actions
341 lines (281 loc) · 10.6 KB
/
RenderTree.js
File metadata and controls
341 lines (281 loc) · 10.6 KB
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
"use strict";
function ObjectIsInSceneError(msg) {
this.name = "ObjectIsInSceneError";
this.message = msg;
}
/*
*
* This tree is a representation for scene
* All materials, objects, transforms will be here
*
* When adding transform a object if the object's parent is a object then add a transform node,
* otherwise update transform node.
* So, you can not keep track of all transforms in runtime.
* Performance concerns.
*
* Camera must be first.
* Use changeOrder when set a camera active
*
*
*/
const Scene = RenderTree;
function RenderTree() {
const matrixStack = [];
const gm = new GlMath();
let modelViewMatrix = gm.mat4();
Object.defineProperty(this, "modelViewMatrix", {
get: function() { return modelViewMatrix; },
enumerable: false,
configurable: false,
});
/*
* must be set in initialize step,
* update this in per frame is costly,
*
* is not in render tree
*
*/
let projectionMatrix = gm.mat4();
Object.defineProperty(this, "projectionMatrix", {
get: () => projectionMatrix,
set: mt => projectionMatrix = mt,
enumerable: false,
configurable: false,
});
let transformMatrix = gm.mat4();
Object.defineProperty(this, "transformMatrix", {
get: () => transformMatrix,
enumerable: false,
configurable: false,
});
let normalMatrix = gm.mat4();
Object.defineProperty(this, "normalMatrix", {
get: () => normalMatrix,
enumerable:false,
configurable: false,
});
let cameraUpdated = false;
Object.defineProperty(this, "cameraUpdated", {
get: () => cameraUpdated,
set: (newCameraUpdated) => cameraUpdated = newCameraUpdated,
enumerable: false,
configurable: false,
});
const modelStack = [];
const lightStack = [];
Object.defineProperty(this, "modelStack", {
get: () => modelStack,
enumerable: false,
configurable: false,
});
Object.defineProperty(this, "lightStack", {
get: () => lightStack,
enumerable: false,
configurable: false,
});
const Node = function Node(data, parent) {
this.data = data;
this.parent = parent;
this.children = [];
}
const root = new Node({}, {});
/*
*
* if added object and parent both are transform, merge them
*
*/
this.add = function(data, parent=root) {
if(this.find(data))
throw new ObjectIsInSceneError("This object is already added to scene!");
const node = new Node(data, parent);
node.parent.children.push(node);
return node;
}
this.addAsParent = function(data, child) {
/*
*
* we need optimization
* we need merge transforms. :)
*
*/
const childIndex = child.parent.children.findIndex(nd => nd === child); // child index in parent's children
/* do optimization */
let merged = false;
// try to merge this transformation with one of the parent's
let currentNode = child.parent;
while(currentNode != root){
// is it transform
if(!currentNode.data.isTransform)
break;
if(currentNode.children.length > 1)
break;
if(currentNode.data.transformType === data.transformType) {
currentNode.data.matrix = gm.mul(data.matrix, currentNode.data.matrix);
merged = true;
break;
}
currentNode = currentNode.parent;
}
// if there is no merge add transformation to tree
if(!merged) {
child.parent.children[childIndex] = new Node(data, child.parent); // now, child is not in tree, it's new transform node
child.parent = child.parent.children[childIndex]; // child's parent is new node
child.parent.children.push(child); // load parent's children from child
}
}
/*
*
* transform objects update transformMatrix
* camera objects update modelViewMatrix if it is active
* other objects yield
*
*/
this.traverse = function*(current=root) {
for(let i = 0; i < current.children.length; i++) {
if(current.children[i].data.isTransform) {
matrixStack.push(transformMatrix);
if(current.children[i].data.transformType === "Rotation")
transformMatrix = gm.mul(transformMatrix, current.children[i].data.matrix);
else if (current.children[i].data.transformType === "Scaling") {
const x = transformMatrix[0][3];
const y = transformMatrix[1][3];
const z = transformMatrix[2][3];
const w = transformMatrix[3][3];
transformMatrix = gm.mul(current.children[i].data.matrix, transformMatrix);
transformMatrix[0][3] = x;
transformMatrix[1][3] = y;
transformMatrix[2][3] = z;
transformMatrix[3][3] = w;
}
else
transformMatrix = gm.mul(current.children[i].data.matrix, transformMatrix);
} else if(current.children[i].data.isCamera) {
if(current.children[i].data.isActive && current.children[i].data.isUpdated) {
/*
* think camera as a triangle.
* at, eye is a point already
* generate a point with at + up
*
*/
const firstAt = gm.vec3(current.children[i].data.at);
let up = gm.vec4(current.children[i].data.up); // up a point in triangle now.
let at = gm.vec4();
let ey = gm.vec4(gm.sub(current.children[i].data.eye, firstAt));
// now, camera is a triangle which has a point on origin.
// this point is at point
// transform entire triangle
up = gm.vec3(gm.mul(transformMatrix, up));
at = gm.vec3(gm.mul(transformMatrix, at));
ey = gm.vec3(gm.mul(transformMatrix, ey));
// we get a transformed triangle
// now we should bring this triangle through firstAt
// dont forget up is a vector :)
up = gm.sub(up, at);
at = gm.add(at, firstAt);
ey = gm.add(ey, firstAt);
modelViewMatrix = gm.modelViewMatrix(at, ey, up);
normalMatrix = gm.tra(gm.inv(modelViewMatrix));
current.children[i].data.updatedAt = at;
current.children[i].data.updatedEye= ey;
current.children[i].data.updatedUp = up;
cameraUpdated = true;
current.children[i].data.isUpdated = false;
}
} else if(current.children[i].data.isReadyToRender) {
yield current.children[i].data;
}
yield* this.traverse(current.children[i]);
if(current.children[i].children.length > 0 && current.children[i].data.isTransform)
transformMatrix = matrixStack.pop();
}
}
this.storedTraverse = function() {
// flush stacks
lightStack.splice(0, lightStack.length);
modelStack.splice(0, modelStack.length);
const gen = this.traverse();
for(let obj of gen) {
if(obj.loadableType === "LIGHT") {
lightStack.push({light: obj, transform: transformMatrix});
} else if(obj.loadableType === "MODEL") {
modelStack.push({model: obj, transform: transformMatrix});
}
}
}
const _traverse = function*(current=root) {
for(let i = 0; i < current.children.length; i++) {
yield current.children[i];
yield* _traverse(current.children[i]);
}
}
this.find = function(data) {
const gen = _traverse();
for(let nd of gen) {
if(nd.data === data)
return nd;
}
return null;
}
this.remove = function(node) {
if(node === root)
throw new RootDeletionError("You are not able to delete root!");
const nodeIndex = node.parent.children.findIndex(nd => nd === node);
node.parent.children.splice(nodeIndex, 1);
}
this.clear = function() {
const gen = _traverse();
for(let node of gen) {
this.remove(node);
}
}
/*
*
* when setting a camera active run this for camera
* camera will be first object in traverse
* all other objects will use camera information for render
*
*/
this.changeOrder = function(node) {
if(node === root)
return;
let nodeIndex = node.parent.children.findIndex(nd => nd === node);
[node.parent.children[0], node.parent.children[nodeIndex]] = [node.parent.children[nodeIndex], node.parent.children[0]];
this.changeOrder(node.parent);
}
this.setProjection = function(settings) {
if(settings.type === "Perspective")
projectionMatrix = gm.perspectiveMatrix(settings.fovy ?? 45, settings.aspect ?? 1, settings.near ?? 0.3, settings.far ?? 10.0);
}
this.isActive = false;
ActiveScene.addObserver(this);
this.setActive = function() {
this.isActive = true;
ActiveScene.notifyAll(this);
}
this.clearActive = function() {
this.isActive = false;
}
this.test = function() {
let gen = _traverse();
for(let node of gen) {
alert(node);
}
}
}
const ActiveScene = {
scenes: [],
addObserver: function(scene) {
this.scenes.push(scene);
},
removeObserver: function(scene) {
this.scenes = this.scenes.filter(function(item, index, arr) {
return item !== scene;
});
},
notifyAll: function(scene) {
this.scenes.forEach(function(item, index, arr) {
if(scene !== item)
item.clearActive();
});
}
}