@mirage-engine/core 0.1.0 → 0.2.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 +19 -0
- package/dist/mirage-engine.js +434 -328
- package/dist/mirage-engine.umd.js +36 -33
- package/dist/src/core/Syncer.d.ts +7 -3
- package/dist/src/renderer/Renderer.d.ts +7 -1
- package/dist/src/types/common.d.ts +3 -1
- package/dist/src/types/config.d.ts +12 -2
- package/dist/src/types/flags.d.ts +4 -4
- package/package.json +2 -2
- package/src/core/Engine.ts +1 -1
- package/src/core/Syncer.ts +71 -8
- package/src/dom/Extractor.ts +37 -12
- package/src/renderer/Renderer.ts +144 -24
- package/src/types/common.ts +6 -5
- package/src/types/config.ts +13 -3
- package/src/types/flags.ts +4 -5
package/src/renderer/Renderer.ts
CHANGED
|
@@ -2,9 +2,12 @@ import * as THREE from "three";
|
|
|
2
2
|
import {
|
|
3
3
|
SceneNode,
|
|
4
4
|
DIRTY_CONTENT,
|
|
5
|
-
|
|
5
|
+
Quality,
|
|
6
6
|
CoreConfig,
|
|
7
7
|
MirageMode,
|
|
8
|
+
USER_LAYER,
|
|
9
|
+
SYSTEM_LAYER,
|
|
10
|
+
travelerClipArea,
|
|
8
11
|
} from "../types";
|
|
9
12
|
import { Painter } from "@mirage-engine/painter";
|
|
10
13
|
|
|
@@ -13,9 +16,11 @@ export class Renderer {
|
|
|
13
16
|
private readonly scene: THREE.Scene;
|
|
14
17
|
private readonly camera: THREE.OrthographicCamera;
|
|
15
18
|
private readonly renderer: THREE.WebGLRenderer;
|
|
19
|
+
private renderTarget: THREE.WebGLRenderTarget | null = null;
|
|
16
20
|
private renderOrder: number = 0;
|
|
17
|
-
private
|
|
21
|
+
private qualityFactor: number = 2;
|
|
18
22
|
private mode: MirageMode = "overlay";
|
|
23
|
+
private clipArea: travelerClipArea = 1;
|
|
19
24
|
|
|
20
25
|
private target: HTMLElement;
|
|
21
26
|
private mountContainer: HTMLElement;
|
|
@@ -32,7 +37,7 @@ export class Renderer {
|
|
|
32
37
|
this.mountContainer = mountContainer;
|
|
33
38
|
|
|
34
39
|
this.mode = config.mode ?? "overlay";
|
|
35
|
-
|
|
40
|
+
this.clipArea = config.travelerClipArea ?? 1;
|
|
36
41
|
this.canvas = document.createElement("canvas");
|
|
37
42
|
this.scene = new THREE.Scene();
|
|
38
43
|
|
|
@@ -53,6 +58,7 @@ export class Renderer {
|
|
|
53
58
|
1000,
|
|
54
59
|
);
|
|
55
60
|
this.camera.position.z = 100;
|
|
61
|
+
this.camera.layers.set(0);
|
|
56
62
|
|
|
57
63
|
// [new]
|
|
58
64
|
// THREE.ColorManagement.enabled = false;
|
|
@@ -68,24 +74,38 @@ export class Renderer {
|
|
|
68
74
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
69
75
|
this.renderer.setSize(width, height);
|
|
70
76
|
|
|
71
|
-
this.applyTextQuality(config.
|
|
77
|
+
this.applyTextQuality(config.quality ?? "medium");
|
|
78
|
+
}
|
|
79
|
+
public createRenderTarget() {
|
|
80
|
+
this.renderTarget = new THREE.WebGLRenderTarget(
|
|
81
|
+
this.targetRect.width * this.qualityFactor,
|
|
82
|
+
this.targetRect.height * this.qualityFactor,
|
|
83
|
+
{
|
|
84
|
+
minFilter: THREE.LinearFilter,
|
|
85
|
+
magFilter: THREE.LinearFilter,
|
|
86
|
+
format: THREE.RGBAFormat,
|
|
87
|
+
stencilBuffer: false,
|
|
88
|
+
depthBuffer: true,
|
|
89
|
+
samples: 4,
|
|
90
|
+
},
|
|
91
|
+
);
|
|
72
92
|
}
|
|
73
93
|
|
|
74
|
-
private applyTextQuality(quality:
|
|
94
|
+
private applyTextQuality(quality: Quality) {
|
|
75
95
|
if (typeof quality === "number") {
|
|
76
|
-
this.
|
|
96
|
+
this.qualityFactor = Math.max(0.1, quality);
|
|
77
97
|
return;
|
|
78
98
|
}
|
|
79
99
|
switch (quality) {
|
|
80
100
|
case "low":
|
|
81
|
-
this.
|
|
101
|
+
this.qualityFactor = 1;
|
|
82
102
|
break;
|
|
83
103
|
case "high":
|
|
84
|
-
this.
|
|
104
|
+
this.qualityFactor = 4;
|
|
85
105
|
break;
|
|
86
106
|
case "medium":
|
|
87
107
|
default:
|
|
88
|
-
this.
|
|
108
|
+
this.qualityFactor = 2;
|
|
89
109
|
break;
|
|
90
110
|
}
|
|
91
111
|
}
|
|
@@ -123,12 +143,16 @@ export class Renderer {
|
|
|
123
143
|
|
|
124
144
|
public setSize(width: number, height: number) {
|
|
125
145
|
this.renderer.setSize(width, height);
|
|
126
|
-
|
|
146
|
+
if (this.renderTarget) {
|
|
147
|
+
this.renderTarget.setSize(
|
|
148
|
+
width * this.qualityFactor,
|
|
149
|
+
height * this.qualityFactor,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
127
152
|
this.camera.left = width / -2;
|
|
128
153
|
this.camera.right = width / 2;
|
|
129
154
|
this.camera.top = height / 2;
|
|
130
155
|
this.camera.bottom = height / -2;
|
|
131
|
-
|
|
132
156
|
this.camera.updateProjectionMatrix();
|
|
133
157
|
}
|
|
134
158
|
|
|
@@ -146,14 +170,7 @@ export class Renderer {
|
|
|
146
170
|
|
|
147
171
|
if (isResized) {
|
|
148
172
|
this.targetRect = newRect;
|
|
149
|
-
this.
|
|
150
|
-
|
|
151
|
-
this.camera.left = this.targetRect.width / -2;
|
|
152
|
-
this.camera.right = this.targetRect.width / 2;
|
|
153
|
-
this.camera.top = this.targetRect.height / 2;
|
|
154
|
-
this.camera.bottom = this.targetRect.height / -2;
|
|
155
|
-
this.camera.layers.set(0);
|
|
156
|
-
this.camera.updateProjectionMatrix();
|
|
173
|
+
this.setSize(this.targetRect.width, this.targetRect.height);
|
|
157
174
|
|
|
158
175
|
this.updateCanvasLayout();
|
|
159
176
|
} else if (isMoved) {
|
|
@@ -186,16 +203,18 @@ export class Renderer {
|
|
|
186
203
|
let mesh = this.meshMap.get(node.element);
|
|
187
204
|
if (!mesh) {
|
|
188
205
|
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
189
|
-
|
|
206
|
+
let material: THREE.MeshBasicMaterial | THREE.Material;
|
|
207
|
+
material = Painter.create(
|
|
190
208
|
"BOX",
|
|
191
209
|
node.styles,
|
|
192
210
|
"",
|
|
193
211
|
node.rect.width,
|
|
194
212
|
node.rect.height,
|
|
213
|
+
this.qualityFactor,
|
|
214
|
+
node.isTraveler ? this.renderTarget?.texture : undefined,
|
|
215
|
+
node.shaderHooks
|
|
195
216
|
);
|
|
196
|
-
|
|
197
217
|
mesh = new THREE.Mesh(geometry, material);
|
|
198
|
-
mesh.layers.set(node.visibility);
|
|
199
218
|
if (node.type === "TEXT") mesh.name = "BG_MESH";
|
|
200
219
|
this.scene.add(mesh);
|
|
201
220
|
this.meshMap.set(node.element, mesh);
|
|
@@ -204,6 +223,8 @@ export class Renderer {
|
|
|
204
223
|
mesh.userData.domRect = node.rect;
|
|
205
224
|
|
|
206
225
|
this.updateMeshProperties(mesh, node);
|
|
226
|
+
this.updateMeshLayers(mesh, node);
|
|
227
|
+
if (node.isTraveler) mesh.layers.enable(28);
|
|
207
228
|
|
|
208
229
|
if (node.type === "BOX") {
|
|
209
230
|
for (const child of node.children) {
|
|
@@ -239,7 +260,7 @@ export class Renderer {
|
|
|
239
260
|
node.textContent || "",
|
|
240
261
|
node.rect.width,
|
|
241
262
|
node.rect.height,
|
|
242
|
-
this.
|
|
263
|
+
this.qualityFactor,
|
|
243
264
|
);
|
|
244
265
|
|
|
245
266
|
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
@@ -247,7 +268,7 @@ export class Renderer {
|
|
|
247
268
|
|
|
248
269
|
textMesh.name = "TEXT_CHILD";
|
|
249
270
|
textMesh.userData = { styleHash: currentStyleHash };
|
|
250
|
-
|
|
271
|
+
this.updateMeshLayers(textMesh, node);
|
|
251
272
|
textMesh.position.z = 0.005;
|
|
252
273
|
parentMesh.add(textMesh);
|
|
253
274
|
}
|
|
@@ -297,10 +318,109 @@ export class Renderer {
|
|
|
297
318
|
"",
|
|
298
319
|
node.rect.width,
|
|
299
320
|
node.rect.height,
|
|
321
|
+
this.qualityFactor,
|
|
322
|
+
node.isTraveler ? this.renderTarget?.texture : undefined,
|
|
300
323
|
);
|
|
301
324
|
}
|
|
302
325
|
|
|
326
|
+
private updateMeshLayers(mesh: THREE.Mesh, node: SceneNode) {
|
|
327
|
+
const layerNum = (1 - (node.visibility & USER_LAYER)) * 30;
|
|
328
|
+
mesh.layers.set(layerNum);
|
|
329
|
+
if (node.visibility === (USER_LAYER | SYSTEM_LAYER)) mesh.layers.enable(29);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private captureRenderTarget() {
|
|
333
|
+
const travelers: THREE.Mesh[] = [];
|
|
334
|
+
for (const mesh of this.meshMap.values()) {
|
|
335
|
+
if ((mesh.layers.mask & (1 << 28)) !== 0) {
|
|
336
|
+
travelers.push(mesh);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (travelers.length === 0) return;
|
|
340
|
+
|
|
341
|
+
const oldClearColor = new THREE.Color();
|
|
342
|
+
const oldClearAlpha = this.renderer.getClearAlpha();
|
|
343
|
+
this.renderer.getClearColor(oldClearColor);
|
|
344
|
+
|
|
345
|
+
this.renderer.setClearColor(0x000000, 0);
|
|
346
|
+
this.renderer.setRenderTarget(this.renderTarget);
|
|
347
|
+
|
|
348
|
+
this.renderer.clear();
|
|
349
|
+
|
|
350
|
+
this.renderer.autoClear = false;
|
|
351
|
+
this.renderer.setScissorTest(true);
|
|
352
|
+
this.camera.layers.set(29);
|
|
353
|
+
|
|
354
|
+
const vector = new THREE.Vector3();
|
|
355
|
+
const canvasWidth = this.targetRect.width;
|
|
356
|
+
const canvasHeight = this.targetRect.height;
|
|
357
|
+
|
|
358
|
+
const pixelRatio = this.renderer.getPixelRatio();
|
|
359
|
+
|
|
360
|
+
for (const traveler of travelers) {
|
|
361
|
+
vector.setFromMatrixPosition(traveler.matrixWorld);
|
|
362
|
+
vector.project(this.camera);
|
|
363
|
+
|
|
364
|
+
const centerX = ((vector.x + 1) / 2) * canvasWidth;
|
|
365
|
+
const centerY = ((vector.y + 1) / 2) * canvasHeight;
|
|
366
|
+
|
|
367
|
+
let clipDiff = 0;
|
|
368
|
+
let clipRadito = 1;
|
|
369
|
+
if (typeof this.clipArea === "number") {
|
|
370
|
+
clipRadito = this.clipArea;
|
|
371
|
+
} else if (this.clipArea.endsWith("%")) {
|
|
372
|
+
clipRadito = parseFloat(this.clipArea) / 100;
|
|
373
|
+
} else if (this.clipArea.endsWith("px")) {
|
|
374
|
+
clipDiff = parseFloat(this.clipArea);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const width = traveler.scale.x * clipRadito + 0.5;
|
|
378
|
+
const height = traveler.scale.y * clipRadito + 0.5;
|
|
379
|
+
|
|
380
|
+
const localX = centerX - width / 2;
|
|
381
|
+
const localY = centerY - height / 2;
|
|
382
|
+
|
|
383
|
+
const scissorX = (localX * this.qualityFactor - clipDiff) / pixelRatio;
|
|
384
|
+
const scissorY = (localY * this.qualityFactor - clipDiff) / pixelRatio;
|
|
385
|
+
const scissorW = (width * this.qualityFactor + clipDiff * 2) / pixelRatio;
|
|
386
|
+
const scissorH =
|
|
387
|
+
(height * this.qualityFactor + clipDiff * 2) / pixelRatio;
|
|
388
|
+
|
|
389
|
+
this.renderer.setScissor(scissorX, scissorY, scissorW, scissorH);
|
|
390
|
+
this.renderer.render(this.scene, this.camera);
|
|
391
|
+
}
|
|
392
|
+
this.renderer.setScissorTest(false);
|
|
393
|
+
this.renderer.autoClear = true;
|
|
394
|
+
this.renderer.setRenderTarget(null);
|
|
395
|
+
this.camera.layers.set(28);
|
|
396
|
+
this.renderer.setClearColor(oldClearColor, oldClearAlpha);
|
|
397
|
+
}
|
|
398
|
+
|
|
303
399
|
public render() {
|
|
400
|
+
if (this.renderTarget) this.captureRenderTarget();
|
|
304
401
|
this.renderer.render(this.scene, this.camera);
|
|
305
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);
|
|
420
|
+
|
|
421
|
+
debugMesh.position.set(0, 0, 90);
|
|
422
|
+
debugMesh.layers.set(28);
|
|
423
|
+
|
|
424
|
+
this.scene.add(debugMesh);
|
|
425
|
+
}
|
|
306
426
|
}
|
package/src/types/common.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TextStyles, BoxStyles } from "@mirage-engine/painter";
|
|
1
|
+
import { TextStyles, BoxStyles, ShaderHooks } from "@mirage-engine/painter";
|
|
2
2
|
import { Visibility } from "./flags";
|
|
3
3
|
|
|
4
|
-
export type NodeType = "BOX" | "TEXT"
|
|
4
|
+
export type NodeType = "BOX" | "TEXT";
|
|
5
5
|
|
|
6
6
|
export interface NodeRect {
|
|
7
7
|
x: number;
|
|
@@ -14,7 +14,7 @@ export interface SceneNode {
|
|
|
14
14
|
id: string;
|
|
15
15
|
type: NodeType;
|
|
16
16
|
element: HTMLElement;
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
rect: NodeRect;
|
|
19
19
|
styles: BoxStyles;
|
|
20
20
|
|
|
@@ -25,7 +25,8 @@ export interface SceneNode {
|
|
|
25
25
|
// TODO: SceneNode의 sort 로직 추가(아마도)
|
|
26
26
|
|
|
27
27
|
visibility: Visibility;
|
|
28
|
-
|
|
28
|
+
isTraveler: boolean;
|
|
29
|
+
shaderHooks?: ShaderHooks;
|
|
30
|
+
|
|
29
31
|
children: SceneNode[];
|
|
30
32
|
}
|
|
31
|
-
|
package/src/types/config.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type Quality = "low" | "medium" | "high" | number;
|
|
2
2
|
export type MirageMode = "overlay" | "duplicate";
|
|
3
|
-
|
|
3
|
+
export type PxUnit = `${number}px`;
|
|
4
|
+
export type PercentUnit = `${number}%`;
|
|
5
|
+
export type travelerClipArea = PxUnit | PercentUnit | number;
|
|
4
6
|
export interface FilterConfig {
|
|
5
7
|
includeTree?: string[];
|
|
6
8
|
excludeTree?: string[];
|
|
@@ -9,13 +11,21 @@ export interface FilterConfig {
|
|
|
9
11
|
end?: string[];
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
export interface ResizeConfig {
|
|
15
|
+
delay?: number;
|
|
16
|
+
onStart?: () => void;
|
|
17
|
+
onEnd?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
interface BaseConfig {
|
|
13
21
|
debug?: boolean;
|
|
14
|
-
|
|
22
|
+
quality?: Quality;
|
|
15
23
|
style?: {
|
|
16
24
|
zIndex?: string;
|
|
17
25
|
};
|
|
18
26
|
filter?: FilterConfig;
|
|
27
|
+
resizeDebounce?: boolean | ResizeConfig;
|
|
28
|
+
travelerClipArea? : travelerClipArea;
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
export interface OverlayConfig extends BaseConfig {
|
package/src/types/flags.ts
CHANGED
|
@@ -7,12 +7,11 @@ export const DIRTY_STRUCTURE = 1 << 3; // 8: childNode add / delete (00001000)
|
|
|
7
7
|
export const DIRTY_CONTENT = 1 << 4; // 16: text content changed (00010000)
|
|
8
8
|
|
|
9
9
|
// Filtering Flag
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
12
|
-
export const EXCLUDED =
|
|
10
|
+
export const USER_LAYER = 1 << 0;
|
|
11
|
+
export const SYSTEM_LAYER = 1 << 1;
|
|
12
|
+
export const EXCLUDED = 0;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
export type Visibility = typeof EXCLUDED | typeof SYSTEM | typeof INCLUDED;
|
|
14
|
+
export type Visibility = 0 | 1 | 2 | 3;
|
|
16
15
|
|
|
17
16
|
export const ALLOWED_FILTERS = [
|
|
18
17
|
"include-tree",
|