@mirage-engine/core 0.0.2 → 0.2.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 +17 -0
- package/dist/mirage-engine.js +483 -299
- package/dist/mirage-engine.umd.js +57 -27
- package/dist/src/core/Syncer.d.ts +7 -1
- package/dist/src/dom/Extractor.d.ts +3 -2
- package/dist/src/renderer/Renderer.d.ts +6 -2
- package/dist/src/types/common.d.ts +3 -0
- package/dist/src/types/config.d.ts +16 -2
- package/dist/src/types/flags.d.ts +5 -0
- package/package.json +2 -2
- package/src/core/Engine.ts +1 -1
- package/src/core/Syncer.ts +72 -4
- package/src/dom/Extractor.ts +87 -4
- package/src/renderer/Renderer.ts +135 -31
- package/src/types/common.ts +8 -5
- package/src/types/config.ts +21 -6
- package/src/types/flags.ts +16 -1
package/src/renderer/Renderer.ts
CHANGED
|
@@ -2,9 +2,11 @@ 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,
|
|
8
10
|
} from "../types";
|
|
9
11
|
import { Painter } from "@mirage-engine/painter";
|
|
10
12
|
|
|
@@ -13,10 +15,10 @@ export class Renderer {
|
|
|
13
15
|
private readonly scene: THREE.Scene;
|
|
14
16
|
private readonly camera: THREE.OrthographicCamera;
|
|
15
17
|
private readonly renderer: THREE.WebGLRenderer;
|
|
18
|
+
private renderTarget: THREE.WebGLRenderTarget | null = null;
|
|
16
19
|
private renderOrder: number = 0;
|
|
17
|
-
private
|
|
20
|
+
private qualityFactor: number = 2;
|
|
18
21
|
private mode: MirageMode = "overlay";
|
|
19
|
-
private customZIndex: string = "9999";
|
|
20
22
|
|
|
21
23
|
private target: HTMLElement;
|
|
22
24
|
private mountContainer: HTMLElement;
|
|
@@ -34,10 +36,6 @@ export class Renderer {
|
|
|
34
36
|
|
|
35
37
|
this.mode = config.mode ?? "overlay";
|
|
36
38
|
|
|
37
|
-
if (config.style?.zIndex) {
|
|
38
|
-
this.customZIndex = config.style.zIndex;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
39
|
this.canvas = document.createElement("canvas");
|
|
42
40
|
this.scene = new THREE.Scene();
|
|
43
41
|
|
|
@@ -58,42 +56,61 @@ export class Renderer {
|
|
|
58
56
|
1000,
|
|
59
57
|
);
|
|
60
58
|
this.camera.position.z = 100;
|
|
59
|
+
this.camera.layers.set(0);
|
|
60
|
+
|
|
61
|
+
// [new]
|
|
62
|
+
// THREE.ColorManagement.enabled = false;
|
|
61
63
|
|
|
62
64
|
this.renderer = new THREE.WebGLRenderer({
|
|
63
65
|
canvas: this.canvas,
|
|
64
66
|
alpha: true,
|
|
65
67
|
antialias: true,
|
|
68
|
+
// [new]
|
|
69
|
+
// premultipliedAlpha: true
|
|
66
70
|
});
|
|
67
71
|
|
|
68
72
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
69
73
|
this.renderer.setSize(width, height);
|
|
70
74
|
|
|
71
|
-
this.applyTextQuality(config.
|
|
75
|
+
this.applyTextQuality(config.quality ?? "medium");
|
|
76
|
+
}
|
|
77
|
+
public createRenderTarget() {
|
|
78
|
+
this.renderTarget = new THREE.WebGLRenderTarget(
|
|
79
|
+
this.targetRect.width * this.qualityFactor,
|
|
80
|
+
this.targetRect.height * this.qualityFactor,
|
|
81
|
+
{
|
|
82
|
+
minFilter: THREE.LinearFilter,
|
|
83
|
+
magFilter: THREE.LinearFilter,
|
|
84
|
+
format: THREE.RGBAFormat,
|
|
85
|
+
stencilBuffer: false,
|
|
86
|
+
depthBuffer: true,
|
|
87
|
+
samples: 4,
|
|
88
|
+
},
|
|
89
|
+
);
|
|
72
90
|
}
|
|
73
91
|
|
|
74
|
-
private applyTextQuality(quality:
|
|
92
|
+
private applyTextQuality(quality: Quality) {
|
|
75
93
|
if (typeof quality === "number") {
|
|
76
|
-
this.
|
|
94
|
+
this.qualityFactor = Math.max(0.1, quality);
|
|
77
95
|
return;
|
|
78
96
|
}
|
|
79
97
|
switch (quality) {
|
|
80
98
|
case "low":
|
|
81
|
-
this.
|
|
99
|
+
this.qualityFactor = 1;
|
|
82
100
|
break;
|
|
83
101
|
case "high":
|
|
84
|
-
this.
|
|
102
|
+
this.qualityFactor = 4;
|
|
85
103
|
break;
|
|
86
104
|
case "medium":
|
|
87
105
|
default:
|
|
88
|
-
this.
|
|
106
|
+
this.qualityFactor = 2;
|
|
89
107
|
break;
|
|
90
108
|
}
|
|
91
109
|
}
|
|
92
110
|
|
|
93
111
|
public mount() {
|
|
94
|
-
this.mountContainer.
|
|
112
|
+
this.mountContainer.prepend(this.canvas);
|
|
95
113
|
|
|
96
|
-
this.canvas.style.zIndex = this.customZIndex;
|
|
97
114
|
this.canvas.style.pointerEvents = this.mode === "overlay" ? "none" : "auto";
|
|
98
115
|
|
|
99
116
|
this.updateCanvasLayout();
|
|
@@ -107,7 +124,6 @@ export class Renderer {
|
|
|
107
124
|
this.canvas.style.position = "";
|
|
108
125
|
this.canvas.style.top = "";
|
|
109
126
|
this.canvas.style.left = "";
|
|
110
|
-
|
|
111
127
|
this.canvas.style.display = "block";
|
|
112
128
|
} else {
|
|
113
129
|
this.canvas.style.position = "absolute";
|
|
@@ -125,12 +141,16 @@ export class Renderer {
|
|
|
125
141
|
|
|
126
142
|
public setSize(width: number, height: number) {
|
|
127
143
|
this.renderer.setSize(width, height);
|
|
128
|
-
|
|
144
|
+
if (this.renderTarget) {
|
|
145
|
+
this.renderTarget.setSize(
|
|
146
|
+
width * this.qualityFactor,
|
|
147
|
+
height * this.qualityFactor,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
129
150
|
this.camera.left = width / -2;
|
|
130
151
|
this.camera.right = width / 2;
|
|
131
152
|
this.camera.top = height / 2;
|
|
132
153
|
this.camera.bottom = height / -2;
|
|
133
|
-
|
|
134
154
|
this.camera.updateProjectionMatrix();
|
|
135
155
|
}
|
|
136
156
|
|
|
@@ -148,13 +168,7 @@ export class Renderer {
|
|
|
148
168
|
|
|
149
169
|
if (isResized) {
|
|
150
170
|
this.targetRect = newRect;
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
this.camera.left = this.targetRect.width / -2;
|
|
154
|
-
this.camera.right = this.targetRect.width / 2;
|
|
155
|
-
this.camera.top = this.targetRect.height / 2;
|
|
156
|
-
this.camera.bottom = this.targetRect.height / -2;
|
|
157
|
-
this.camera.updateProjectionMatrix();
|
|
171
|
+
this.setSize(this.targetRect.width, this.targetRect.height);
|
|
158
172
|
|
|
159
173
|
this.updateCanvasLayout();
|
|
160
174
|
} else if (isMoved) {
|
|
@@ -174,7 +188,6 @@ export class Renderer {
|
|
|
174
188
|
if (!activeElements.has(el)) {
|
|
175
189
|
this.scene.remove(mesh);
|
|
176
190
|
|
|
177
|
-
// Garbage Collection
|
|
178
191
|
mesh.geometry.dispose();
|
|
179
192
|
if (mesh.material instanceof THREE.Material) mesh.material.dispose();
|
|
180
193
|
this.meshMap.delete(el);
|
|
@@ -188,17 +201,18 @@ export class Renderer {
|
|
|
188
201
|
let mesh = this.meshMap.get(node.element);
|
|
189
202
|
if (!mesh) {
|
|
190
203
|
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
191
|
-
|
|
204
|
+
let material: THREE.MeshBasicMaterial | THREE.Material;
|
|
205
|
+
material = Painter.create(
|
|
192
206
|
"BOX",
|
|
193
207
|
node.styles,
|
|
194
208
|
"",
|
|
195
209
|
node.rect.width,
|
|
196
210
|
node.rect.height,
|
|
211
|
+
this.qualityFactor,
|
|
212
|
+
node.isTraveler ? this.renderTarget?.texture : undefined,
|
|
197
213
|
);
|
|
198
|
-
|
|
199
214
|
mesh = new THREE.Mesh(geometry, material);
|
|
200
215
|
if (node.type === "TEXT") mesh.name = "BG_MESH";
|
|
201
|
-
|
|
202
216
|
this.scene.add(mesh);
|
|
203
217
|
this.meshMap.set(node.element, mesh);
|
|
204
218
|
}
|
|
@@ -206,6 +220,8 @@ export class Renderer {
|
|
|
206
220
|
mesh.userData.domRect = node.rect;
|
|
207
221
|
|
|
208
222
|
this.updateMeshProperties(mesh, node);
|
|
223
|
+
this.updateMeshLayers(mesh, node);
|
|
224
|
+
if (node.isTraveler) mesh.layers.enable(28);
|
|
209
225
|
|
|
210
226
|
if (node.type === "BOX") {
|
|
211
227
|
for (const child of node.children) {
|
|
@@ -241,7 +257,7 @@ export class Renderer {
|
|
|
241
257
|
node.textContent || "",
|
|
242
258
|
node.rect.width,
|
|
243
259
|
node.rect.height,
|
|
244
|
-
this.
|
|
260
|
+
this.qualityFactor,
|
|
245
261
|
);
|
|
246
262
|
|
|
247
263
|
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
@@ -249,7 +265,7 @@ export class Renderer {
|
|
|
249
265
|
|
|
250
266
|
textMesh.name = "TEXT_CHILD";
|
|
251
267
|
textMesh.userData = { styleHash: currentStyleHash };
|
|
252
|
-
|
|
268
|
+
this.updateMeshLayers(textMesh, node);
|
|
253
269
|
textMesh.position.z = 0.005;
|
|
254
270
|
parentMesh.add(textMesh);
|
|
255
271
|
}
|
|
@@ -299,10 +315,98 @@ export class Renderer {
|
|
|
299
315
|
"",
|
|
300
316
|
node.rect.width,
|
|
301
317
|
node.rect.height,
|
|
318
|
+
this.qualityFactor,
|
|
319
|
+
node.isTraveler ? this.renderTarget?.texture : undefined,
|
|
302
320
|
);
|
|
303
321
|
}
|
|
304
322
|
|
|
323
|
+
private updateMeshLayers(mesh: THREE.Mesh, node: SceneNode) {
|
|
324
|
+
const layerNum = (1 - (node.visibility & USER_LAYER)) * 30;
|
|
325
|
+
mesh.layers.set(layerNum);
|
|
326
|
+
if (node.visibility === (USER_LAYER | SYSTEM_LAYER)) mesh.layers.enable(29);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private captureRenderTarget() {
|
|
330
|
+
const travelers: THREE.Mesh[] = [];
|
|
331
|
+
for (const mesh of this.meshMap.values()) {
|
|
332
|
+
if ((mesh.layers.mask & (1 << 28)) !== 0) {
|
|
333
|
+
travelers.push(mesh);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (travelers.length === 0) return;
|
|
337
|
+
|
|
338
|
+
const oldClearColor = new THREE.Color();
|
|
339
|
+
const oldClearAlpha = this.renderer.getClearAlpha();
|
|
340
|
+
this.renderer.getClearColor(oldClearColor);
|
|
341
|
+
|
|
342
|
+
this.renderer.setClearColor(0x000000, 0);
|
|
343
|
+
this.renderer.setRenderTarget(this.renderTarget);
|
|
344
|
+
|
|
345
|
+
this.renderer.clear();
|
|
346
|
+
|
|
347
|
+
this.renderer.autoClear = false;
|
|
348
|
+
this.renderer.setScissorTest(true);
|
|
349
|
+
this.camera.layers.set(29);
|
|
350
|
+
|
|
351
|
+
const vector = new THREE.Vector3();
|
|
352
|
+
const canvasWidth = this.targetRect.width;
|
|
353
|
+
const canvasHeight = this.targetRect.height;
|
|
354
|
+
|
|
355
|
+
const pixelRatio = this.renderer.getPixelRatio();
|
|
356
|
+
|
|
357
|
+
for (const traveler of travelers) {
|
|
358
|
+
vector.setFromMatrixPosition(traveler.matrixWorld);
|
|
359
|
+
vector.project(this.camera);
|
|
360
|
+
|
|
361
|
+
const centerX = ((vector.x + 1) / 2) * canvasWidth;
|
|
362
|
+
const centerY = ((vector.y + 1) / 2) * canvasHeight;
|
|
363
|
+
|
|
364
|
+
const width = traveler.scale.x;
|
|
365
|
+
const height = traveler.scale.y;
|
|
366
|
+
|
|
367
|
+
const localX = centerX - width / 2;
|
|
368
|
+
const localY = centerY - height / 2;
|
|
369
|
+
|
|
370
|
+
const scissorX = (localX * this.qualityFactor) / pixelRatio;
|
|
371
|
+
const scissorY = (localY * this.qualityFactor) / pixelRatio;
|
|
372
|
+
const scissorW = (width * this.qualityFactor) / pixelRatio;
|
|
373
|
+
const scissorH = (height * this.qualityFactor) / pixelRatio;
|
|
374
|
+
|
|
375
|
+
this.renderer.setScissor(scissorX, scissorY, scissorW, scissorH);
|
|
376
|
+
this.renderer.render(this.scene, this.camera);
|
|
377
|
+
}
|
|
378
|
+
this.renderer.setScissorTest(false);
|
|
379
|
+
this.renderer.autoClear = true;
|
|
380
|
+
this.renderer.setRenderTarget(null);
|
|
381
|
+
this.camera.layers.set(28);
|
|
382
|
+
this.renderer.setClearColor(oldClearColor, oldClearAlpha);
|
|
383
|
+
}
|
|
384
|
+
|
|
305
385
|
public render() {
|
|
386
|
+
if (this.renderTarget) this.captureRenderTarget();
|
|
306
387
|
this.renderer.render(this.scene, this.camera);
|
|
307
388
|
}
|
|
389
|
+
|
|
390
|
+
// for debugging
|
|
391
|
+
public showScissoredRenderTarget() {
|
|
392
|
+
if (!this.renderTarget) return;
|
|
393
|
+
|
|
394
|
+
const w = this.targetRect.width;
|
|
395
|
+
const h = this.targetRect.height;
|
|
396
|
+
|
|
397
|
+
const geometry = new THREE.PlaneGeometry(w, h);
|
|
398
|
+
const material = new THREE.MeshBasicMaterial({
|
|
399
|
+
map: this.renderTarget.texture,
|
|
400
|
+
side: THREE.DoubleSide,
|
|
401
|
+
transparent: true,
|
|
402
|
+
opacity: 0.8,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const debugMesh = new THREE.Mesh(geometry, material);
|
|
406
|
+
|
|
407
|
+
debugMesh.position.set(0, 0, 90);
|
|
408
|
+
debugMesh.layers.set(28);
|
|
409
|
+
|
|
410
|
+
this.scene.add(debugMesh);
|
|
411
|
+
}
|
|
308
412
|
}
|
package/src/types/common.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TextStyles, BoxStyles } from "@mirage-engine/painter";
|
|
2
|
+
import { Visibility } from "./flags";
|
|
2
3
|
|
|
3
|
-
export type NodeType = "BOX" | "TEXT"
|
|
4
|
+
export type NodeType = "BOX" | "TEXT";
|
|
4
5
|
|
|
5
6
|
export interface NodeRect {
|
|
6
7
|
x: number;
|
|
@@ -9,13 +10,11 @@ export interface NodeRect {
|
|
|
9
10
|
height: number;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
13
|
export interface SceneNode {
|
|
15
14
|
id: string;
|
|
16
15
|
type: NodeType;
|
|
17
16
|
element: HTMLElement;
|
|
18
|
-
|
|
17
|
+
|
|
19
18
|
rect: NodeRect;
|
|
20
19
|
styles: BoxStyles;
|
|
21
20
|
|
|
@@ -23,6 +22,10 @@ export interface SceneNode {
|
|
|
23
22
|
textStyles?: TextStyles;
|
|
24
23
|
|
|
25
24
|
dirtyMask: number;
|
|
25
|
+
// TODO: SceneNode의 sort 로직 추가(아마도)
|
|
26
|
+
|
|
27
|
+
visibility: Visibility;
|
|
28
|
+
isTraveler: boolean;
|
|
29
|
+
|
|
26
30
|
children: SceneNode[];
|
|
27
31
|
}
|
|
28
|
-
|
package/src/types/config.ts
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type Quality = "low" | "medium" | "high" | number;
|
|
2
2
|
export type MirageMode = "overlay" | "duplicate";
|
|
3
|
+
export interface FilterConfig {
|
|
4
|
+
includeTree?: string[];
|
|
5
|
+
excludeTree?: string[];
|
|
6
|
+
includeSelf?: string[];
|
|
7
|
+
excludeSelf?: string[];
|
|
8
|
+
end?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ResizeConfig {
|
|
12
|
+
delay?: number;
|
|
13
|
+
onStart?: () => void;
|
|
14
|
+
onEnd?: () => void;
|
|
15
|
+
}
|
|
3
16
|
|
|
4
17
|
interface BaseConfig {
|
|
5
18
|
debug?: boolean;
|
|
6
|
-
|
|
19
|
+
quality?: Quality;
|
|
7
20
|
style?: {
|
|
8
21
|
zIndex?: string;
|
|
9
22
|
};
|
|
23
|
+
filter?: FilterConfig;
|
|
24
|
+
resizeDebounce?: boolean | ResizeConfig;
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
export interface OverlayConfig extends BaseConfig {
|
|
13
|
-
mode?: "overlay";
|
|
28
|
+
mode?: "overlay";
|
|
14
29
|
}
|
|
15
30
|
|
|
16
31
|
export interface DuplicateConfig extends BaseConfig {
|
|
17
|
-
mode: "duplicate";
|
|
18
|
-
container?: HTMLElement;
|
|
32
|
+
mode: "duplicate";
|
|
33
|
+
container?: HTMLElement;
|
|
19
34
|
}
|
|
20
35
|
|
|
21
|
-
export type CoreConfig = OverlayConfig | DuplicateConfig;
|
|
36
|
+
export type CoreConfig = OverlayConfig | DuplicateConfig;
|
package/src/types/flags.ts
CHANGED
|
@@ -4,4 +4,19 @@ export const DIRTY_RECT = 1 << 0; // 1: x, y, width, height (00000001)
|
|
|
4
4
|
export const DIRTY_STYLE = 1 << 1; // 2: style (00000010)
|
|
5
5
|
export const DIRTY_ZINDEX = 1 << 2; // 4: zIndex (00000100)
|
|
6
6
|
export const DIRTY_STRUCTURE = 1 << 3; // 8: childNode add / delete (00001000)
|
|
7
|
-
export const DIRTY_CONTENT = 1 << 4;
|
|
7
|
+
export const DIRTY_CONTENT = 1 << 4; // 16: text content changed (00010000)
|
|
8
|
+
|
|
9
|
+
// Filtering Flag
|
|
10
|
+
export const USER_LAYER = 1 << 0;
|
|
11
|
+
export const SYSTEM_LAYER = 1 << 1;
|
|
12
|
+
export const EXCLUDED = 0;
|
|
13
|
+
|
|
14
|
+
export type Visibility = 0 | 1 | 2 | 3;
|
|
15
|
+
|
|
16
|
+
export const ALLOWED_FILTERS = [
|
|
17
|
+
"include-tree",
|
|
18
|
+
"exclude-tree",
|
|
19
|
+
"include-self",
|
|
20
|
+
"exclude-self",
|
|
21
|
+
"end",
|
|
22
|
+
];
|