jailedthreejs 0.9.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -0
- package/dist/NoScope.js +208 -0
- package/dist/Train.js +349 -0
- package/dist/artist.js +492 -0
- package/dist/cell.js +508 -0
- package/dist/index.js +12 -0
- package/dist/main.js +136 -0
- package/dist/utils.js +300 -0
- package/package.json +41 -0
package/dist/cell.js
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
// cell.js
|
|
2
|
+
//
|
|
3
|
+
// The Cell class drives a single <cell> element:
|
|
4
|
+
// - DOM → Three.js object conversion
|
|
5
|
+
// - Event wiring / raycasting integration
|
|
6
|
+
// - CSS → object painting
|
|
7
|
+
// - Mutation observers (DOM + <style> changes)
|
|
8
|
+
// - Per-frame update callbacks
|
|
9
|
+
|
|
10
|
+
import * as THREE from 'three';
|
|
11
|
+
import { fastRemove_arry, getClassMap } from './utils.js';
|
|
12
|
+
import {
|
|
13
|
+
paintCell,
|
|
14
|
+
paintConvict,
|
|
15
|
+
deep_searchParms,
|
|
16
|
+
paintSpecificMuse,
|
|
17
|
+
paintConstantMuse,
|
|
18
|
+
getCSSRule
|
|
19
|
+
} from './artist.js';
|
|
20
|
+
import {
|
|
21
|
+
default_onCellClick_method,
|
|
22
|
+
default_onCellPointerMove_method,
|
|
23
|
+
default_onCellMouseDown_method,
|
|
24
|
+
default_onCellMouseUp_method,
|
|
25
|
+
default_onCellDoubleClick_method,
|
|
26
|
+
default_onCellContextMenu_method
|
|
27
|
+
} from './NoScope.js';
|
|
28
|
+
|
|
29
|
+
class Cell {
|
|
30
|
+
static allCells = new WeakMap();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve an existing Cell for a <cell> element.
|
|
34
|
+
*
|
|
35
|
+
* @param {HTMLElement} element
|
|
36
|
+
* @returns {Cell|null}
|
|
37
|
+
*/
|
|
38
|
+
static getCell(element) {
|
|
39
|
+
if (Cell.allCells.has(element)) {
|
|
40
|
+
return Cell.allCells.get(element);
|
|
41
|
+
}
|
|
42
|
+
console.error('No Cell found with the element:', element);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {HTMLElement} cellElm
|
|
48
|
+
* @param {THREE.WebGLRenderer} renderer
|
|
49
|
+
* @param {THREE.Scene} scene
|
|
50
|
+
* @param {THREE.Camera|null} [camera=null]
|
|
51
|
+
* @param {Function|null} [_MainAnimMethod=null]
|
|
52
|
+
*/
|
|
53
|
+
constructor(cellElm, renderer, scene, camera = null, _MainAnimMethod = null) {
|
|
54
|
+
this.cellElm = cellElm;
|
|
55
|
+
Object.defineProperty(cellElm, 'cell', {
|
|
56
|
+
value: this,
|
|
57
|
+
enumerable: false
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.threeRenderer = renderer;
|
|
61
|
+
this.loadedScene = scene;
|
|
62
|
+
this.focusedCamera = camera;
|
|
63
|
+
|
|
64
|
+
this.constantConvicts = [];
|
|
65
|
+
this.classyConvicts = [];
|
|
66
|
+
this.namedConvicts = [];
|
|
67
|
+
this._allConvictsByDom = new WeakMap();
|
|
68
|
+
|
|
69
|
+
this.updateFunds = [];
|
|
70
|
+
this._observedStyleElements = new WeakSet();
|
|
71
|
+
this._pendingStyleRepaint = false;
|
|
72
|
+
|
|
73
|
+
// paint constant :active rules each frame
|
|
74
|
+
this.updateFunds.push(() => {
|
|
75
|
+
this.constantConvicts.forEach(cC => {
|
|
76
|
+
paintConstantMuse(cC);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this._last_cast_caught = null;
|
|
81
|
+
this._lastHitPosition = null;
|
|
82
|
+
Cell.allCells.set(cellElm, this);
|
|
83
|
+
|
|
84
|
+
// initial scan
|
|
85
|
+
this._ScanCell();
|
|
86
|
+
|
|
87
|
+
// bind DOM event handlers
|
|
88
|
+
this._boundPointerMove = evt => {
|
|
89
|
+
default_onCellPointerMove_method(evt, this);
|
|
90
|
+
};
|
|
91
|
+
this._boundClick = evt => {
|
|
92
|
+
default_onCellClick_method(evt, this);
|
|
93
|
+
};
|
|
94
|
+
this._boundMouseDown = evt => {
|
|
95
|
+
default_onCellMouseDown_method(evt, this);
|
|
96
|
+
};
|
|
97
|
+
this._boundMouseUp = evt => {
|
|
98
|
+
default_onCellMouseUp_method(evt, this);
|
|
99
|
+
};
|
|
100
|
+
this._boundDoubleClick = evt => {
|
|
101
|
+
default_onCellDoubleClick_method(evt, this);
|
|
102
|
+
};
|
|
103
|
+
this._boundContextMenu = evt => {
|
|
104
|
+
evt.preventDefault();
|
|
105
|
+
default_onCellContextMenu_method(evt, this);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
cellElm.addEventListener('mousemove', this._boundPointerMove);
|
|
109
|
+
cellElm.addEventListener('click', this._boundClick);
|
|
110
|
+
cellElm.addEventListener('mousedown', this._boundMouseDown);
|
|
111
|
+
cellElm.addEventListener('mouseup', this._boundMouseUp);
|
|
112
|
+
cellElm.addEventListener('dblclick', this._boundDoubleClick);
|
|
113
|
+
cellElm.addEventListener('contextmenu', this._boundContextMenu);
|
|
114
|
+
|
|
115
|
+
// initial paint
|
|
116
|
+
paintCell(this);
|
|
117
|
+
|
|
118
|
+
// Observe <style> content so keyframes / rules updates repaint
|
|
119
|
+
this._styleElemObserver = new MutationObserver(() => {
|
|
120
|
+
if (this._pendingStyleRepaint) return;
|
|
121
|
+
this._pendingStyleRepaint = true;
|
|
122
|
+
requestAnimationFrame(() => {
|
|
123
|
+
this._pendingStyleRepaint = false;
|
|
124
|
+
paintCell(this);
|
|
125
|
+
this.classyConvicts.concat(this.namedConvicts).forEach(paintSpecificMuse);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this._observeStyleElements = root => {
|
|
130
|
+
if (!root) return;
|
|
131
|
+
const targets = [];
|
|
132
|
+
if (root.nodeName === 'STYLE') {
|
|
133
|
+
targets.push(root);
|
|
134
|
+
} else if (typeof root.querySelectorAll === 'function') {
|
|
135
|
+
targets.push(...root.querySelectorAll('style'));
|
|
136
|
+
}
|
|
137
|
+
targets.forEach(styleEl => {
|
|
138
|
+
if (this._observedStyleElements.has(styleEl)) return;
|
|
139
|
+
this._observedStyleElements.add(styleEl);
|
|
140
|
+
this._styleElemObserver.observe(styleEl, {
|
|
141
|
+
childList: true,
|
|
142
|
+
characterData: true,
|
|
143
|
+
subtree: true
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
this._styleHostObserver = new MutationObserver(mutationList => {
|
|
149
|
+
mutationList.forEach(mutation => {
|
|
150
|
+
mutation.addedNodes.forEach(node => {
|
|
151
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'STYLE') {
|
|
152
|
+
this._observeStyleElements(node);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this._observeStyleElements(this.cellElm);
|
|
159
|
+
if (document.head) {
|
|
160
|
+
this._observeStyleElements(document.head);
|
|
161
|
+
this._styleHostObserver.observe(document.head, {
|
|
162
|
+
childList: true,
|
|
163
|
+
subtree: true
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Observe inline style/id/class changes and child mutations
|
|
168
|
+
this._styleObserver = new MutationObserver(mutationList => {
|
|
169
|
+
mutationList.forEach(mutation => {
|
|
170
|
+
if (mutation.target.nodeName === 'CANVAS') return;
|
|
171
|
+
|
|
172
|
+
switch (mutation.type) {
|
|
173
|
+
case 'childList': {
|
|
174
|
+
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
|
175
|
+
const node = mutation.addedNodes[i];
|
|
176
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.nodeName !== 'CANVAS') {
|
|
177
|
+
if (node.nodeName === 'STYLE') {
|
|
178
|
+
this._observeStyleElements(node);
|
|
179
|
+
paintCell(this);
|
|
180
|
+
} else {
|
|
181
|
+
this.ScanElement(node);
|
|
182
|
+
const convict = this.getConvictByDom(node);
|
|
183
|
+
if (convict) {
|
|
184
|
+
paintSpecificMuse(convict);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (let i = 0; i < mutation.removedNodes.length; i++) {
|
|
190
|
+
const node = mutation.removedNodes[i];
|
|
191
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.nodeName !== 'CANVAS') {
|
|
192
|
+
this.removeConvict(this._allConvictsByDom.get(node));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'attributes': {
|
|
198
|
+
const target = mutation.target;
|
|
199
|
+
const convict = target.convict;
|
|
200
|
+
if (!convict) break;
|
|
201
|
+
|
|
202
|
+
if (mutation.attributeName === 'id') {
|
|
203
|
+
convict.userData.domId = target.id;
|
|
204
|
+
} else if (mutation.attributeName === 'class') {
|
|
205
|
+
const nextClasses = Array.from(target.classList).filter(Boolean);
|
|
206
|
+
convict.userData.classList = nextClasses;
|
|
207
|
+
convict.name = nextClasses[0] || '';
|
|
208
|
+
} else if (mutation.attributeName === 'style') {
|
|
209
|
+
// inline style changed; repaint this convict
|
|
210
|
+
paintConvict(target, this);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this._styleObserver.observe(this.cellElm, {
|
|
219
|
+
attributes: true,
|
|
220
|
+
childList: true,
|
|
221
|
+
attributeFilter: ['style', 'id', 'class'],
|
|
222
|
+
subtree: true
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Animation loop
|
|
226
|
+
this._running = true;
|
|
227
|
+
this._anim = _MainAnimMethod
|
|
228
|
+
? _MainAnimMethod.bind(this)
|
|
229
|
+
: () => {
|
|
230
|
+
if (!this._running) return;
|
|
231
|
+
this.updateFunds.forEach(update => update());
|
|
232
|
+
requestAnimationFrame(this._anim);
|
|
233
|
+
if (this.focusedCamera) {
|
|
234
|
+
this.threeRenderer.render(this.loadedScene, this.focusedCamera);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Resize handling
|
|
239
|
+
this._resizeObserver = new ResizeObserver(entries => {
|
|
240
|
+
for (const e of entries) {
|
|
241
|
+
const { width, height } = e.contentRect;
|
|
242
|
+
const dpr = window.devicePixelRatio || 1;
|
|
243
|
+
this.threeRenderer.setPixelRatio(dpr);
|
|
244
|
+
|
|
245
|
+
const safeWidth = Math.max(width, 1);
|
|
246
|
+
const safeHeight = Math.max(height, 1);
|
|
247
|
+
this.threeRenderer.setSize(safeWidth, safeHeight, false);
|
|
248
|
+
|
|
249
|
+
if (this.focusedCamera && this.focusedCamera.isPerspectiveCamera) {
|
|
250
|
+
this.focusedCamera.aspect = safeWidth / safeHeight;
|
|
251
|
+
}
|
|
252
|
+
if (this.focusedCamera) {
|
|
253
|
+
this.focusedCamera.updateProjectionMatrix();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
this._resizeObserver.observe(this.cellElm);
|
|
258
|
+
|
|
259
|
+
this._anim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Initial scan of cell children.
|
|
264
|
+
* @private
|
|
265
|
+
*/
|
|
266
|
+
_ScanCell() {
|
|
267
|
+
for (let i = 0; i < this.cellElm.children.length; i++) {
|
|
268
|
+
const convictElm = this.cellElm.children[i];
|
|
269
|
+
this.ScanElement(convictElm);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Convert a DOM element into a Three.js object and wire it up.
|
|
275
|
+
*
|
|
276
|
+
* @param {HTMLElement} elm
|
|
277
|
+
*/
|
|
278
|
+
ScanElement(elm) {
|
|
279
|
+
if (this._allConvictsByDom.has(elm)) return;
|
|
280
|
+
|
|
281
|
+
const parentObj = this.getConvictByDom(elm.parentElement) || this.loadedScene;
|
|
282
|
+
const instance = this.ConvertDomToObject(elm);
|
|
283
|
+
|
|
284
|
+
if (instance === null) {
|
|
285
|
+
// still recurse children
|
|
286
|
+
for (let i = 0; i < elm.children.length; i++) {
|
|
287
|
+
this.ScanElement(elm.children[i]);
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Camera tags: configure projection
|
|
293
|
+
if (elm.tagName.includes('CAMERA')) {
|
|
294
|
+
const rect = this.cellElm.getBoundingClientRect();
|
|
295
|
+
const aspect = rect.height ? rect.width / rect.height : 1;
|
|
296
|
+
|
|
297
|
+
if (elm.tagName === 'PERSPECTIVECAMERA') {
|
|
298
|
+
instance.fov = 75;
|
|
299
|
+
instance.aspect = aspect;
|
|
300
|
+
instance.far = 1000;
|
|
301
|
+
instance.near = 0.1;
|
|
302
|
+
} else {
|
|
303
|
+
const frustumSize = 20;
|
|
304
|
+
instance.frustumSize = frustumSize;
|
|
305
|
+
instance.aspect = aspect;
|
|
306
|
+
instance.left = (-frustumSize * aspect) / 2;
|
|
307
|
+
instance.right = (frustumSize * aspect) / 2;
|
|
308
|
+
instance.top = frustumSize / 2;
|
|
309
|
+
instance.bottom = -frustumSize / 2;
|
|
310
|
+
instance.refreshLook = fSize => {
|
|
311
|
+
instance.frustumSize = fSize;
|
|
312
|
+
instance.left = (-fSize * instance.aspect) / 2;
|
|
313
|
+
instance.right = (fSize * instance.aspect) / 2;
|
|
314
|
+
instance.top = fSize / 2;
|
|
315
|
+
instance.bottom = -fSize / 2;
|
|
316
|
+
instance.updateProjectionMatrix();
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const rectW = rect.width || 1;
|
|
321
|
+
const rectH = rect.height || 1;
|
|
322
|
+
|
|
323
|
+
if (elm.hasAttribute('render')) {
|
|
324
|
+
this.focusedCamera = instance;
|
|
325
|
+
this.focusedCamera.updateProjectionMatrix();
|
|
326
|
+
this.threeRenderer.setPixelRatio(window.devicePixelRatio || 1);
|
|
327
|
+
this.threeRenderer.setSize(rectW, rectH, false);
|
|
328
|
+
} else if (!this.focusedCamera) {
|
|
329
|
+
this.focusedCamera = instance;
|
|
330
|
+
this.focusedCamera.updateProjectionMatrix();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
instance.userData.domEl = elm;
|
|
335
|
+
instance.userData.extraParams = [];
|
|
336
|
+
instance.userData.classList = [];
|
|
337
|
+
instance.transition = null;
|
|
338
|
+
|
|
339
|
+
parentObj.add(instance);
|
|
340
|
+
|
|
341
|
+
if (elm.id) {
|
|
342
|
+
instance.userData.domId = elm.id;
|
|
343
|
+
this.namedConvicts.push(instance);
|
|
344
|
+
if (!this.constantConvicts.includes(instance) && getCSSRule(`#${elm.id}:active`)) {
|
|
345
|
+
this.constantConvicts.push(instance);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const classList = Array.from(elm.classList || []).filter(Boolean);
|
|
350
|
+
if (classList.length) {
|
|
351
|
+
instance.userData.classList = classList;
|
|
352
|
+
instance.name = classList[0];
|
|
353
|
+
this.classyConvicts.push(instance);
|
|
354
|
+
const hasActiveRule = classList.some(cls => getCSSRule(`.${cls}:active`));
|
|
355
|
+
if (hasActiveRule && !this.constantConvicts.includes(instance)) {
|
|
356
|
+
this.constantConvicts.push(instance);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this._allConvictsByDom.set(elm, instance);
|
|
361
|
+
|
|
362
|
+
for (let i = 0; i < elm.children.length; i++) {
|
|
363
|
+
this.ScanElement(elm.children[i]);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!Object.prototype.hasOwnProperty.call(elm, 'convict')) {
|
|
367
|
+
Object.defineProperty(elm, 'convict', {
|
|
368
|
+
value: this.getConvictByDom(elm),
|
|
369
|
+
enumerable: false
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Tag → THREE.Object3D constructor.
|
|
376
|
+
*
|
|
377
|
+
* @param {HTMLElement} elm
|
|
378
|
+
* @returns {THREE.Object3D|null}
|
|
379
|
+
*/
|
|
380
|
+
ConvertDomToObject(elm) {
|
|
381
|
+
if (elm.tagName === 'CANVAS') return null;
|
|
382
|
+
|
|
383
|
+
const key = elm.tagName.replace(/-/g, '');
|
|
384
|
+
const Ctor = getClassMap()[key];
|
|
385
|
+
if (!Ctor) {
|
|
386
|
+
console.warn(`Unknown THREE class for <${elm.tagName.toLowerCase()}>`);
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
return new Ctor();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Remove a convict and its children.
|
|
394
|
+
*
|
|
395
|
+
* @param {THREE.Object3D|null} convict
|
|
396
|
+
*/
|
|
397
|
+
removeConvict(convict) {
|
|
398
|
+
if (!convict) return;
|
|
399
|
+
|
|
400
|
+
convict.children.slice().forEach(child => {
|
|
401
|
+
const domNode = child.userData?.domEl;
|
|
402
|
+
if (domNode) {
|
|
403
|
+
this.removeConvict(this._allConvictsByDom.get(domNode));
|
|
404
|
+
} else {
|
|
405
|
+
this.removeConvict(child);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
fastRemove_arry(this.classyConvicts, convict);
|
|
410
|
+
fastRemove_arry(this.namedConvicts, convict);
|
|
411
|
+
fastRemove_arry(this.constantConvicts, convict);
|
|
412
|
+
|
|
413
|
+
if (convict.userData.domEl) {
|
|
414
|
+
this._allConvictsByDom.delete(convict.userData.domEl);
|
|
415
|
+
convict.userData.domEl.remove();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (convict.parent) {
|
|
419
|
+
convict.parent.remove(convict);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get convict by DOM element.
|
|
425
|
+
*
|
|
426
|
+
* @param {HTMLElement} element
|
|
427
|
+
*/
|
|
428
|
+
getConvictByDom(element) {
|
|
429
|
+
return this._allConvictsByDom.get(element);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get convict by DOM id (global document lookup).
|
|
434
|
+
*
|
|
435
|
+
* @param {string} id
|
|
436
|
+
*/
|
|
437
|
+
getConvictById(id) {
|
|
438
|
+
const el = document.getElementById(id);
|
|
439
|
+
return el ? this._allConvictsByDom.get(el) : undefined;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get all convicts with a given class.
|
|
444
|
+
*
|
|
445
|
+
* @param {string} className
|
|
446
|
+
* @returns {Array<THREE.Object3D>}
|
|
447
|
+
*/
|
|
448
|
+
getConvictsByClass(className) {
|
|
449
|
+
const elements = Array.from(document.getElementsByClassName(className));
|
|
450
|
+
const out = [];
|
|
451
|
+
elements.forEach(elm => {
|
|
452
|
+
const convict = this.getConvictByDom(elm);
|
|
453
|
+
if (convict) out.push(convict);
|
|
454
|
+
});
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Register a per-frame callback.
|
|
460
|
+
*
|
|
461
|
+
* @param {Function} fn
|
|
462
|
+
*/
|
|
463
|
+
addUpdateFunction(fn) {
|
|
464
|
+
if (typeof fn === 'function') {
|
|
465
|
+
const bound = fn.bind(this);
|
|
466
|
+
bound.originalFn = fn;
|
|
467
|
+
this.updateFunds.push(bound);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Remove a previously registered per-frame callback.
|
|
473
|
+
*
|
|
474
|
+
* @param {Function} fn
|
|
475
|
+
*/
|
|
476
|
+
removeUpdateFunction(fn) {
|
|
477
|
+
const idx = this.updateFunds.findIndex(item => item?.originalFn === fn);
|
|
478
|
+
if (idx >= 0) {
|
|
479
|
+
this.updateFunds.splice(idx, 1);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Tear down observers, handlers and canvas.
|
|
485
|
+
*/
|
|
486
|
+
dispose() {
|
|
487
|
+
this._running = false;
|
|
488
|
+
|
|
489
|
+
this._resizeObserver.disconnect();
|
|
490
|
+
this._styleObserver.disconnect();
|
|
491
|
+
this._styleElemObserver.disconnect();
|
|
492
|
+
this._styleHostObserver.disconnect();
|
|
493
|
+
|
|
494
|
+
this.cellElm.removeEventListener('mousemove', this._boundPointerMove);
|
|
495
|
+
this.cellElm.removeEventListener('click', this._boundClick);
|
|
496
|
+
this.cellElm.removeEventListener('mousedown', this._boundMouseDown);
|
|
497
|
+
this.cellElm.removeEventListener('mouseup', this._boundMouseUp);
|
|
498
|
+
this.cellElm.removeEventListener('dblclick', this._boundDoubleClick);
|
|
499
|
+
this.cellElm.removeEventListener('contextmenu', this._boundContextMenu);
|
|
500
|
+
|
|
501
|
+
const canvas = this.threeRenderer.domElement;
|
|
502
|
+
if (canvas && canvas.parentNode) {
|
|
503
|
+
canvas.parentNode.removeChild(canvas);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export default Cell;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// index.js
|
|
2
|
+
//
|
|
3
|
+
// Entry point for the JailedThreeJS module.
|
|
4
|
+
// Re-exports all public API in one place.
|
|
5
|
+
|
|
6
|
+
export { default as Cell } from './cell.js';
|
|
7
|
+
export { default as JThree } from './main.js';
|
|
8
|
+
|
|
9
|
+
export * from './artist.js';
|
|
10
|
+
export * from './NoScope.js';
|
|
11
|
+
export * from './Train.js';
|
|
12
|
+
export * from './utils.js';
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// main.js
|
|
2
|
+
//
|
|
3
|
+
// JThree facade: finds <cell> elements, bootstraps renderer/scene/cell
|
|
4
|
+
// for each, and keeps a WeakMap of created Cell instances.
|
|
5
|
+
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
import Cell from './cell.js';
|
|
8
|
+
|
|
9
|
+
class JTHREE {
|
|
10
|
+
static __Loaded_Cells__ = new WeakMap();
|
|
11
|
+
static __StyleTag__ = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert all <cell> elements in the document.
|
|
15
|
+
*/
|
|
16
|
+
static init_convert() {
|
|
17
|
+
if (!JTHREE.__StyleTag__ && document.head) {
|
|
18
|
+
const styleSheet = document.createElement('style');
|
|
19
|
+
styleSheet.textContent = `
|
|
20
|
+
cell > :not(canvas) {
|
|
21
|
+
display: none;
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
document.head.appendChild(styleSheet);
|
|
25
|
+
JTHREE.__StyleTag__ = styleSheet;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
document.querySelectorAll('cell').forEach(el => {
|
|
29
|
+
if (JTHREE.__Loaded_Cells__.has(el)) return;
|
|
30
|
+
JTHREE.create_THREEJSRENDERER(el);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Legacy alias.
|
|
36
|
+
*/
|
|
37
|
+
static _convert_init_() {
|
|
38
|
+
return JTHREE.init_convert();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create renderer + scene for a given <cell> element.
|
|
43
|
+
*
|
|
44
|
+
* @param {HTMLElement} cellEl
|
|
45
|
+
* @returns {Cell}
|
|
46
|
+
*/
|
|
47
|
+
static create_THREEJSRENDERER(cellEl) {
|
|
48
|
+
if (JTHREE.__Loaded_Cells__.has(cellEl)) {
|
|
49
|
+
return JTHREE.__Loaded_Cells__.get(cellEl);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { canvas, width, height, dpr } = createWebGLOverlay(cellEl);
|
|
53
|
+
const safeWidth = width || 1;
|
|
54
|
+
const safeHeight = height || 1;
|
|
55
|
+
|
|
56
|
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
|
57
|
+
renderer.setPixelRatio(dpr);
|
|
58
|
+
renderer.setSize(safeWidth, safeHeight, false);
|
|
59
|
+
renderer.setClearColor(0x000000, 1);
|
|
60
|
+
|
|
61
|
+
const scene = new THREE.Scene();
|
|
62
|
+
|
|
63
|
+
// Find explicit cameras
|
|
64
|
+
const regex = /camera/i;
|
|
65
|
+
const foundCameraElms = Array.from(cellEl.children).filter(child =>
|
|
66
|
+
regex.test(child.tagName) ||
|
|
67
|
+
regex.test(child.id) ||
|
|
68
|
+
regex.test(child.className)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
let camera = null;
|
|
72
|
+
if (foundCameraElms.length === 0) {
|
|
73
|
+
camera = new THREE.PerspectiveCamera(75, safeWidth / safeHeight, 0.1, 1000);
|
|
74
|
+
console.warn('No camera found for', cellEl, '. Creating a default camera.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const cell = new Cell(cellEl, renderer, scene, camera || null);
|
|
78
|
+
JTHREE.__Loaded_Cells__.set(cellEl, cell);
|
|
79
|
+
|
|
80
|
+
cellEl.dispatchEvent(
|
|
81
|
+
new CustomEvent('OnStart', { detail: { cell, CellEl: cellEl } })
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return cell;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a WebGL canvas overlay on a host element.
|
|
90
|
+
*
|
|
91
|
+
* @param {HTMLElement} hostEl
|
|
92
|
+
* @param {Object} [glOptions={}]
|
|
93
|
+
* @returns {{canvas:HTMLCanvasElement, gl:WebGLRenderingContext, width:number, height:number, dpr:number}}
|
|
94
|
+
*/
|
|
95
|
+
function createWebGLOverlay(hostEl, glOptions = {}) {
|
|
96
|
+
const { width, height } = hostEl.getBoundingClientRect();
|
|
97
|
+
const dpr = window.devicePixelRatio || 1;
|
|
98
|
+
|
|
99
|
+
if (getComputedStyle(hostEl).position === 'static') {
|
|
100
|
+
hostEl.style.position = 'relative';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const canvas = document.createElement('canvas');
|
|
104
|
+
canvas.width = Math.max(1, Math.round(width * dpr));
|
|
105
|
+
canvas.height = Math.max(1, Math.round(height * dpr));
|
|
106
|
+
Object.assign(canvas.style, {
|
|
107
|
+
position: 'absolute',
|
|
108
|
+
top: '0',
|
|
109
|
+
left: '0',
|
|
110
|
+
width: `${width}px`,
|
|
111
|
+
height: `${height}px`,
|
|
112
|
+
pointerEvents: 'none',
|
|
113
|
+
//zIndex: '-999'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
hostEl.appendChild(canvas);
|
|
117
|
+
|
|
118
|
+
const gl =
|
|
119
|
+
canvas.getContext('webgl2', glOptions) ||
|
|
120
|
+
canvas.getContext('webgl', glOptions) ||
|
|
121
|
+
canvas.getContext('experimental-webgl', glOptions);
|
|
122
|
+
|
|
123
|
+
if (!gl) {
|
|
124
|
+
throw new Error('Your browser doesn’t support WebGL.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
128
|
+
return { canvas, gl, width, height, dpr };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Auto-initialise on import.
|
|
132
|
+
JTHREE.init_convert();
|
|
133
|
+
window.JThree = JTHREE;
|
|
134
|
+
|
|
135
|
+
export { JTHREE };
|
|
136
|
+
export default JTHREE;
|