@mirage-engine/core 0.2.1 → 0.3.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/CHANGELOG.md +20 -0
- package/dist/mirage-engine.js +1443 -503
- package/dist/mirage-engine.umd.js +142 -72
- package/dist/src/animation/Animator.d.ts +6 -0
- package/dist/src/core/Engine.d.ts +5 -0
- package/dist/src/core/Syncer.d.ts +5 -16
- package/dist/src/dom/Extractor.d.ts +1 -2
- package/dist/src/renderer/Renderer.d.ts +14 -8
- package/dist/src/store/MeshRegistry.d.ts +9 -0
- package/dist/src/store/TextureLifecycleManager.d.ts +16 -0
- package/dist/src/types/animate.d.ts +19 -0
- package/dist/src/types/attributes.d.ts +63 -0
- package/dist/src/types/common.d.ts +9 -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 -0
- package/package.json +3 -2
- package/src/animation/Animator.ts +108 -0
- package/src/core/Engine.ts +69 -5
- package/src/core/Syncer.ts +49 -190
- package/src/dom/Extractor.ts +498 -64
- package/src/env.d.ts +4 -0
- package/src/renderer/Renderer.ts +430 -133
- package/src/store/MeshRegistry.ts +32 -0
- package/src/store/TextureLifecycleManager.ts +126 -0
- package/src/types/animate.ts +26 -0
- package/src/types/attributes.ts +57 -0
- package/src/types/common.ts +6 -0
- package/src/types/config.ts +5 -10
- package/src/types/flags.ts +20 -15
- package/src/types/index.ts +3 -1
package/src/renderer/Renderer.ts
CHANGED
|
@@ -6,38 +6,62 @@ 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";
|
|
16
|
+
import { MeshRegistry } from "../store/MeshRegistry";
|
|
17
|
+
import { TextureLifecycleManager } from "../store/TextureLifecycleManager";
|
|
13
18
|
|
|
14
19
|
export class Renderer {
|
|
15
20
|
public readonly canvas: HTMLCanvasElement;
|
|
16
21
|
private readonly scene: THREE.Scene;
|
|
17
22
|
private readonly camera: THREE.OrthographicCamera;
|
|
18
23
|
private readonly renderer: THREE.WebGLRenderer;
|
|
19
|
-
private
|
|
24
|
+
private renderTargets: THREE.WebGLRenderTarget[] = [];
|
|
20
25
|
private renderOrder: number = 0;
|
|
21
|
-
|
|
26
|
+
public qualityFactor: number = 2;
|
|
22
27
|
private mode: MirageMode = "overlay";
|
|
23
28
|
private clipArea: travelerClipArea = 1;
|
|
29
|
+
public targetLayer: number | LayerTarget = "base";
|
|
24
30
|
|
|
25
31
|
private target: HTMLElement;
|
|
26
32
|
private mountContainer: HTMLElement;
|
|
33
|
+
private registry: MeshRegistry;
|
|
27
34
|
private targetRect: DOMRect;
|
|
28
35
|
|
|
29
|
-
private
|
|
36
|
+
private travelersByLayer: Set<THREE.Mesh>[] = Array.from(
|
|
37
|
+
{ length: ATTR_TRAVEL.MAX_LAYERS },
|
|
38
|
+
() => new Set(),
|
|
39
|
+
);
|
|
40
|
+
private textureManager: TextureLifecycleManager;
|
|
41
|
+
// private meshMap: Map<HTMLElement, THREE.Mesh> = new Map();
|
|
42
|
+
|
|
43
|
+
public readonly fixedMeshes: Set<THREE.Mesh> = new Set();
|
|
30
44
|
|
|
31
45
|
constructor(
|
|
32
46
|
target: HTMLElement,
|
|
33
47
|
config: CoreConfig,
|
|
34
48
|
mountContainer: HTMLElement,
|
|
49
|
+
registry: MeshRegistry,
|
|
35
50
|
) {
|
|
36
51
|
this.target = target;
|
|
37
52
|
this.mountContainer = mountContainer;
|
|
53
|
+
this.registry = registry;
|
|
54
|
+
|
|
55
|
+
this.textureManager = new TextureLifecycleManager((el, texture) => {
|
|
56
|
+
const mesh = this.registry.get(el);
|
|
57
|
+
if (mesh && mesh.material instanceof THREE.ShaderMaterial) {
|
|
58
|
+
Painter.forceUpdateUniforms(mesh.material, { texture: texture });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
38
61
|
|
|
39
62
|
this.mode = config.mode ?? "overlay";
|
|
40
63
|
this.clipArea = config.travelerClipArea ?? 1;
|
|
64
|
+
this.targetLayer = config.layer ?? "base";
|
|
41
65
|
this.canvas = document.createElement("canvas");
|
|
42
66
|
this.scene = new THREE.Scene();
|
|
43
67
|
|
|
@@ -58,10 +82,7 @@ export class Renderer {
|
|
|
58
82
|
1000,
|
|
59
83
|
);
|
|
60
84
|
this.camera.position.z = 100;
|
|
61
|
-
this.camera.layers.set(
|
|
62
|
-
|
|
63
|
-
// [new]
|
|
64
|
-
// THREE.ColorManagement.enabled = false;
|
|
85
|
+
this.camera.layers.set(this.getSceneLayer());
|
|
65
86
|
|
|
66
87
|
this.renderer = new THREE.WebGLRenderer({
|
|
67
88
|
canvas: this.canvas,
|
|
@@ -71,24 +92,41 @@ export class Renderer {
|
|
|
71
92
|
// premultipliedAlpha: true
|
|
72
93
|
});
|
|
73
94
|
|
|
95
|
+
THREE.ColorManagement.enabled = false;
|
|
96
|
+
this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
|
|
97
|
+
|
|
74
98
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
75
99
|
this.renderer.setSize(width, height);
|
|
76
100
|
|
|
77
101
|
this.applyTextQuality(config.quality ?? "medium");
|
|
78
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
|
+
|
|
79
114
|
public createRenderTarget() {
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
}
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
private applyTextQuality(quality: Quality) {
|
|
@@ -104,6 +142,9 @@ export class Renderer {
|
|
|
104
142
|
this.qualityFactor = 4;
|
|
105
143
|
break;
|
|
106
144
|
case "medium":
|
|
145
|
+
// this.qualityFactor = 2;
|
|
146
|
+
this.qualityFactor = 2;
|
|
147
|
+
break;
|
|
107
148
|
default:
|
|
108
149
|
this.qualityFactor = 2;
|
|
109
150
|
break;
|
|
@@ -135,19 +176,42 @@ export class Renderer {
|
|
|
135
176
|
}
|
|
136
177
|
}
|
|
137
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
|
+
|
|
138
204
|
public dispose() {
|
|
139
205
|
this.renderer.dispose();
|
|
140
206
|
this.canvas.remove();
|
|
207
|
+
this.textureManager.disposeAll();
|
|
141
208
|
// TODO: Scene 내부 Mesh들도 순회하며 dispose
|
|
142
209
|
}
|
|
143
210
|
|
|
144
211
|
public setSize(width: number, height: number) {
|
|
145
212
|
this.renderer.setSize(width, height);
|
|
146
|
-
|
|
147
|
-
this.
|
|
148
|
-
width * this.qualityFactor,
|
|
149
|
-
height * this.qualityFactor,
|
|
150
|
-
);
|
|
213
|
+
for (const target of this.renderTargets) {
|
|
214
|
+
target.setSize(width * this.qualityFactor, height * this.qualityFactor);
|
|
151
215
|
}
|
|
152
216
|
this.camera.left = width / -2;
|
|
153
217
|
this.camera.right = width / 2;
|
|
@@ -156,7 +220,7 @@ export class Renderer {
|
|
|
156
220
|
this.camera.updateProjectionMatrix();
|
|
157
221
|
}
|
|
158
222
|
|
|
159
|
-
public syncScene(graphNode: SceneNode) {
|
|
223
|
+
public syncScene(graphNode: SceneNode, pendingDeletions: Set<HTMLElement>) {
|
|
160
224
|
const newRect = this.target.getBoundingClientRect();
|
|
161
225
|
|
|
162
226
|
const isResized =
|
|
@@ -182,110 +246,230 @@ export class Renderer {
|
|
|
182
246
|
|
|
183
247
|
this.renderOrder = 0;
|
|
184
248
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
249
|
+
this.reconcileNode(graphNode);
|
|
250
|
+
|
|
251
|
+
if (pendingDeletions.size > 0) {
|
|
252
|
+
pendingDeletions.forEach((el) => {
|
|
253
|
+
const meshToDestroy = this.registry.get(el) as THREE.Mesh | undefined;
|
|
254
|
+
if (meshToDestroy) {
|
|
255
|
+
this.scene.remove(meshToDestroy);
|
|
256
|
+
for (const set of this.travelersByLayer) {
|
|
257
|
+
set.delete(meshToDestroy);
|
|
258
|
+
}
|
|
259
|
+
this.fixedMeshes.delete(meshToDestroy);
|
|
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
|
+
}
|
|
270
|
+
meshToDestroy.traverse((child) => {
|
|
271
|
+
if (child instanceof THREE.Mesh) {
|
|
272
|
+
if (child.geometry) child.geometry.dispose();
|
|
273
|
+
if (child.material) {
|
|
274
|
+
if (Array.isArray(child.material)) {
|
|
275
|
+
child.material.forEach((mat) => mat.dispose());
|
|
276
|
+
} else {
|
|
277
|
+
child.material.dispose();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
this.registry.remove(el);
|
|
283
|
+
this.textureManager.unregister(el);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
197
286
|
}
|
|
287
|
+
|
|
288
|
+
// mesh.geometry.dispose();
|
|
289
|
+
// if (mesh.material instanceof THREE.Material) mesh.material.dispose();
|
|
290
|
+
// this.meshMap.delete(el);
|
|
291
|
+
// }
|
|
292
|
+
// }
|
|
198
293
|
}
|
|
199
294
|
|
|
200
|
-
|
|
201
|
-
|
|
295
|
+
// 탐색 후 완성된 Scene node를 이용하여 mesh를 만들거나 조정
|
|
296
|
+
// => 이 과정에서 scene node에 있는 ele를 넣은 hash => activeElements
|
|
297
|
+
// => 이후 activeElements를 이용하여 mesh를 정리!!!+ map에서도 삭제
|
|
202
298
|
|
|
203
|
-
|
|
299
|
+
// private reconcileNode(node: SceneNode, activeElements: Set<HTMLElement>) {
|
|
300
|
+
private reconcileNode(node: SceneNode) {
|
|
301
|
+
let mesh = this.registry.get(node.element) as THREE.Mesh | undefined;
|
|
302
|
+
|
|
204
303
|
if (!mesh) {
|
|
205
304
|
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
206
|
-
|
|
207
|
-
|
|
305
|
+
const initialTexture = node.isTraveler
|
|
306
|
+
? this.renderTargets[node.captureLayer - 2]?.texture
|
|
307
|
+
: this.textureManager.get(node.element);
|
|
308
|
+
|
|
309
|
+
const material = Painter.create(
|
|
208
310
|
"BOX",
|
|
209
311
|
node.styles,
|
|
210
312
|
"",
|
|
211
313
|
node.rect.width,
|
|
212
314
|
node.rect.height,
|
|
213
315
|
this.qualityFactor,
|
|
214
|
-
|
|
215
|
-
node.shaderHooks
|
|
316
|
+
initialTexture,
|
|
317
|
+
node.shaderHooks,
|
|
216
318
|
);
|
|
319
|
+
|
|
217
320
|
mesh = new THREE.Mesh(geometry, material);
|
|
321
|
+
|
|
218
322
|
if (node.type === "TEXT") mesh.name = "BG_MESH";
|
|
219
323
|
this.scene.add(mesh);
|
|
220
|
-
|
|
324
|
+
|
|
325
|
+
this.registry.register(node.element, mesh);
|
|
326
|
+
mesh.userData.baseMaterial = material;
|
|
221
327
|
}
|
|
222
328
|
|
|
223
|
-
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
// [Important] use whene mesh animating with js
|
|
224
332
|
|
|
225
333
|
this.updateMeshProperties(mesh, node);
|
|
226
334
|
this.updateMeshLayers(mesh, node);
|
|
227
|
-
if (node.isTraveler)
|
|
335
|
+
if (node.isTraveler) {
|
|
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
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
for (const set of this.travelersByLayer) {
|
|
345
|
+
set.delete(mesh);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (node.isFixed) {
|
|
350
|
+
this.fixedMeshes.add(mesh);
|
|
351
|
+
} else {
|
|
352
|
+
this.fixedMeshes.delete(mesh);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (node.styles.imageSrc) {
|
|
356
|
+
this.textureManager.register(node.element, node.styles.imageSrc);
|
|
357
|
+
} else {
|
|
358
|
+
this.textureManager.unregister(node.element);
|
|
359
|
+
}
|
|
228
360
|
|
|
229
361
|
if (node.type === "BOX") {
|
|
230
362
|
for (const child of node.children) {
|
|
231
|
-
this.reconcileNode(child
|
|
363
|
+
this.reconcileNode(child);
|
|
232
364
|
}
|
|
233
365
|
} else if (node.type === "TEXT") {
|
|
234
|
-
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
|
+
}
|
|
235
370
|
}
|
|
236
371
|
}
|
|
237
372
|
|
|
238
|
-
private reconcileTextChild(parentMesh: THREE.Mesh, node: SceneNode) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
const
|
|
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("|");
|
|
383
|
+
const cachedStyleHash = parentMesh.userData?.textChildStyleHash;
|
|
245
384
|
const isDirty =
|
|
246
|
-
|
|
247
|
-
node.dirtyMask & DIRTY_CONTENT ||
|
|
248
|
-
currentStyleHash !== cachedStyleHash;
|
|
385
|
+
node.dirtyMask & DIRTY_CONTENT || currentStyleHash !== cachedStyleHash;
|
|
249
386
|
|
|
250
387
|
if (isDirty) {
|
|
251
|
-
|
|
388
|
+
// Remove all existing TEXT_CHILD meshes
|
|
389
|
+
const existingChildren = parentMesh.children.filter((c) =>
|
|
390
|
+
c.name.startsWith("TEXT_CHILD"),
|
|
391
|
+
);
|
|
392
|
+
existingChildren.forEach((child) => {
|
|
393
|
+
const textMesh = child as THREE.Mesh;
|
|
252
394
|
(textMesh.material as THREE.MeshBasicMaterial).map?.dispose();
|
|
253
395
|
textMesh.geometry.dispose();
|
|
254
396
|
parentMesh.remove(textMesh);
|
|
255
|
-
}
|
|
397
|
+
});
|
|
256
398
|
|
|
257
|
-
const
|
|
258
|
-
"TEXT",
|
|
259
|
-
node.textStyles!,
|
|
260
|
-
node.textContent || "",
|
|
261
|
-
node.rect.width,
|
|
262
|
-
node.rect.height,
|
|
263
|
-
this.qualityFactor,
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
267
|
-
textMesh = new THREE.Mesh(geometry, material);
|
|
268
|
-
|
|
269
|
-
textMesh.name = "TEXT_CHILD";
|
|
270
|
-
textMesh.userData = { styleHash: currentStyleHash };
|
|
271
|
-
this.updateMeshLayers(textMesh, node);
|
|
272
|
-
textMesh.position.z = 0.005;
|
|
273
|
-
parentMesh.add(textMesh);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (textMesh) {
|
|
277
|
-
const parentRect = parentMesh.userData.domRect;
|
|
399
|
+
const parentRect = node.rect;
|
|
278
400
|
const parentCenterX = parentRect.x + parentRect.width / 2;
|
|
279
401
|
const parentCenterY = parentRect.y + parentRect.height / 2;
|
|
280
402
|
|
|
281
|
-
|
|
282
|
-
|
|
403
|
+
lines.forEach((line, index) => {
|
|
404
|
+
const material = Painter.create(
|
|
405
|
+
"TEXT",
|
|
406
|
+
stylesToUse,
|
|
407
|
+
line.text,
|
|
408
|
+
line.rect.width,
|
|
409
|
+
line.rect.height,
|
|
410
|
+
this.qualityFactor,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
414
|
+
const textMesh = new THREE.Mesh(geometry, material);
|
|
415
|
+
|
|
416
|
+
textMesh.name = `TEXT_CHILD_${index}`;
|
|
417
|
+
|
|
418
|
+
// Parent is already scaled to node.rect.width/height.
|
|
419
|
+
// We counter-scale the child so its absolute size is exactly line.rect.width/height.
|
|
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;
|
|
424
|
+
textMesh.scale.set(scaleX, scaleY, 1);
|
|
425
|
+
|
|
426
|
+
const textCenterX = line.rect.x + line.rect.width / 2;
|
|
427
|
+
const textCenterY = line.rect.y + line.rect.height / 2;
|
|
428
|
+
|
|
429
|
+
const offsetX = textCenterX - parentCenterX;
|
|
430
|
+
const offsetY = -(textCenterY - parentCenterY);
|
|
431
|
+
|
|
432
|
+
// Position must also be counter-scaled relative to parent's scale
|
|
433
|
+
textMesh.position.set(
|
|
434
|
+
node.rect.width === 0 ? 0 : offsetX / node.rect.width,
|
|
435
|
+
node.rect.height === 0 ? 0 : offsetY / node.rect.height,
|
|
436
|
+
0.005,
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
parentMesh.add(textMesh);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
parentMesh.userData.textChildStyleHash = currentStyleHash;
|
|
443
|
+
}
|
|
283
444
|
|
|
284
|
-
|
|
285
|
-
|
|
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);
|
|
286
452
|
|
|
287
|
-
|
|
288
|
-
|
|
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
|
+
});
|
|
289
473
|
}
|
|
290
474
|
|
|
291
475
|
private updateMeshProperties(mesh: THREE.Mesh, node: SceneNode) {
|
|
@@ -294,9 +478,16 @@ export class Renderer {
|
|
|
294
478
|
const pixelRatio = this.renderer.getPixelRatio();
|
|
295
479
|
const canvasWidth = this.renderer.domElement.width / pixelRatio;
|
|
296
480
|
const canvasHeight = this.renderer.domElement.height / pixelRatio;
|
|
297
|
-
|
|
481
|
+
|
|
482
|
+
mesh.material = mesh.userData.baseMaterial as THREE.Material;
|
|
298
483
|
mesh.scale.set(rect.width, rect.height, 1);
|
|
299
484
|
|
|
485
|
+
mesh.userData.domRect = {
|
|
486
|
+
...rect,
|
|
487
|
+
width: rect.width,
|
|
488
|
+
height: rect.height,
|
|
489
|
+
};
|
|
490
|
+
|
|
300
491
|
const Z_MICRO_OFFSET = 0.001;
|
|
301
492
|
this.renderOrder++;
|
|
302
493
|
|
|
@@ -306,50 +497,168 @@ export class Renderer {
|
|
|
306
497
|
const localX = rect.x - targetPageX;
|
|
307
498
|
const localY = rect.y - targetPageY;
|
|
308
499
|
|
|
500
|
+
const baseX = localX - canvasWidth / 2 + rect.width / 2;
|
|
501
|
+
const baseY = -localY + canvasHeight / 2 - rect.height / 2;
|
|
502
|
+
|
|
309
503
|
mesh.position.set(
|
|
310
|
-
|
|
311
|
-
|
|
504
|
+
baseX,
|
|
505
|
+
baseY,
|
|
312
506
|
styles.zIndex + this.renderOrder * Z_MICRO_OFFSET,
|
|
313
507
|
);
|
|
508
|
+
const pureDOM_X = rect.x; // 트랜스폼 오염 전 순수 좌표 (가정)
|
|
509
|
+
const pureDOM_Y = rect.y;
|
|
510
|
+
|
|
511
|
+
mesh.userData.basePosition = { x: baseX, y: baseY };
|
|
512
|
+
mesh.userData.originalBasePosition = { x: baseX, y: baseY };
|
|
513
|
+
mesh.userData.baseSize = { width: rect.width, height: rect.height };
|
|
514
|
+
|
|
515
|
+
mesh.userData.baseDOM = { x: pureDOM_X, y: pureDOM_Y };
|
|
516
|
+
mesh.userData.isFixed = node.isFixed;
|
|
517
|
+
mesh.userData.initialScroll = { x: window.scrollX, y: window.scrollY };
|
|
518
|
+
|
|
519
|
+
const targetEl =
|
|
520
|
+
node.element.nodeType === Node.TEXT_NODE
|
|
521
|
+
? node.element.parentElement!
|
|
522
|
+
: node.element;
|
|
523
|
+
const computed = window.getComputedStyle(targetEl);
|
|
524
|
+
let baseTx = 0,
|
|
525
|
+
baseTy = 0;
|
|
526
|
+
if (computed.transform && computed.transform !== "none") {
|
|
527
|
+
const matrix = new DOMMatrix(computed.transform);
|
|
528
|
+
baseTx = matrix.m41;
|
|
529
|
+
baseTy = matrix.m42;
|
|
530
|
+
}
|
|
531
|
+
mesh.userData.baseTransform = { x: baseTx, y: baseTy };
|
|
532
|
+
|
|
533
|
+
delete mesh.userData.originRatioX;
|
|
534
|
+
delete mesh.userData.originRatioY;
|
|
535
|
+
// mesh.position.set(
|
|
536
|
+
// localX - canvasWidth / 2 + rect.width / 2,
|
|
537
|
+
// -localY + canvasHeight / 2 - rect.height / 2,
|
|
538
|
+
// styles.zIndex + this.renderOrder * Z_MICRO_OFFSET,
|
|
539
|
+
// );
|
|
314
540
|
Painter.update(
|
|
315
|
-
mesh.
|
|
541
|
+
mesh.userData.baseMaterial,
|
|
316
542
|
"BOX",
|
|
317
|
-
|
|
543
|
+
styles,
|
|
318
544
|
"",
|
|
319
|
-
|
|
320
|
-
|
|
545
|
+
rect.width,
|
|
546
|
+
rect.height,
|
|
321
547
|
this.qualityFactor,
|
|
322
|
-
node.isTraveler
|
|
548
|
+
node.isTraveler
|
|
549
|
+
? this.renderTargets[node.captureLayer - 2]?.texture
|
|
550
|
+
: this.textureManager.get(node.element),
|
|
323
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
|
+
}
|
|
324
607
|
}
|
|
325
608
|
|
|
326
609
|
private updateMeshLayers(mesh: THREE.Mesh, node: SceneNode) {
|
|
327
|
-
const layerNum =
|
|
610
|
+
const layerNum =
|
|
611
|
+
node.visibility & USER_LAYER ? THREE_LAYERS.BASE : THREE_LAYERS.HIDDEN;
|
|
328
612
|
mesh.layers.set(layerNum);
|
|
329
|
-
if (node.visibility === (USER_LAYER | SYSTEM_LAYER)) mesh.layers.enable(29);
|
|
330
|
-
}
|
|
331
613
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
+
}
|
|
337
635
|
}
|
|
338
636
|
}
|
|
339
|
-
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private captureRenderTarget(
|
|
640
|
+
travelers: Set<THREE.Mesh>,
|
|
641
|
+
targetLayer: number,
|
|
642
|
+
renderTarget: THREE.WebGLRenderTarget | null,
|
|
643
|
+
) {
|
|
644
|
+
// [Problem] this method called on requestAnimationFrame
|
|
645
|
+
// => this logic travers all meshes every frame, need optimization
|
|
646
|
+
|
|
647
|
+
// if (travelers.length === 0) return;
|
|
648
|
+
if (travelers.size === 0 || !renderTarget) return;
|
|
340
649
|
|
|
341
650
|
const oldClearColor = new THREE.Color();
|
|
342
651
|
const oldClearAlpha = this.renderer.getClearAlpha();
|
|
343
652
|
this.renderer.getClearColor(oldClearColor);
|
|
344
653
|
|
|
345
654
|
this.renderer.setClearColor(0x000000, 0);
|
|
346
|
-
this.renderer.setRenderTarget(
|
|
655
|
+
this.renderer.setRenderTarget(renderTarget);
|
|
347
656
|
|
|
348
657
|
this.renderer.clear();
|
|
349
658
|
|
|
350
659
|
this.renderer.autoClear = false;
|
|
351
660
|
this.renderer.setScissorTest(true);
|
|
352
|
-
this.camera.layers.set(
|
|
661
|
+
this.camera.layers.set(targetLayer);
|
|
353
662
|
|
|
354
663
|
const vector = new THREE.Vector3();
|
|
355
664
|
const canvasWidth = this.targetRect.width;
|
|
@@ -392,35 +701,23 @@ export class Renderer {
|
|
|
392
701
|
this.renderer.setScissorTest(false);
|
|
393
702
|
this.renderer.autoClear = true;
|
|
394
703
|
this.renderer.setRenderTarget(null);
|
|
395
|
-
this.camera.layers.set(
|
|
704
|
+
this.camera.layers.set(this.getSceneLayer());
|
|
396
705
|
this.renderer.setClearColor(oldClearColor, oldClearAlpha);
|
|
397
706
|
}
|
|
398
707
|
|
|
399
708
|
public render() {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// for debugging
|
|
405
|
-
public showScissoredRenderTarget() {
|
|
406
|
-
if (!this.renderTarget) return;
|
|
407
|
-
|
|
408
|
-
const w = this.targetRect.width;
|
|
409
|
-
const h = this.targetRect.height;
|
|
410
|
-
|
|
411
|
-
const geometry = new THREE.PlaneGeometry(w, h);
|
|
412
|
-
const material = new THREE.MeshBasicMaterial({
|
|
413
|
-
map: this.renderTarget.texture,
|
|
414
|
-
side: THREE.DoubleSide,
|
|
415
|
-
transparent: true,
|
|
416
|
-
opacity: 0.8,
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
const debugMesh = new THREE.Mesh(geometry, material);
|
|
709
|
+
for (let i = 0; i < ATTR_TRAVEL.MAX_LAYERS; i++) {
|
|
710
|
+
const currentLayer = i + 1;
|
|
420
711
|
|
|
421
|
-
|
|
422
|
-
|
|
712
|
+
this.captureRenderTarget(
|
|
713
|
+
this.travelersByLayer[i],
|
|
714
|
+
THREE_LAYERS.getCaptureLayer(currentLayer),
|
|
715
|
+
this.renderTargets[i],
|
|
716
|
+
);
|
|
717
|
+
}
|
|
423
718
|
|
|
424
|
-
this.scene.
|
|
719
|
+
this.renderer.render(this.scene, this.camera);
|
|
425
720
|
}
|
|
721
|
+
|
|
722
|
+
|
|
426
723
|
}
|