@mirage-engine/core 0.2.2 → 0.3.1
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/CHANGELOG.md +21 -0
- package/dist/mirage-engine.js +990 -639
- package/dist/mirage-engine.umd.js +16 -10
- package/dist/src/animation/Animator.d.ts +1 -6
- package/dist/src/core/Engine.d.ts +3 -0
- package/dist/src/core/Syncer.d.ts +2 -20
- package/dist/src/dom/Extractor.d.ts +1 -2
- package/dist/src/renderer/Renderer.d.ts +7 -5
- package/dist/src/types/attributes.d.ts +63 -0
- package/dist/src/types/common.d.ts +4 -0
- package/dist/src/types/config.d.ts +2 -8
- package/dist/src/types/flags.d.ts +8 -7
- package/dist/src/types/index.d.ts +2 -1
- package/package.json +3 -2
- package/src/animation/Animator.ts +2 -57
- package/src/core/Engine.ts +14 -2
- package/src/core/Syncer.ts +39 -235
- package/src/dom/Extractor.ts +357 -68
- package/src/renderer/Renderer.ts +283 -98
- package/src/store/TextureLifecycleManager.ts +24 -9
- package/src/types/attributes.ts +57 -0
- package/src/types/common.ts +4 -0
- package/src/types/config.ts +5 -10
- package/src/types/flags.ts +20 -15
- package/src/types/index.ts +2 -1
package/src/renderer/Renderer.ts
CHANGED
|
@@ -6,10 +6,13 @@ import {
|
|
|
6
6
|
CoreConfig,
|
|
7
7
|
MirageMode,
|
|
8
8
|
USER_LAYER,
|
|
9
|
-
|
|
9
|
+
SELECT_LAYER,
|
|
10
10
|
travelerClipArea,
|
|
11
|
+
THREE_LAYERS,
|
|
12
|
+
ATTR_TRAVEL,
|
|
13
|
+
LayerTarget,
|
|
11
14
|
} from "../types";
|
|
12
|
-
import { Painter } from "@mirage-engine/painter";
|
|
15
|
+
import { Painter, TextStyles, BoxStyles } from "@mirage-engine/painter";
|
|
13
16
|
import { MeshRegistry } from "../store/MeshRegistry";
|
|
14
17
|
import { TextureLifecycleManager } from "../store/TextureLifecycleManager";
|
|
15
18
|
|
|
@@ -18,18 +21,22 @@ export class Renderer {
|
|
|
18
21
|
private readonly scene: THREE.Scene;
|
|
19
22
|
private readonly camera: THREE.OrthographicCamera;
|
|
20
23
|
private readonly renderer: THREE.WebGLRenderer;
|
|
21
|
-
private
|
|
24
|
+
private renderTargets: THREE.WebGLRenderTarget[] = [];
|
|
22
25
|
private renderOrder: number = 0;
|
|
23
|
-
|
|
26
|
+
public qualityFactor: number = 2;
|
|
24
27
|
private mode: MirageMode = "overlay";
|
|
25
28
|
private clipArea: travelerClipArea = 1;
|
|
29
|
+
public targetLayer: number | LayerTarget = "base";
|
|
26
30
|
|
|
27
31
|
private target: HTMLElement;
|
|
28
32
|
private mountContainer: HTMLElement;
|
|
29
33
|
private registry: MeshRegistry;
|
|
30
34
|
private targetRect: DOMRect;
|
|
31
35
|
|
|
32
|
-
private
|
|
36
|
+
private travelersByLayer: Set<THREE.Mesh>[] = Array.from(
|
|
37
|
+
{ length: ATTR_TRAVEL.MAX_LAYERS },
|
|
38
|
+
() => new Set(),
|
|
39
|
+
);
|
|
33
40
|
private textureManager: TextureLifecycleManager;
|
|
34
41
|
// private meshMap: Map<HTMLElement, THREE.Mesh> = new Map();
|
|
35
42
|
|
|
@@ -44,7 +51,7 @@ export class Renderer {
|
|
|
44
51
|
this.target = target;
|
|
45
52
|
this.mountContainer = mountContainer;
|
|
46
53
|
this.registry = registry;
|
|
47
|
-
|
|
54
|
+
|
|
48
55
|
this.textureManager = new TextureLifecycleManager((el, texture) => {
|
|
49
56
|
const mesh = this.registry.get(el);
|
|
50
57
|
if (mesh && mesh.material instanceof THREE.ShaderMaterial) {
|
|
@@ -54,6 +61,7 @@ export class Renderer {
|
|
|
54
61
|
|
|
55
62
|
this.mode = config.mode ?? "overlay";
|
|
56
63
|
this.clipArea = config.travelerClipArea ?? 1;
|
|
64
|
+
this.targetLayer = config.layer ?? "base";
|
|
57
65
|
this.canvas = document.createElement("canvas");
|
|
58
66
|
this.scene = new THREE.Scene();
|
|
59
67
|
|
|
@@ -74,8 +82,7 @@ export class Renderer {
|
|
|
74
82
|
1000,
|
|
75
83
|
);
|
|
76
84
|
this.camera.position.z = 100;
|
|
77
|
-
this.camera.layers.set(
|
|
78
|
-
|
|
85
|
+
this.camera.layers.set(this.getSceneLayer());
|
|
79
86
|
|
|
80
87
|
this.renderer = new THREE.WebGLRenderer({
|
|
81
88
|
canvas: this.canvas,
|
|
@@ -85,29 +92,41 @@ export class Renderer {
|
|
|
85
92
|
// premultipliedAlpha: true
|
|
86
93
|
});
|
|
87
94
|
|
|
88
|
-
|
|
89
95
|
THREE.ColorManagement.enabled = false;
|
|
90
96
|
this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
|
|
91
97
|
|
|
92
|
-
|
|
93
98
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
94
99
|
this.renderer.setSize(width, height);
|
|
95
100
|
|
|
96
101
|
this.applyTextQuality(config.quality ?? "medium");
|
|
97
102
|
}
|
|
103
|
+
|
|
104
|
+
private getSceneLayer(): number {
|
|
105
|
+
if (typeof this.targetLayer === "number") {
|
|
106
|
+
return this.targetLayer;
|
|
107
|
+
} else if (this.targetLayer === "selected") {
|
|
108
|
+
return THREE_LAYERS.SELECTED;
|
|
109
|
+
} else {
|
|
110
|
+
return THREE_LAYERS.BASE;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
98
114
|
public createRenderTarget() {
|
|
99
|
-
|
|
100
|
-
this.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
for (let i = 0; i < ATTR_TRAVEL.MAX_LAYERS; i++) {
|
|
116
|
+
this.renderTargets.push(
|
|
117
|
+
new THREE.WebGLRenderTarget(
|
|
118
|
+
this.targetRect.width * this.qualityFactor,
|
|
119
|
+
this.targetRect.height * this.qualityFactor,
|
|
120
|
+
{
|
|
121
|
+
minFilter: THREE.LinearFilter,
|
|
122
|
+
magFilter: THREE.LinearFilter,
|
|
123
|
+
format: THREE.RGBAFormat,
|
|
124
|
+
stencilBuffer: false,
|
|
125
|
+
depthBuffer: true,
|
|
126
|
+
},
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
private applyTextQuality(quality: Quality) {
|
|
@@ -123,6 +142,9 @@ export class Renderer {
|
|
|
123
142
|
this.qualityFactor = 4;
|
|
124
143
|
break;
|
|
125
144
|
case "medium":
|
|
145
|
+
// this.qualityFactor = 2;
|
|
146
|
+
this.qualityFactor = 2;
|
|
147
|
+
break;
|
|
126
148
|
default:
|
|
127
149
|
this.qualityFactor = 2;
|
|
128
150
|
break;
|
|
@@ -154,6 +176,31 @@ export class Renderer {
|
|
|
154
176
|
}
|
|
155
177
|
}
|
|
156
178
|
|
|
179
|
+
public updateUniforms(element: HTMLElement, uniforms: Record<string, any>) {
|
|
180
|
+
const mesh = this.registry.get(element);
|
|
181
|
+
if (!mesh) return;
|
|
182
|
+
|
|
183
|
+
mesh.traverse((child) => {
|
|
184
|
+
if ((child as THREE.Mesh).isMesh && (child as THREE.Mesh).material) {
|
|
185
|
+
Painter.forceUpdateUniforms(
|
|
186
|
+
(child as THREE.Mesh).material as THREE.ShaderMaterial,
|
|
187
|
+
uniforms,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (mesh.userData.nativeMesh) {
|
|
193
|
+
mesh.userData.nativeMesh.traverse((child: THREE.Object3D) => {
|
|
194
|
+
if ((child as THREE.Mesh).isMesh && (child as THREE.Mesh).material) {
|
|
195
|
+
Painter.forceUpdateUniforms(
|
|
196
|
+
(child as THREE.Mesh).material as THREE.ShaderMaterial,
|
|
197
|
+
uniforms,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
157
204
|
public dispose() {
|
|
158
205
|
this.renderer.dispose();
|
|
159
206
|
this.canvas.remove();
|
|
@@ -163,11 +210,8 @@ export class Renderer {
|
|
|
163
210
|
|
|
164
211
|
public setSize(width: number, height: number) {
|
|
165
212
|
this.renderer.setSize(width, height);
|
|
166
|
-
|
|
167
|
-
this.
|
|
168
|
-
width * this.qualityFactor,
|
|
169
|
-
height * this.qualityFactor,
|
|
170
|
-
);
|
|
213
|
+
for (const target of this.renderTargets) {
|
|
214
|
+
target.setSize(width * this.qualityFactor, height * this.qualityFactor);
|
|
171
215
|
}
|
|
172
216
|
this.camera.left = width / -2;
|
|
173
217
|
this.camera.right = width / 2;
|
|
@@ -202,9 +246,6 @@ export class Renderer {
|
|
|
202
246
|
|
|
203
247
|
this.renderOrder = 0;
|
|
204
248
|
|
|
205
|
-
// const activeElements = new Set<HTMLElement>();
|
|
206
|
-
|
|
207
|
-
// this.reconcileNode(graphNode, activeElements);
|
|
208
249
|
this.reconcileNode(graphNode);
|
|
209
250
|
|
|
210
251
|
if (pendingDeletions.size > 0) {
|
|
@@ -212,9 +253,20 @@ export class Renderer {
|
|
|
212
253
|
const meshToDestroy = this.registry.get(el) as THREE.Mesh | undefined;
|
|
213
254
|
if (meshToDestroy) {
|
|
214
255
|
this.scene.remove(meshToDestroy);
|
|
215
|
-
this.
|
|
256
|
+
for (const set of this.travelersByLayer) {
|
|
257
|
+
set.delete(meshToDestroy);
|
|
258
|
+
}
|
|
216
259
|
this.fixedMeshes.delete(meshToDestroy);
|
|
217
260
|
meshToDestroy.geometry.dispose();
|
|
261
|
+
if (meshToDestroy.userData.nativeMesh) {
|
|
262
|
+
this.scene.remove(meshToDestroy.userData.nativeMesh);
|
|
263
|
+
if (Array.isArray(meshToDestroy.userData.nativeMesh.material)) {
|
|
264
|
+
meshToDestroy.userData.nativeMesh.material.forEach((mat: THREE.Material) => mat.dispose());
|
|
265
|
+
} else {
|
|
266
|
+
meshToDestroy.userData.nativeMesh.material.dispose();
|
|
267
|
+
}
|
|
268
|
+
meshToDestroy.userData.nativeMesh.geometry.dispose();
|
|
269
|
+
}
|
|
218
270
|
meshToDestroy.traverse((child) => {
|
|
219
271
|
if (child instanceof THREE.Mesh) {
|
|
220
272
|
if (child.geometry) child.geometry.dispose();
|
|
@@ -246,16 +298,15 @@ export class Renderer {
|
|
|
246
298
|
|
|
247
299
|
// private reconcileNode(node: SceneNode, activeElements: Set<HTMLElement>) {
|
|
248
300
|
private reconcileNode(node: SceneNode) {
|
|
249
|
-
// activeElements.add(node.element);
|
|
250
|
-
|
|
251
|
-
// let mesh = this.meshMap.get(node.element);
|
|
252
301
|
let mesh = this.registry.get(node.element) as THREE.Mesh | undefined;
|
|
302
|
+
|
|
253
303
|
if (!mesh) {
|
|
254
304
|
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
255
|
-
|
|
256
|
-
|
|
305
|
+
const initialTexture = node.isTraveler
|
|
306
|
+
? this.renderTargets[node.captureLayer - 2]?.texture
|
|
307
|
+
: this.textureManager.get(node.element);
|
|
257
308
|
|
|
258
|
-
material = Painter.create(
|
|
309
|
+
const material = Painter.create(
|
|
259
310
|
"BOX",
|
|
260
311
|
node.styles,
|
|
261
312
|
"",
|
|
@@ -265,23 +316,34 @@ export class Renderer {
|
|
|
265
316
|
initialTexture,
|
|
266
317
|
node.shaderHooks,
|
|
267
318
|
);
|
|
319
|
+
|
|
268
320
|
mesh = new THREE.Mesh(geometry, material);
|
|
321
|
+
|
|
269
322
|
if (node.type === "TEXT") mesh.name = "BG_MESH";
|
|
270
323
|
this.scene.add(mesh);
|
|
324
|
+
|
|
271
325
|
this.registry.register(node.element, mesh);
|
|
326
|
+
mesh.userData.baseMaterial = material;
|
|
272
327
|
}
|
|
273
328
|
|
|
329
|
+
|
|
330
|
+
|
|
274
331
|
// [Important] use whene mesh animating with js
|
|
275
|
-
mesh.userData.domRect = node.rect;
|
|
276
332
|
|
|
277
333
|
this.updateMeshProperties(mesh, node);
|
|
278
334
|
this.updateMeshLayers(mesh, node);
|
|
279
335
|
if (node.isTraveler) {
|
|
280
|
-
|
|
281
|
-
|
|
336
|
+
for (let i = 0; i < ATTR_TRAVEL.MAX_LAYERS; i++) {
|
|
337
|
+
if (i === node.captureLayer - 2) {
|
|
338
|
+
this.travelersByLayer[i].add(mesh);
|
|
339
|
+
} else {
|
|
340
|
+
this.travelersByLayer[i].delete(mesh);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
282
343
|
} else {
|
|
283
|
-
|
|
284
|
-
|
|
344
|
+
for (const set of this.travelersByLayer) {
|
|
345
|
+
set.delete(mesh);
|
|
346
|
+
}
|
|
285
347
|
}
|
|
286
348
|
|
|
287
349
|
if (node.isFixed) {
|
|
@@ -301,22 +363,33 @@ export class Renderer {
|
|
|
301
363
|
this.reconcileNode(child);
|
|
302
364
|
}
|
|
303
365
|
} else if (node.type === "TEXT") {
|
|
304
|
-
this.reconcileTextChild(mesh, node);
|
|
366
|
+
this.reconcileTextChild(mesh, node, false);
|
|
367
|
+
if (mesh.userData.nativeMesh && node.nativeStyles) {
|
|
368
|
+
this.reconcileTextChild(mesh.userData.nativeMesh as THREE.Mesh, node, true);
|
|
369
|
+
}
|
|
305
370
|
}
|
|
306
371
|
}
|
|
307
372
|
|
|
308
|
-
private reconcileTextChild(parentMesh: THREE.Mesh, node: SceneNode) {
|
|
309
|
-
const lines = node.textLines || [
|
|
310
|
-
|
|
373
|
+
private reconcileTextChild(parentMesh: THREE.Mesh, node: SceneNode, isNative: boolean) {
|
|
374
|
+
const lines = node.textLines || [
|
|
375
|
+
{ text: node.textContent || "", rect: node.rect },
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
const stylesToUse = (isNative ? node.nativeStyles : node.textStyles) as TextStyles;
|
|
379
|
+
const currentStyleHash =
|
|
380
|
+
JSON.stringify(stylesToUse) +
|
|
381
|
+
node.textContent +
|
|
382
|
+
lines.map((l) => l.text).join("|");
|
|
311
383
|
const cachedStyleHash = parentMesh.userData?.textChildStyleHash;
|
|
312
384
|
const isDirty =
|
|
313
|
-
node.dirtyMask & DIRTY_CONTENT ||
|
|
314
|
-
currentStyleHash !== cachedStyleHash;
|
|
385
|
+
node.dirtyMask & DIRTY_CONTENT || currentStyleHash !== cachedStyleHash;
|
|
315
386
|
|
|
316
387
|
if (isDirty) {
|
|
317
388
|
// Remove all existing TEXT_CHILD meshes
|
|
318
|
-
const existingChildren = parentMesh.children.filter(c =>
|
|
319
|
-
|
|
389
|
+
const existingChildren = parentMesh.children.filter((c) =>
|
|
390
|
+
c.name.startsWith("TEXT_CHILD"),
|
|
391
|
+
);
|
|
392
|
+
existingChildren.forEach((child) => {
|
|
320
393
|
const textMesh = child as THREE.Mesh;
|
|
321
394
|
(textMesh.material as THREE.MeshBasicMaterial).map?.dispose();
|
|
322
395
|
textMesh.geometry.dispose();
|
|
@@ -330,7 +403,7 @@ export class Renderer {
|
|
|
330
403
|
lines.forEach((line, index) => {
|
|
331
404
|
const material = Painter.create(
|
|
332
405
|
"TEXT",
|
|
333
|
-
|
|
406
|
+
stylesToUse,
|
|
334
407
|
line.text,
|
|
335
408
|
line.rect.width,
|
|
336
409
|
line.rect.height,
|
|
@@ -341,12 +414,13 @@ export class Renderer {
|
|
|
341
414
|
const textMesh = new THREE.Mesh(geometry, material);
|
|
342
415
|
|
|
343
416
|
textMesh.name = `TEXT_CHILD_${index}`;
|
|
344
|
-
this.updateMeshLayers(textMesh, node);
|
|
345
417
|
|
|
346
|
-
// Parent is already scaled to node.rect.width/height.
|
|
418
|
+
// Parent is already scaled to node.rect.width/height.
|
|
347
419
|
// We counter-scale the child so its absolute size is exactly line.rect.width/height.
|
|
348
|
-
const scaleX =
|
|
349
|
-
|
|
420
|
+
const scaleX =
|
|
421
|
+
node.rect.width === 0 ? 1 : line.rect.width / node.rect.width;
|
|
422
|
+
const scaleY =
|
|
423
|
+
node.rect.height === 0 ? 1 : line.rect.height / node.rect.height;
|
|
350
424
|
textMesh.scale.set(scaleX, scaleY, 1);
|
|
351
425
|
|
|
352
426
|
const textCenterX = line.rect.x + line.rect.width / 2;
|
|
@@ -359,7 +433,7 @@ export class Renderer {
|
|
|
359
433
|
textMesh.position.set(
|
|
360
434
|
node.rect.width === 0 ? 0 : offsetX / node.rect.width,
|
|
361
435
|
node.rect.height === 0 ? 0 : offsetY / node.rect.height,
|
|
362
|
-
0.005
|
|
436
|
+
0.005,
|
|
363
437
|
);
|
|
364
438
|
|
|
365
439
|
parentMesh.add(textMesh);
|
|
@@ -367,6 +441,35 @@ export class Renderer {
|
|
|
367
441
|
|
|
368
442
|
parentMesh.userData.textChildStyleHash = currentStyleHash;
|
|
369
443
|
}
|
|
444
|
+
|
|
445
|
+
// Always update layers for all text children, as visibility/nativeLayer can change without dirtifying the style
|
|
446
|
+
parentMesh.children.forEach((child) => {
|
|
447
|
+
if (!child.name.startsWith("TEXT_CHILD")) return;
|
|
448
|
+
const textMesh = child as THREE.Mesh;
|
|
449
|
+
|
|
450
|
+
const layerNum = node.visibility & USER_LAYER ? THREE_LAYERS.BASE : THREE_LAYERS.HIDDEN;
|
|
451
|
+
textMesh.layers.set(layerNum);
|
|
452
|
+
|
|
453
|
+
if (node.visibility & SELECT_LAYER) {
|
|
454
|
+
textMesh.layers.enable(THREE_LAYERS.SELECTED);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (node.visibility & USER_LAYER) {
|
|
458
|
+
if (!isNative && node.nativeLayer !== undefined && node.nativeStyles !== undefined) {
|
|
459
|
+
for (let i = node.captureLayer; i < node.nativeLayer; i++) {
|
|
460
|
+
textMesh.layers.enable(THREE_LAYERS.getCaptureLayer(i));
|
|
461
|
+
}
|
|
462
|
+
} else if (isNative && node.nativeLayer !== undefined) {
|
|
463
|
+
for (let i = Math.max(node.captureLayer, node.nativeLayer); i <= ATTR_TRAVEL.MAX_LAYERS + 1; i++) {
|
|
464
|
+
textMesh.layers.enable(THREE_LAYERS.getCaptureLayer(i));
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
for (let i = node.captureLayer; i <= ATTR_TRAVEL.MAX_LAYERS + 1; i++) {
|
|
468
|
+
textMesh.layers.enable(THREE_LAYERS.getCaptureLayer(i));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
370
473
|
}
|
|
371
474
|
|
|
372
475
|
private updateMeshProperties(mesh: THREE.Mesh, node: SceneNode) {
|
|
@@ -375,9 +478,16 @@ export class Renderer {
|
|
|
375
478
|
const pixelRatio = this.renderer.getPixelRatio();
|
|
376
479
|
const canvasWidth = this.renderer.domElement.width / pixelRatio;
|
|
377
480
|
const canvasHeight = this.renderer.domElement.height / pixelRatio;
|
|
378
|
-
|
|
481
|
+
|
|
482
|
+
mesh.material = mesh.userData.baseMaterial as THREE.Material;
|
|
379
483
|
mesh.scale.set(rect.width, rect.height, 1);
|
|
380
484
|
|
|
485
|
+
mesh.userData.domRect = {
|
|
486
|
+
...rect,
|
|
487
|
+
width: rect.width,
|
|
488
|
+
height: rect.height,
|
|
489
|
+
};
|
|
490
|
+
|
|
381
491
|
const Z_MICRO_OFFSET = 0.001;
|
|
382
492
|
this.renderOrder++;
|
|
383
493
|
|
|
@@ -401,15 +511,18 @@ export class Renderer {
|
|
|
401
511
|
mesh.userData.basePosition = { x: baseX, y: baseY };
|
|
402
512
|
mesh.userData.originalBasePosition = { x: baseX, y: baseY };
|
|
403
513
|
mesh.userData.baseSize = { width: rect.width, height: rect.height };
|
|
404
|
-
|
|
514
|
+
|
|
405
515
|
mesh.userData.baseDOM = { x: pureDOM_X, y: pureDOM_Y };
|
|
406
516
|
mesh.userData.isFixed = node.isFixed;
|
|
407
517
|
mesh.userData.initialScroll = { x: window.scrollX, y: window.scrollY };
|
|
408
518
|
|
|
409
|
-
|
|
410
|
-
|
|
519
|
+
const targetEl =
|
|
520
|
+
node.element.nodeType === Node.TEXT_NODE
|
|
521
|
+
? node.element.parentElement!
|
|
522
|
+
: node.element;
|
|
411
523
|
const computed = window.getComputedStyle(targetEl);
|
|
412
|
-
let baseTx = 0,
|
|
524
|
+
let baseTx = 0,
|
|
525
|
+
baseTy = 0;
|
|
413
526
|
if (computed.transform && computed.transform !== "none") {
|
|
414
527
|
const matrix = new DOMMatrix(computed.transform);
|
|
415
528
|
baseTx = matrix.m41;
|
|
@@ -417,7 +530,6 @@ export class Renderer {
|
|
|
417
530
|
}
|
|
418
531
|
mesh.userData.baseTransform = { x: baseTx, y: baseTy };
|
|
419
532
|
|
|
420
|
-
// ✨ 추가: 애니메이션이 끝나고 씬이 갱신되면 비율 캐시 초기화!
|
|
421
533
|
delete mesh.userData.originRatioX;
|
|
422
534
|
delete mesh.userData.originRatioY;
|
|
423
535
|
// mesh.position.set(
|
|
@@ -426,42 +538,127 @@ export class Renderer {
|
|
|
426
538
|
// styles.zIndex + this.renderOrder * Z_MICRO_OFFSET,
|
|
427
539
|
// );
|
|
428
540
|
Painter.update(
|
|
429
|
-
mesh.
|
|
541
|
+
mesh.userData.baseMaterial,
|
|
430
542
|
"BOX",
|
|
431
|
-
|
|
543
|
+
styles,
|
|
432
544
|
"",
|
|
433
|
-
|
|
434
|
-
|
|
545
|
+
rect.width,
|
|
546
|
+
rect.height,
|
|
435
547
|
this.qualityFactor,
|
|
436
|
-
node.isTraveler
|
|
548
|
+
node.isTraveler
|
|
549
|
+
? this.renderTargets[node.captureLayer - 2]?.texture
|
|
550
|
+
: this.textureManager.get(node.element),
|
|
437
551
|
);
|
|
552
|
+
|
|
553
|
+
if (node.nativeStyles && node.nativeRect) {
|
|
554
|
+
if (!mesh.userData.nativeMesh) {
|
|
555
|
+
const nativeMaterial = Painter.create(
|
|
556
|
+
"BOX",
|
|
557
|
+
node.nativeStyles,
|
|
558
|
+
"",
|
|
559
|
+
node.nativeRect.width,
|
|
560
|
+
node.nativeRect.height,
|
|
561
|
+
this.qualityFactor,
|
|
562
|
+
node.isTraveler
|
|
563
|
+
? this.renderTargets[node.captureLayer - 2]?.texture
|
|
564
|
+
: this.textureManager.get(node.element),
|
|
565
|
+
node.shaderHooks,
|
|
566
|
+
);
|
|
567
|
+
const nativeMesh = new THREE.Mesh(mesh.geometry, nativeMaterial);
|
|
568
|
+
if (node.type === "TEXT") nativeMesh.name = "BG_MESH";
|
|
569
|
+
this.scene.add(nativeMesh);
|
|
570
|
+
mesh.userData.nativeMesh = nativeMesh;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const nativeMesh = mesh.userData.nativeMesh as THREE.Mesh;
|
|
574
|
+
const nativeLocalX = node.nativeRect.x - targetPageX;
|
|
575
|
+
const nativeLocalY = node.nativeRect.y - targetPageY;
|
|
576
|
+
const nativeBaseX = nativeLocalX - canvasWidth / 2 + node.nativeRect.width / 2;
|
|
577
|
+
const nativeBaseY = -nativeLocalY + canvasHeight / 2 - node.nativeRect.height / 2;
|
|
578
|
+
|
|
579
|
+
nativeMesh.scale.set(node.nativeRect.width, node.nativeRect.height, 1);
|
|
580
|
+
nativeMesh.position.set(
|
|
581
|
+
nativeBaseX,
|
|
582
|
+
nativeBaseY,
|
|
583
|
+
(node.nativeStyles as BoxStyles).zIndex + this.renderOrder * Z_MICRO_OFFSET,
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
Painter.update(
|
|
587
|
+
nativeMesh.material as THREE.Material,
|
|
588
|
+
"BOX",
|
|
589
|
+
node.nativeStyles,
|
|
590
|
+
"",
|
|
591
|
+
node.nativeRect.width,
|
|
592
|
+
node.nativeRect.height,
|
|
593
|
+
this.qualityFactor,
|
|
594
|
+
node.isTraveler
|
|
595
|
+
? this.renderTargets[node.captureLayer - 2]?.texture
|
|
596
|
+
: this.textureManager.get(node.element),
|
|
597
|
+
);
|
|
598
|
+
} else {
|
|
599
|
+
if (mesh.userData.nativeMesh) {
|
|
600
|
+
this.scene.remove(mesh.userData.nativeMesh);
|
|
601
|
+
if (mesh.userData.nativeMesh.material instanceof THREE.Material) {
|
|
602
|
+
mesh.userData.nativeMesh.material.dispose();
|
|
603
|
+
}
|
|
604
|
+
delete mesh.userData.nativeMesh;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
438
607
|
}
|
|
439
608
|
|
|
440
609
|
private updateMeshLayers(mesh: THREE.Mesh, node: SceneNode) {
|
|
441
|
-
const layerNum =
|
|
610
|
+
const layerNum =
|
|
611
|
+
node.visibility & USER_LAYER ? THREE_LAYERS.BASE : THREE_LAYERS.HIDDEN;
|
|
442
612
|
mesh.layers.set(layerNum);
|
|
443
|
-
|
|
613
|
+
|
|
614
|
+
if (node.visibility & SELECT_LAYER) {
|
|
615
|
+
mesh.layers.enable(THREE_LAYERS.SELECTED);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (mesh.userData.nativeMesh && node.nativeLayer !== undefined) {
|
|
619
|
+
const nativeMesh = mesh.userData.nativeMesh as THREE.Mesh;
|
|
620
|
+
nativeMesh.layers.set(THREE_LAYERS.HIDDEN);
|
|
621
|
+
|
|
622
|
+
if (node.visibility & USER_LAYER) {
|
|
623
|
+
for (let i = node.captureLayer; i < node.nativeLayer; i++) {
|
|
624
|
+
mesh.layers.enable(THREE_LAYERS.getCaptureLayer(i));
|
|
625
|
+
}
|
|
626
|
+
for (let i = Math.max(node.captureLayer, node.nativeLayer); i <= ATTR_TRAVEL.MAX_LAYERS + 1; i++) {
|
|
627
|
+
nativeMesh.layers.enable(THREE_LAYERS.getCaptureLayer(i));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
if (node.visibility & USER_LAYER) {
|
|
632
|
+
for (let i = node.captureLayer; i <= ATTR_TRAVEL.MAX_LAYERS + 1; i++) {
|
|
633
|
+
mesh.layers.enable(THREE_LAYERS.getCaptureLayer(i));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
444
637
|
}
|
|
445
638
|
|
|
446
|
-
private captureRenderTarget(
|
|
639
|
+
private captureRenderTarget(
|
|
640
|
+
travelers: Set<THREE.Mesh>,
|
|
641
|
+
targetLayer: number,
|
|
642
|
+
renderTarget: THREE.WebGLRenderTarget | null,
|
|
643
|
+
) {
|
|
447
644
|
// [Problem] this method called on requestAnimationFrame
|
|
448
645
|
// => this logic travers all meshes every frame, need optimization
|
|
449
646
|
|
|
450
647
|
// if (travelers.length === 0) return;
|
|
451
|
-
if (
|
|
648
|
+
if (travelers.size === 0 || !renderTarget) return;
|
|
452
649
|
|
|
453
650
|
const oldClearColor = new THREE.Color();
|
|
454
651
|
const oldClearAlpha = this.renderer.getClearAlpha();
|
|
455
652
|
this.renderer.getClearColor(oldClearColor);
|
|
456
653
|
|
|
457
654
|
this.renderer.setClearColor(0x000000, 0);
|
|
458
|
-
this.renderer.setRenderTarget(
|
|
655
|
+
this.renderer.setRenderTarget(renderTarget);
|
|
459
656
|
|
|
460
657
|
this.renderer.clear();
|
|
461
658
|
|
|
462
659
|
this.renderer.autoClear = false;
|
|
463
660
|
this.renderer.setScissorTest(true);
|
|
464
|
-
this.camera.layers.set(
|
|
661
|
+
this.camera.layers.set(targetLayer);
|
|
465
662
|
|
|
466
663
|
const vector = new THREE.Vector3();
|
|
467
664
|
const canvasWidth = this.targetRect.width;
|
|
@@ -469,7 +666,7 @@ export class Renderer {
|
|
|
469
666
|
|
|
470
667
|
const pixelRatio = this.renderer.getPixelRatio();
|
|
471
668
|
|
|
472
|
-
for (const traveler of
|
|
669
|
+
for (const traveler of travelers) {
|
|
473
670
|
vector.setFromMatrixPosition(traveler.matrixWorld);
|
|
474
671
|
vector.project(this.camera);
|
|
475
672
|
|
|
@@ -504,35 +701,23 @@ export class Renderer {
|
|
|
504
701
|
this.renderer.setScissorTest(false);
|
|
505
702
|
this.renderer.autoClear = true;
|
|
506
703
|
this.renderer.setRenderTarget(null);
|
|
507
|
-
this.camera.layers.set(
|
|
704
|
+
this.camera.layers.set(this.getSceneLayer());
|
|
508
705
|
this.renderer.setClearColor(oldClearColor, oldClearAlpha);
|
|
509
706
|
}
|
|
510
707
|
|
|
511
708
|
public render() {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// for debugging
|
|
517
|
-
public showScissoredRenderTarget() {
|
|
518
|
-
if (!this.renderTarget) return;
|
|
519
|
-
|
|
520
|
-
const w = this.targetRect.width;
|
|
521
|
-
const h = this.targetRect.height;
|
|
522
|
-
|
|
523
|
-
const geometry = new THREE.PlaneGeometry(w, h);
|
|
524
|
-
const material = new THREE.MeshBasicMaterial({
|
|
525
|
-
map: this.renderTarget.texture,
|
|
526
|
-
side: THREE.DoubleSide,
|
|
527
|
-
transparent: true,
|
|
528
|
-
opacity: 0.8,
|
|
529
|
-
});
|
|
709
|
+
for (let i = 0; i < ATTR_TRAVEL.MAX_LAYERS; i++) {
|
|
710
|
+
const currentLayer = i + 1;
|
|
530
711
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
712
|
+
this.captureRenderTarget(
|
|
713
|
+
this.travelersByLayer[i],
|
|
714
|
+
THREE_LAYERS.getCaptureLayer(currentLayer),
|
|
715
|
+
this.renderTargets[i],
|
|
716
|
+
);
|
|
717
|
+
}
|
|
535
718
|
|
|
536
|
-
this.scene.
|
|
719
|
+
this.renderer.render(this.scene, this.camera);
|
|
537
720
|
}
|
|
721
|
+
|
|
722
|
+
|
|
538
723
|
}
|
|
@@ -58,22 +58,37 @@ export class TextureLifecycleManager {
|
|
|
58
58
|
this.loadStatus.set(element, true);
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
let textureImage: ImageBitmap | HTMLImageElement;
|
|
62
|
+
|
|
63
|
+
if (url.startsWith("data:image/svg+xml")) {
|
|
64
|
+
textureImage = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
65
|
+
const img = new Image();
|
|
66
|
+
img.onload = () => resolve(img);
|
|
67
|
+
img.onerror = reject;
|
|
68
|
+
img.src = url;
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
const response = await fetch(url);
|
|
72
|
+
const blob = await response.blob();
|
|
73
|
+
textureImage = await createImageBitmap(blob, { imageOrientation: "flipY" });
|
|
74
|
+
}
|
|
64
75
|
|
|
65
76
|
// Check if URL changed or element was unregistered while decoding
|
|
66
77
|
if (this.elementUrls.get(element) !== url) {
|
|
67
|
-
|
|
78
|
+
if ('close' in textureImage) textureImage.close();
|
|
68
79
|
return;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
const texture = new THREE.Texture(textureImage);
|
|
83
|
+
// createImageBitmap already flips Y, but HTMLImageElement relies on THREE.js flipY
|
|
84
|
+
if (!(textureImage instanceof HTMLImageElement)) {
|
|
85
|
+
texture.flipY = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Inherit CanvasTexture defaults or set them explicitly if needed
|
|
89
|
+
texture.colorSpace = THREE.LinearSRGBColorSpace; // or adjust according to your pipeline
|
|
76
90
|
texture.needsUpdate = true;
|
|
91
|
+
|
|
77
92
|
this.textures.set(element, texture);
|
|
78
93
|
this.onUpdate(element, texture);
|
|
79
94
|
} catch (e) {
|