@mirage-engine/core 0.1.0 → 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.
@@ -2,9 +2,11 @@ import * as THREE from "three";
2
2
  import {
3
3
  SceneNode,
4
4
  DIRTY_CONTENT,
5
- TextQuality,
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,8 +15,9 @@ 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 textQualityFactor: number = 2;
20
+ private qualityFactor: number = 2;
18
21
  private mode: MirageMode = "overlay";
19
22
 
20
23
  private target: HTMLElement;
@@ -53,6 +56,7 @@ export class Renderer {
53
56
  1000,
54
57
  );
55
58
  this.camera.position.z = 100;
59
+ this.camera.layers.set(0);
56
60
 
57
61
  // [new]
58
62
  // THREE.ColorManagement.enabled = false;
@@ -68,24 +72,38 @@ export class Renderer {
68
72
  this.renderer.setPixelRatio(window.devicePixelRatio);
69
73
  this.renderer.setSize(width, height);
70
74
 
71
- this.applyTextQuality(config.textQuality ?? "medium");
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: TextQuality) {
92
+ private applyTextQuality(quality: Quality) {
75
93
  if (typeof quality === "number") {
76
- this.textQualityFactor = Math.max(0.1, quality);
94
+ this.qualityFactor = Math.max(0.1, quality);
77
95
  return;
78
96
  }
79
97
  switch (quality) {
80
98
  case "low":
81
- this.textQualityFactor = 1;
99
+ this.qualityFactor = 1;
82
100
  break;
83
101
  case "high":
84
- this.textQualityFactor = 4;
102
+ this.qualityFactor = 4;
85
103
  break;
86
104
  case "medium":
87
105
  default:
88
- this.textQualityFactor = 2;
106
+ this.qualityFactor = 2;
89
107
  break;
90
108
  }
91
109
  }
@@ -123,12 +141,16 @@ export class Renderer {
123
141
 
124
142
  public setSize(width: number, height: number) {
125
143
  this.renderer.setSize(width, height);
126
-
144
+ if (this.renderTarget) {
145
+ this.renderTarget.setSize(
146
+ width * this.qualityFactor,
147
+ height * this.qualityFactor,
148
+ );
149
+ }
127
150
  this.camera.left = width / -2;
128
151
  this.camera.right = width / 2;
129
152
  this.camera.top = height / 2;
130
153
  this.camera.bottom = height / -2;
131
-
132
154
  this.camera.updateProjectionMatrix();
133
155
  }
134
156
 
@@ -146,14 +168,7 @@ export class Renderer {
146
168
 
147
169
  if (isResized) {
148
170
  this.targetRect = newRect;
149
- this.renderer.setSize(this.targetRect.width, this.targetRect.height);
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();
171
+ this.setSize(this.targetRect.width, this.targetRect.height);
157
172
 
158
173
  this.updateCanvasLayout();
159
174
  } else if (isMoved) {
@@ -186,16 +201,17 @@ export class Renderer {
186
201
  let mesh = this.meshMap.get(node.element);
187
202
  if (!mesh) {
188
203
  const geometry = new THREE.PlaneGeometry(1, 1);
189
- const material = Painter.create(
204
+ let material: THREE.MeshBasicMaterial | THREE.Material;
205
+ material = Painter.create(
190
206
  "BOX",
191
207
  node.styles,
192
208
  "",
193
209
  node.rect.width,
194
210
  node.rect.height,
211
+ this.qualityFactor,
212
+ node.isTraveler ? this.renderTarget?.texture : undefined,
195
213
  );
196
-
197
214
  mesh = new THREE.Mesh(geometry, material);
198
- mesh.layers.set(node.visibility);
199
215
  if (node.type === "TEXT") mesh.name = "BG_MESH";
200
216
  this.scene.add(mesh);
201
217
  this.meshMap.set(node.element, mesh);
@@ -204,6 +220,8 @@ export class Renderer {
204
220
  mesh.userData.domRect = node.rect;
205
221
 
206
222
  this.updateMeshProperties(mesh, node);
223
+ this.updateMeshLayers(mesh, node);
224
+ if (node.isTraveler) mesh.layers.enable(28);
207
225
 
208
226
  if (node.type === "BOX") {
209
227
  for (const child of node.children) {
@@ -239,7 +257,7 @@ export class Renderer {
239
257
  node.textContent || "",
240
258
  node.rect.width,
241
259
  node.rect.height,
242
- this.textQualityFactor,
260
+ this.qualityFactor,
243
261
  );
244
262
 
245
263
  const geometry = new THREE.PlaneGeometry(1, 1);
@@ -247,7 +265,7 @@ export class Renderer {
247
265
 
248
266
  textMesh.name = "TEXT_CHILD";
249
267
  textMesh.userData = { styleHash: currentStyleHash };
250
- textMesh.layers.set(node.visibility);
268
+ this.updateMeshLayers(textMesh, node);
251
269
  textMesh.position.z = 0.005;
252
270
  parentMesh.add(textMesh);
253
271
  }
@@ -297,10 +315,98 @@ export class Renderer {
297
315
  "",
298
316
  node.rect.width,
299
317
  node.rect.height,
318
+ this.qualityFactor,
319
+ node.isTraveler ? this.renderTarget?.texture : undefined,
300
320
  );
301
321
  }
302
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
+
303
385
  public render() {
386
+ if (this.renderTarget) this.captureRenderTarget();
304
387
  this.renderer.render(this.scene, this.camera);
305
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
+ }
306
412
  }
@@ -1,7 +1,7 @@
1
1
  import { TextStyles, BoxStyles } 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,7 @@ export interface SceneNode {
25
25
  // TODO: SceneNode의 sort 로직 추가(아마도)
26
26
 
27
27
  visibility: Visibility;
28
-
28
+ isTraveler: boolean;
29
+
29
30
  children: SceneNode[];
30
31
  }
31
-
@@ -1,6 +1,5 @@
1
- export type TextQuality = "low" | "medium" | "high" | number;
1
+ export type Quality = "low" | "medium" | "high" | number;
2
2
  export type MirageMode = "overlay" | "duplicate";
3
-
4
3
  export interface FilterConfig {
5
4
  includeTree?: string[];
6
5
  excludeTree?: string[];
@@ -9,13 +8,20 @@ export interface FilterConfig {
9
8
  end?: string[];
10
9
  }
11
10
 
11
+ export interface ResizeConfig {
12
+ delay?: number;
13
+ onStart?: () => void;
14
+ onEnd?: () => void;
15
+ }
16
+
12
17
  interface BaseConfig {
13
18
  debug?: boolean;
14
- textQuality?: TextQuality;
19
+ quality?: Quality;
15
20
  style?: {
16
21
  zIndex?: string;
17
22
  };
18
23
  filter?: FilterConfig;
24
+ resizeDebounce?: boolean | ResizeConfig;
19
25
  }
20
26
 
21
27
  export interface OverlayConfig extends BaseConfig {
@@ -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 INCLUDED = 0;
11
- export const SYSTEM = 29;
12
- export const EXCLUDED = 30;
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",