@joystyk/render3d 0.0.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.
- package/CHANGELOG.md +19 -0
- package/README.md +20 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/lifecycle.d.ts +13 -0
- package/dist/lifecycle.js +64 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/quality.d.ts +11 -0
- package/dist/quality.js +44 -0
- package/dist/quality.js.map +1 -0
- package/dist/stage.d.ts +41 -0
- package/dist/stage.js +187 -0
- package/dist/stage.js.map +1 -0
- package/package.json +42 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bb095c7: Add shared real-time input, pause, pace records and ghost races, plus a WebGL 2 renderer
|
|
8
|
+
lifecycle with context recovery and adaptive quality. Launch daily fixed-step simulations
|
|
9
|
+
from published configs and retain their puzzle identity through saved games.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [bb095c7]
|
|
14
|
+
- Updated dependencies [bb095c7]
|
|
15
|
+
- @joystyk/contracts@0.2.0
|
|
16
|
+
|
|
17
|
+
## 0.1.0
|
|
18
|
+
|
|
19
|
+
Initial shared arcade kit (host policy).
|
package/README.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @joystyk/render3d
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Three.js lifecycle helpers, imported only from a game's `render/` layer.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`createRenderStage({ host, build, pause, announce })` creates a strict WebGL 2 surface. The
|
|
6
|
+
`build` callback receives a renderer, scene and `track(resource)` disposal helper, and returns
|
|
7
|
+
`{ camera, draw(view, alpha, quality), resize? }`. Feed snapshots through `stage.draw`;
|
|
8
|
+
identical view/alpha keys are skipped. The host's CSS sizes `.render3d-canvas` to its viewport.
|
|
9
|
+
`stage.dispose()` releases the observer, listeners, geometries, materials, textures and context.
|
|
10
|
+
|
|
11
|
+
A lost context calls `pause`, blocks drawing and offers **Try 3D again**. Restoring or retrying
|
|
12
|
+
runs `build` again, so GPU resources are recreated. It never resumes the simulation.
|
|
13
|
+
`stage.graphics('auto' | 'low' | 'medium' | 'high')` selects adaptive or pinned quality.
|
|
14
|
+
Auto starts at medium and steps down after three seconds with render-time p95 over 16 ms;
|
|
15
|
+
it never steps up during a run. Pixel-ratio caps are 1, 1.5 and 2. The `development` option
|
|
16
|
+
shows p95, draw calls and triangles. Quality never changes the simulation.
|
|
17
|
+
|
|
18
|
+
`roomEnvironment`, `pickPlane` and `project` support game scenes. Existing scene classes can
|
|
19
|
+
use `createRenderer`, `webgl2Available` and `bindGraphicsRecovery` to share the same WebGL 2
|
|
20
|
+
boundary and full resource rebuild while retaining their own drawing APIs.
|
|
21
|
+
|
|
22
|
+
See host policy (Joystyk host reference) for reference-phone budgets. Desktop software
|
|
23
|
+
WebGL tests do not replace measurements on the reference phone.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createRenderStage, roomEnvironment, pickPlane, project, type SceneContext, type SceneDrawing, type RenderOptions, } from './stage.js';
|
|
2
|
+
export { AutoQuality, PIXEL_RATIO, type Quality, type Graphics } from './quality.js';
|
|
3
|
+
export { createRenderer, webgl2Available, bindGraphicsRecovery } from './lifecycle.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AutoQuality, PIXEL_RATIO } from "./quality.js";
|
|
2
|
+
import { createRenderStage, pickPlane, project, roomEnvironment } from "./stage.js";
|
|
3
|
+
import { bindGraphicsRecovery, createRenderer, webgl2Available } from "./lifecycle.js";
|
|
4
|
+
export { AutoQuality, PIXEL_RATIO, bindGraphicsRecovery, createRenderStage, createRenderer, pickPlane, project, roomEnvironment, webgl2Available };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { type Quality } from './quality.js';
|
|
3
|
+
/** Existing game scenes share the same strict WebGL 2 renderer boundary. */
|
|
4
|
+
export declare function createRenderer(options?: Omit<THREE.WebGLRendererParameters, 'canvas'> & {
|
|
5
|
+
canvas?: HTMLCanvasElement;
|
|
6
|
+
}, quality?: Quality): THREE.WebGLRenderer;
|
|
7
|
+
export declare function webgl2Available(): boolean;
|
|
8
|
+
/** Rebuilds all resources using the game's scene factory; it never resumes game time. */
|
|
9
|
+
export declare function bindGraphicsRecovery(host: HTMLElement, canvas: HTMLCanvasElement, o: {
|
|
10
|
+
pause(): void;
|
|
11
|
+
rebuild(): void;
|
|
12
|
+
announce?: (message: string) => void;
|
|
13
|
+
}): () => void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { PIXEL_RATIO } from "./quality.js";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
//#region src/lifecycle.ts
|
|
4
|
+
/** Existing game scenes share the same strict WebGL 2 renderer boundary. */
|
|
5
|
+
function createRenderer(options = {}, quality = "medium") {
|
|
6
|
+
const canvas = options.canvas ?? document.createElement("canvas");
|
|
7
|
+
const context = canvas.getContext("webgl2", {
|
|
8
|
+
antialias: options.antialias ?? true,
|
|
9
|
+
alpha: options.alpha ?? false,
|
|
10
|
+
powerPreference: options.powerPreference ?? "low-power"
|
|
11
|
+
});
|
|
12
|
+
if (context === null) throw Error("This game needs WebGL 2. Enable hardware acceleration, then try again.");
|
|
13
|
+
const renderer = new THREE.WebGLRenderer({
|
|
14
|
+
...options,
|
|
15
|
+
canvas,
|
|
16
|
+
context
|
|
17
|
+
});
|
|
18
|
+
renderer.setPixelRatio(Math.min(devicePixelRatio || 1, PIXEL_RATIO[quality]));
|
|
19
|
+
renderer.domElement.setAttribute("aria-hidden", "true");
|
|
20
|
+
return renderer;
|
|
21
|
+
}
|
|
22
|
+
function webgl2Available() {
|
|
23
|
+
const context = document.createElement("canvas").getContext("webgl2");
|
|
24
|
+
if (context === null) return false;
|
|
25
|
+
context.getExtension("WEBGL_lose_context")?.loseContext();
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
/** Rebuilds all resources using the game's scene factory; it never resumes game time. */
|
|
29
|
+
function bindGraphicsRecovery(host, canvas, o) {
|
|
30
|
+
const abort = new AbortController(), { signal } = abort;
|
|
31
|
+
const notice = document.createElement("p"), retry = document.createElement("button");
|
|
32
|
+
notice.hidden = true;
|
|
33
|
+
retry.hidden = true;
|
|
34
|
+
retry.className = "retry-graphics";
|
|
35
|
+
retry.type = "button";
|
|
36
|
+
retry.textContent = "Try 3D again";
|
|
37
|
+
const rebuild = () => {
|
|
38
|
+
o.rebuild();
|
|
39
|
+
};
|
|
40
|
+
canvas.addEventListener("webglcontextlost", (e) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
o.pause();
|
|
43
|
+
if (signal.aborted) return;
|
|
44
|
+
notice.hidden = false;
|
|
45
|
+
retry.hidden = false;
|
|
46
|
+
if (!retry.isConnected) host.append(notice, retry);
|
|
47
|
+
notice.textContent = "Graphics interrupted. Try 3D again to restore the scene.";
|
|
48
|
+
o.announce?.(notice.textContent);
|
|
49
|
+
}, { signal });
|
|
50
|
+
canvas.addEventListener("webglcontextrestored", rebuild, { signal });
|
|
51
|
+
retry.addEventListener("click", (event) => {
|
|
52
|
+
event.stopPropagation();
|
|
53
|
+
rebuild();
|
|
54
|
+
}, { signal });
|
|
55
|
+
return () => {
|
|
56
|
+
abort.abort();
|
|
57
|
+
notice.remove();
|
|
58
|
+
retry.remove();
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { bindGraphicsRecovery, createRenderer, webgl2Available };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","names":[],"sources":["../src/lifecycle.ts"],"sourcesContent":["import * as THREE from 'three';\nimport { PIXEL_RATIO, type Quality } from './quality.ts';\n/** Existing game scenes share the same strict WebGL 2 renderer boundary. */\nexport function createRenderer(\n options: Omit<THREE.WebGLRendererParameters, 'canvas'> & {\n canvas?: HTMLCanvasElement;\n } = {},\n quality: Quality = 'medium',\n): THREE.WebGLRenderer {\n const canvas = options.canvas ?? document.createElement('canvas');\n const context = canvas.getContext('webgl2', {\n antialias: options.antialias ?? true,\n alpha: options.alpha ?? false,\n powerPreference: options.powerPreference ?? 'low-power',\n });\n if (context === null) throw Error('This game needs WebGL 2. Enable hardware acceleration, then try again.');\n const renderer = new THREE.WebGLRenderer({ ...options, canvas, context: context as WebGL2RenderingContext });\n renderer.setPixelRatio(Math.min(devicePixelRatio || 1, PIXEL_RATIO[quality]));\n renderer.domElement.setAttribute('aria-hidden', 'true');\n return renderer;\n}\nexport function webgl2Available(): boolean {\n const canvas = document.createElement('canvas'),\n context = canvas.getContext('webgl2');\n if (context === null) return false;\n context.getExtension('WEBGL_lose_context')?.loseContext();\n return true;\n}\n/** Rebuilds all resources using the game's scene factory; it never resumes game time. */\nexport function bindGraphicsRecovery(\n host: HTMLElement,\n canvas: HTMLCanvasElement,\n o: {\n pause(): void;\n rebuild(): void;\n announce?: (message: string) => void;\n },\n): () => void {\n const abort = new AbortController(),\n { signal } = abort;\n const notice = document.createElement('p'),\n retry = document.createElement('button');\n notice.hidden = true;\n retry.hidden = true;\n retry.className = 'retry-graphics';\n retry.type = 'button';\n retry.textContent = 'Try 3D again';\n const rebuild = (): void => {\n o.rebuild();\n };\n canvas.addEventListener(\n 'webglcontextlost',\n (e) => {\n e.preventDefault();\n o.pause();\n if (signal.aborted) return;\n notice.hidden = false;\n retry.hidden = false;\n if (!retry.isConnected) host.append(notice, retry);\n notice.textContent = 'Graphics interrupted. Try 3D again to restore the scene.';\n o.announce?.(notice.textContent);\n },\n { signal },\n );\n canvas.addEventListener('webglcontextrestored', rebuild, { signal });\n retry.addEventListener(\n 'click',\n (event) => {\n event.stopPropagation();\n rebuild();\n },\n { signal },\n );\n return () => {\n abort.abort();\n notice.remove();\n retry.remove();\n };\n}\n"],"mappings":";;;;AAGA,SAAgB,eACd,UAEI,CAAC,GACL,UAAmB,UACE;CACrB,MAAM,SAAS,QAAQ,UAAU,SAAS,cAAc,QAAQ;CAChE,MAAM,UAAU,OAAO,WAAW,UAAU;EAC1C,WAAW,QAAQ,aAAa;EAChC,OAAO,QAAQ,SAAS;EACxB,iBAAiB,QAAQ,mBAAmB;CAC9C,CAAC;CACD,IAAI,YAAY,MAAM,MAAM,MAAM,wEAAwE;CAC1G,MAAM,WAAW,IAAI,MAAM,cAAc;EAAE,GAAG;EAAS;EAAiB;CAAkC,CAAC;CAC3G,SAAS,cAAc,KAAK,IAAI,oBAAoB,GAAG,YAAY,QAAQ,CAAC;CAC5E,SAAS,WAAW,aAAa,eAAe,MAAM;CACtD,OAAO;AACT;AACA,SAAgB,kBAA2B;CACzC,MACE,UADa,SAAS,cAAc,QAC1B,CAAA,CAAO,WAAW,QAAQ;CACtC,IAAI,YAAY,MAAM,OAAO;CAC7B,QAAQ,aAAa,oBAAoB,CAAC,EAAE,YAAY;CACxD,OAAO;AACT;;AAEA,SAAgB,qBACd,MACA,QACA,GAKY;CACZ,MAAM,QAAQ,IAAI,gBAAgB,GAChC,EAAE,WAAW;CACf,MAAM,SAAS,SAAS,cAAc,GAAG,GACvC,QAAQ,SAAS,cAAc,QAAQ;CACzC,OAAO,SAAS;CAChB,MAAM,SAAS;CACf,MAAM,YAAY;CAClB,MAAM,OAAO;CACb,MAAM,cAAc;CACpB,MAAM,gBAAsB;EAC1B,EAAE,QAAQ;CACZ;CACA,OAAO,iBACL,qBACC,MAAM;EACL,EAAE,eAAe;EACjB,EAAE,MAAM;EACR,IAAI,OAAO,SAAS;EACpB,OAAO,SAAS;EAChB,MAAM,SAAS;EACf,IAAI,CAAC,MAAM,aAAa,KAAK,OAAO,QAAQ,KAAK;EACjD,OAAO,cAAc;EACrB,EAAE,WAAW,OAAO,WAAW;CACjC,GACA,EAAE,OAAO,CACX;CACA,OAAO,iBAAiB,wBAAwB,SAAS,EAAE,OAAO,CAAC;CACnE,MAAM,iBACJ,UACC,UAAU;EACT,MAAM,gBAAgB;EACtB,QAAQ;CACV,GACA,EAAE,OAAO,CACX;CACA,aAAa;EACX,MAAM,MAAM;EACZ,OAAO,OAAO;EACd,MAAM,OAAO;CACf;AACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Quality = 'low' | 'medium' | 'high';
|
|
2
|
+
export type Graphics = 'auto' | Quality;
|
|
3
|
+
export declare const PIXEL_RATIO: Readonly<Record<Quality, number>>;
|
|
4
|
+
/** Measurement window and sustained-pressure timer use the caller's monotonic clock. */
|
|
5
|
+
export declare class AutoQuality {
|
|
6
|
+
#private;
|
|
7
|
+
get level(): Quality;
|
|
8
|
+
get p95(): number;
|
|
9
|
+
set(setting: Graphics): void;
|
|
10
|
+
sample(ms: number, now: number): boolean;
|
|
11
|
+
}
|
package/dist/quality.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/quality.ts
|
|
2
|
+
var PIXEL_RATIO = {
|
|
3
|
+
low: 1,
|
|
4
|
+
medium: 1.5,
|
|
5
|
+
high: 2
|
|
6
|
+
};
|
|
7
|
+
/** Measurement window and sustained-pressure timer use the caller's monotonic clock. */
|
|
8
|
+
var AutoQuality = class {
|
|
9
|
+
#samples = [];
|
|
10
|
+
#slowSince = null;
|
|
11
|
+
#level = "medium";
|
|
12
|
+
#setting = "auto";
|
|
13
|
+
get level() {
|
|
14
|
+
return this.#setting === "auto" ? this.#level : this.#setting;
|
|
15
|
+
}
|
|
16
|
+
get p95() {
|
|
17
|
+
const sorted = this.#samples.map((s) => s.ms).sort((a, b) => a - b);
|
|
18
|
+
return sorted[Math.max(0, Math.ceil(sorted.length * .95) - 1)] ?? 0;
|
|
19
|
+
}
|
|
20
|
+
set(setting) {
|
|
21
|
+
this.#setting = setting;
|
|
22
|
+
this.#slowSince = null;
|
|
23
|
+
}
|
|
24
|
+
sample(ms, now) {
|
|
25
|
+
this.#samples.push({
|
|
26
|
+
at: now,
|
|
27
|
+
ms
|
|
28
|
+
});
|
|
29
|
+
this.#samples = this.#samples.filter((s) => s.at >= now - 1e3);
|
|
30
|
+
if (this.#setting !== "auto" || this.p95 <= 16) {
|
|
31
|
+
this.#slowSince = null;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
this.#slowSince ??= now;
|
|
35
|
+
if (now - this.#slowSince < 3e3 || this.#level === "low") return false;
|
|
36
|
+
this.#level = this.#level === "high" ? "medium" : "low";
|
|
37
|
+
this.#slowSince = null;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { AutoQuality, PIXEL_RATIO };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=quality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.js","names":[],"sources":["../src/quality.ts"],"sourcesContent":["export type Quality = 'low' | 'medium' | 'high';\nexport type Graphics = 'auto' | Quality;\nexport const PIXEL_RATIO: Readonly<Record<Quality, number>> = { low: 1, medium: 1.5, high: 2 };\n/** Measurement window and sustained-pressure timer use the caller's monotonic clock. */\nexport class AutoQuality {\n #samples: {\n at: number;\n ms: number;\n }[] = [];\n #slowSince: number | null = null;\n #level: Quality = 'medium';\n #setting: Graphics = 'auto';\n get level(): Quality {\n return this.#setting === 'auto' ? this.#level : this.#setting;\n }\n get p95(): number {\n const sorted = this.#samples.map((s) => s.ms).sort((a, b) => a - b);\n return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)] ?? 0;\n }\n set(setting: Graphics): void {\n this.#setting = setting;\n this.#slowSince = null;\n }\n sample(ms: number, now: number): boolean {\n this.#samples.push({ at: now, ms });\n this.#samples = this.#samples.filter((s) => s.at >= now - 1000);\n if (this.#setting !== 'auto' || this.p95 <= 16) {\n this.#slowSince = null;\n return false;\n }\n this.#slowSince ??= now;\n if (now - this.#slowSince < 3000 || this.#level === 'low') return false;\n this.#level = this.#level === 'high' ? 'medium' : 'low';\n this.#slowSince = null;\n return true;\n }\n}\n"],"mappings":";AAEA,IAAa,cAAiD;CAAE,KAAK;CAAG,QAAQ;CAAK,MAAM;AAAE;;AAE7F,IAAa,cAAb,MAAyB;CACvB,WAGM,CAAC;CACP,aAA4B;CAC5B,SAAkB;CAClB,WAAqB;CACrB,IAAI,QAAiB;EACnB,OAAO,KAAK,aAAa,SAAS,KAAK,SAAS,KAAK;CACvD;CACA,IAAI,MAAc;EAChB,MAAM,SAAS,KAAK,SAAS,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;EAClE,OAAO,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,OAAO,SAAS,GAAI,IAAI,CAAC,MAAM;CACrE;CACA,IAAI,SAAyB;EAC3B,KAAK,WAAW;EAChB,KAAK,aAAa;CACpB;CACA,OAAO,IAAY,KAAsB;EACvC,KAAK,SAAS,KAAK;GAAE,IAAI;GAAK;EAAG,CAAC;EAClC,KAAK,WAAW,KAAK,SAAS,QAAQ,MAAM,EAAE,MAAM,MAAM,GAAI;EAC9D,IAAI,KAAK,aAAa,UAAU,KAAK,OAAO,IAAI;GAC9C,KAAK,aAAa;GAClB,OAAO;EACT;EACA,KAAK,eAAe;EACpB,IAAI,MAAM,KAAK,aAAa,OAAQ,KAAK,WAAW,OAAO,OAAO;EAClE,KAAK,SAAS,KAAK,WAAW,SAAS,WAAW;EAClD,KAAK,aAAa;EAClB,OAAO;CACT;AACF"}
|
package/dist/stage.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { type Graphics, type Quality } from './quality.js';
|
|
3
|
+
export type Disposable = {
|
|
4
|
+
dispose(): void;
|
|
5
|
+
};
|
|
6
|
+
export type SceneContext = {
|
|
7
|
+
readonly renderer: THREE.WebGLRenderer;
|
|
8
|
+
readonly scene: THREE.Scene;
|
|
9
|
+
track<T extends Disposable>(resource: T): T;
|
|
10
|
+
};
|
|
11
|
+
export type SceneDrawing<View> = {
|
|
12
|
+
readonly camera: THREE.Camera;
|
|
13
|
+
draw(view: View, alpha: number, quality: Quality): void;
|
|
14
|
+
resize?(width: number, height: number): void;
|
|
15
|
+
};
|
|
16
|
+
export type RenderOptions<View> = {
|
|
17
|
+
readonly host: HTMLElement;
|
|
18
|
+
readonly build: (context: SceneContext) => SceneDrawing<View>;
|
|
19
|
+
readonly pause: () => void;
|
|
20
|
+
readonly announce: (message: string) => void;
|
|
21
|
+
readonly development?: boolean;
|
|
22
|
+
readonly now?: () => number;
|
|
23
|
+
};
|
|
24
|
+
export declare function roomEnvironment({ renderer, scene, track }: SceneContext): void;
|
|
25
|
+
export declare function createRenderStage<View>(o: RenderOptions<View>): {
|
|
26
|
+
readonly canvas: HTMLCanvasElement | null;
|
|
27
|
+
readonly available: boolean;
|
|
28
|
+
readonly measurement: {
|
|
29
|
+
p95: number;
|
|
30
|
+
calls: number;
|
|
31
|
+
triangles: number;
|
|
32
|
+
quality: Quality;
|
|
33
|
+
};
|
|
34
|
+
draw(view: View, alpha?: number, key?: string): void;
|
|
35
|
+
graphics(value: Graphics): void;
|
|
36
|
+
retry: () => void;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
};
|
|
39
|
+
/** Camera-space pointer picking on a game's plane. */
|
|
40
|
+
export declare function pickPlane(canvas: HTMLCanvasElement, camera: THREE.Camera, x: number, y: number, plane?: THREE.Plane): THREE.Vector3 | null;
|
|
41
|
+
export declare function project(point: THREE.Vector3, camera: THREE.Camera): THREE.Vector3;
|
package/dist/stage.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { AutoQuality, PIXEL_RATIO } from "./quality.js";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
|
|
4
|
+
//#region src/stage.ts
|
|
5
|
+
function roomEnvironment({ renderer, scene, track }) {
|
|
6
|
+
const room = new RoomEnvironment(), pmrem = new THREE.PMREMGenerator(renderer);
|
|
7
|
+
scene.environment = track(pmrem.fromScene(room, .04)).texture;
|
|
8
|
+
room.dispose();
|
|
9
|
+
pmrem.dispose();
|
|
10
|
+
}
|
|
11
|
+
function createRenderStage(o) {
|
|
12
|
+
const quality = new AutoQuality(), now = o.now ?? (() => performance.now());
|
|
13
|
+
let renderer = null, drawing = null;
|
|
14
|
+
let scene = null, canvas = null;
|
|
15
|
+
let resources = /* @__PURE__ */ new Set(), events = null;
|
|
16
|
+
let last = null, drawn = "", disposed = false, lost = false;
|
|
17
|
+
let width = 1, height = 1;
|
|
18
|
+
const notice = document.createElement("p"), retry = document.createElement("button"), overlay = document.createElement("output");
|
|
19
|
+
retry.type = "button";
|
|
20
|
+
retry.textContent = "Try 3D again";
|
|
21
|
+
retry.hidden = true;
|
|
22
|
+
overlay.hidden = !o.development;
|
|
23
|
+
overlay.setAttribute("aria-label", "Rendering performance");
|
|
24
|
+
o.host.append(notice, retry, overlay);
|
|
25
|
+
const release = () => {
|
|
26
|
+
events?.abort();
|
|
27
|
+
events = null;
|
|
28
|
+
if (scene !== null) scene.traverse((object) => {
|
|
29
|
+
if (object instanceof THREE.InstancedMesh) resources.add(object);
|
|
30
|
+
const mesh = object;
|
|
31
|
+
if (mesh.geometry !== void 0) resources.add(mesh.geometry);
|
|
32
|
+
const materials = mesh.material === void 0 ? [] : Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
33
|
+
for (const material of materials) {
|
|
34
|
+
resources.add(material);
|
|
35
|
+
for (const value of Object.values(material)) if (value instanceof THREE.Texture) resources.add(value);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
for (const resource of resources) resource.dispose();
|
|
39
|
+
resources = /* @__PURE__ */ new Set();
|
|
40
|
+
renderer?.dispose();
|
|
41
|
+
renderer?.forceContextLoss();
|
|
42
|
+
renderer = null;
|
|
43
|
+
drawing = null;
|
|
44
|
+
scene = null;
|
|
45
|
+
canvas?.remove();
|
|
46
|
+
canvas = null;
|
|
47
|
+
};
|
|
48
|
+
const resize = () => {
|
|
49
|
+
width = Math.max(1, o.host.clientWidth);
|
|
50
|
+
height = Math.max(1, o.host.clientHeight || width * .7);
|
|
51
|
+
if (renderer !== null) renderer.shadowMap.enabled = quality.level === "high";
|
|
52
|
+
renderer?.setPixelRatio(Math.min(devicePixelRatio || 1, PIXEL_RATIO[quality.level]));
|
|
53
|
+
renderer?.setSize(width, height, false);
|
|
54
|
+
drawing?.resize?.(width, height);
|
|
55
|
+
drawn = "";
|
|
56
|
+
redraw();
|
|
57
|
+
};
|
|
58
|
+
const redraw = () => {
|
|
59
|
+
if (disposed || lost || renderer === null || drawing === null || scene === null || last === null) return;
|
|
60
|
+
const key = `${last.key}:${last.alpha}:${quality.level}:${width}:${height}`;
|
|
61
|
+
if (key === drawn) return;
|
|
62
|
+
const started = now();
|
|
63
|
+
drawing.draw(last.view, last.alpha, quality.level);
|
|
64
|
+
renderer.render(scene, drawing.camera);
|
|
65
|
+
if (quality.sample(now() - started, now())) {
|
|
66
|
+
renderer.shadowMap.enabled = quality.level === "high";
|
|
67
|
+
renderer.setPixelRatio(Math.min(devicePixelRatio || 1, PIXEL_RATIO[quality.level]));
|
|
68
|
+
renderer.setSize(width, height, false);
|
|
69
|
+
}
|
|
70
|
+
overlay.textContent = `p95 ${quality.p95.toFixed(1)} ms · ${renderer.info.render.calls} calls · ${renderer.info.render.triangles} triangles · ${quality.level}`;
|
|
71
|
+
drawn = key;
|
|
72
|
+
};
|
|
73
|
+
const rebuild = () => {
|
|
74
|
+
if (disposed) return;
|
|
75
|
+
release();
|
|
76
|
+
lost = false;
|
|
77
|
+
drawn = "";
|
|
78
|
+
try {
|
|
79
|
+
canvas = document.createElement("canvas");
|
|
80
|
+
const context = canvas.getContext("webgl2", {
|
|
81
|
+
antialias: true,
|
|
82
|
+
alpha: false,
|
|
83
|
+
powerPreference: "low-power"
|
|
84
|
+
});
|
|
85
|
+
if (context === null) throw new Error("WebGL 2 unavailable");
|
|
86
|
+
renderer = new THREE.WebGLRenderer({
|
|
87
|
+
canvas,
|
|
88
|
+
context,
|
|
89
|
+
antialias: true
|
|
90
|
+
});
|
|
91
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
92
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
93
|
+
canvas.setAttribute("aria-hidden", "true");
|
|
94
|
+
canvas.classList.add("render3d-canvas");
|
|
95
|
+
o.host.prepend(canvas);
|
|
96
|
+
scene = new THREE.Scene();
|
|
97
|
+
drawing = o.build({
|
|
98
|
+
renderer,
|
|
99
|
+
scene,
|
|
100
|
+
track: (resource) => {
|
|
101
|
+
resources.add(resource);
|
|
102
|
+
return resource;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
events = new AbortController();
|
|
106
|
+
canvas.addEventListener("webglcontextlost", (e) => {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
lost = true;
|
|
109
|
+
o.pause();
|
|
110
|
+
notice.textContent = "Graphics interrupted. Your game is paused.";
|
|
111
|
+
retry.hidden = false;
|
|
112
|
+
o.announce(notice.textContent);
|
|
113
|
+
}, { signal: events.signal });
|
|
114
|
+
canvas.addEventListener("webglcontextrestored", () => {
|
|
115
|
+
rebuild();
|
|
116
|
+
o.announce("Graphics restored. Resume when ready.");
|
|
117
|
+
}, { signal: events.signal });
|
|
118
|
+
notice.textContent = "";
|
|
119
|
+
retry.hidden = true;
|
|
120
|
+
resize();
|
|
121
|
+
} catch {
|
|
122
|
+
release();
|
|
123
|
+
lost = true;
|
|
124
|
+
o.pause();
|
|
125
|
+
notice.textContent = "This game needs WebGL 2. Enable hardware acceleration, then try again.";
|
|
126
|
+
retry.hidden = false;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
retry.addEventListener("click", rebuild);
|
|
130
|
+
const observer = new ResizeObserver(resize);
|
|
131
|
+
observer.observe(o.host);
|
|
132
|
+
rebuild();
|
|
133
|
+
return {
|
|
134
|
+
get canvas() {
|
|
135
|
+
return canvas;
|
|
136
|
+
},
|
|
137
|
+
get available() {
|
|
138
|
+
return renderer !== null && !lost;
|
|
139
|
+
},
|
|
140
|
+
get measurement() {
|
|
141
|
+
return {
|
|
142
|
+
p95: quality.p95,
|
|
143
|
+
calls: renderer?.info.render.calls ?? 0,
|
|
144
|
+
triangles: renderer?.info.render.triangles ?? 0,
|
|
145
|
+
quality: quality.level
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
draw(view, alpha = 1, key = JSON.stringify(view)) {
|
|
149
|
+
last = {
|
|
150
|
+
view,
|
|
151
|
+
alpha,
|
|
152
|
+
key
|
|
153
|
+
};
|
|
154
|
+
redraw();
|
|
155
|
+
},
|
|
156
|
+
graphics(value) {
|
|
157
|
+
quality.set(value);
|
|
158
|
+
resize();
|
|
159
|
+
},
|
|
160
|
+
retry: rebuild,
|
|
161
|
+
dispose() {
|
|
162
|
+
if (disposed) return;
|
|
163
|
+
disposed = true;
|
|
164
|
+
observer.disconnect();
|
|
165
|
+
release();
|
|
166
|
+
retry.removeEventListener("click", rebuild);
|
|
167
|
+
notice.remove();
|
|
168
|
+
retry.remove();
|
|
169
|
+
overlay.remove();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Camera-space pointer picking on a game's plane. */
|
|
174
|
+
function pickPlane(canvas, camera, x, y, plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)) {
|
|
175
|
+
const rect = canvas.getBoundingClientRect();
|
|
176
|
+
if (rect.width === 0 || rect.height === 0) return null;
|
|
177
|
+
const ray = new THREE.Raycaster();
|
|
178
|
+
ray.setFromCamera(new THREE.Vector2((x - rect.left) / rect.width * 2 - 1, 1 - (y - rect.top) / rect.height * 2), camera);
|
|
179
|
+
return ray.ray.intersectPlane(plane, new THREE.Vector3());
|
|
180
|
+
}
|
|
181
|
+
function project(point, camera) {
|
|
182
|
+
return point.clone().project(camera);
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
export { createRenderStage, pickPlane, project, roomEnvironment };
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=stage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage.js","names":[],"sources":["../src/stage.ts"],"sourcesContent":["import * as THREE from 'three';\nimport { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';\nimport { AutoQuality, PIXEL_RATIO, type Graphics, type Quality } from './quality.ts';\nexport type Disposable = {\n dispose(): void;\n};\nexport type SceneContext = {\n readonly renderer: THREE.WebGLRenderer;\n readonly scene: THREE.Scene;\n track<T extends Disposable>(resource: T): T;\n};\nexport type SceneDrawing<View> = {\n readonly camera: THREE.Camera;\n draw(view: View, alpha: number, quality: Quality): void;\n resize?(width: number, height: number): void;\n};\nexport type RenderOptions<View> = {\n readonly host: HTMLElement;\n readonly build: (context: SceneContext) => SceneDrawing<View>;\n readonly pause: () => void;\n readonly announce: (message: string) => void;\n readonly development?: boolean;\n readonly now?: () => number;\n};\nexport function roomEnvironment({ renderer, scene, track }: SceneContext): void {\n const room = new RoomEnvironment(),\n pmrem = new THREE.PMREMGenerator(renderer);\n const target = track(pmrem.fromScene(room, 0.04));\n scene.environment = target.texture;\n room.dispose();\n pmrem.dispose();\n}\nexport function createRenderStage<View>(o: RenderOptions<View>) {\n const quality = new AutoQuality(),\n now = o.now ?? (() => performance.now());\n let renderer: THREE.WebGLRenderer | null = null,\n drawing: SceneDrawing<View> | null = null;\n let scene: THREE.Scene | null = null,\n canvas: HTMLCanvasElement | null = null;\n let resources = new Set<Disposable>(),\n events: AbortController | null = null;\n let last: {\n view: View;\n alpha: number;\n key: string;\n } | null = null,\n drawn = '',\n disposed = false,\n lost = false;\n let width = 1,\n height = 1;\n const notice = document.createElement('p'),\n retry = document.createElement('button'),\n overlay = document.createElement('output');\n retry.type = 'button';\n retry.textContent = 'Try 3D again';\n retry.hidden = true;\n overlay.hidden = !o.development;\n overlay.setAttribute('aria-label', 'Rendering performance');\n o.host.append(notice, retry, overlay);\n const release = (): void => {\n events?.abort();\n events = null;\n if (scene !== null)\n scene.traverse((object) => {\n if (object instanceof THREE.InstancedMesh) resources.add(object);\n const mesh = object as THREE.Mesh;\n if (mesh.geometry !== undefined) resources.add(mesh.geometry);\n const materials =\n mesh.material === undefined ? [] : Array.isArray(mesh.material) ? mesh.material : [mesh.material];\n for (const material of materials) {\n resources.add(material);\n for (const value of Object.values(material)) if (value instanceof THREE.Texture) resources.add(value);\n }\n });\n for (const resource of resources) resource.dispose();\n resources = new Set();\n renderer?.dispose();\n renderer?.forceContextLoss();\n renderer = null;\n drawing = null;\n scene = null;\n canvas?.remove();\n canvas = null;\n };\n const resize = (): void => {\n width = Math.max(1, o.host.clientWidth);\n height = Math.max(1, o.host.clientHeight || width * 0.7);\n if (renderer !== null) renderer.shadowMap.enabled = quality.level === 'high';\n renderer?.setPixelRatio(Math.min(devicePixelRatio || 1, PIXEL_RATIO[quality.level]));\n renderer?.setSize(width, height, false);\n drawing?.resize?.(width, height);\n drawn = '';\n redraw();\n };\n const redraw = (): void => {\n if (disposed || lost || renderer === null || drawing === null || scene === null || last === null) return;\n const key = `${last.key}:${last.alpha}:${quality.level}:${width}:${height}`;\n if (key === drawn) return;\n const started = now();\n drawing.draw(last.view, last.alpha, quality.level);\n renderer.render(scene, drawing.camera);\n if (quality.sample(now() - started, now())) {\n renderer.shadowMap.enabled = quality.level === 'high';\n renderer.setPixelRatio(Math.min(devicePixelRatio || 1, PIXEL_RATIO[quality.level]));\n renderer.setSize(width, height, false);\n }\n overlay.textContent = `p95 ${quality.p95.toFixed(1)} ms · ${renderer.info.render.calls} calls · ${renderer.info.render.triangles} triangles · ${quality.level}`;\n drawn = key;\n };\n const rebuild = (): void => {\n if (disposed) return;\n release();\n lost = false;\n drawn = '';\n try {\n canvas = document.createElement('canvas');\n const context = canvas.getContext('webgl2', { antialias: true, alpha: false, powerPreference: 'low-power' });\n if (context === null) throw new Error('WebGL 2 unavailable');\n renderer = new THREE.WebGLRenderer({ canvas, context, antialias: true });\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.ACESFilmicToneMapping;\n canvas.setAttribute('aria-hidden', 'true');\n canvas.classList.add('render3d-canvas');\n o.host.prepend(canvas);\n scene = new THREE.Scene();\n drawing = o.build({\n renderer,\n scene,\n track: (resource) => {\n resources.add(resource);\n return resource;\n },\n });\n events = new AbortController();\n canvas.addEventListener(\n 'webglcontextlost',\n (e) => {\n e.preventDefault();\n lost = true;\n o.pause();\n notice.textContent = 'Graphics interrupted. Your game is paused.';\n retry.hidden = false;\n o.announce(notice.textContent);\n },\n { signal: events.signal },\n );\n canvas.addEventListener(\n 'webglcontextrestored',\n () => {\n rebuild();\n o.announce('Graphics restored. Resume when ready.');\n },\n { signal: events.signal },\n );\n notice.textContent = '';\n retry.hidden = true;\n resize();\n } catch {\n release();\n lost = true;\n o.pause();\n notice.textContent = 'This game needs WebGL 2. Enable hardware acceleration, then try again.';\n retry.hidden = false;\n }\n };\n retry.addEventListener('click', rebuild);\n const observer = new ResizeObserver(resize);\n observer.observe(o.host);\n rebuild();\n return {\n get canvas(): HTMLCanvasElement | null {\n return canvas;\n },\n get available(): boolean {\n return renderer !== null && !lost;\n },\n get measurement() {\n return {\n p95: quality.p95,\n calls: renderer?.info.render.calls ?? 0,\n triangles: renderer?.info.render.triangles ?? 0,\n quality: quality.level,\n };\n },\n draw(view: View, alpha = 1, key = JSON.stringify(view)): void {\n last = { view, alpha, key };\n redraw();\n },\n graphics(value: Graphics): void {\n quality.set(value);\n resize();\n },\n retry: rebuild,\n dispose(): void {\n if (disposed) return;\n disposed = true;\n observer.disconnect();\n release();\n retry.removeEventListener('click', rebuild);\n notice.remove();\n retry.remove();\n overlay.remove();\n },\n };\n}\n/** Camera-space pointer picking on a game's plane. */\nexport function pickPlane(\n canvas: HTMLCanvasElement,\n camera: THREE.Camera,\n x: number,\n y: number,\n plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0),\n): THREE.Vector3 | null {\n const rect = canvas.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return null;\n const ray = new THREE.Raycaster();\n ray.setFromCamera(\n new THREE.Vector2(((x - rect.left) / rect.width) * 2 - 1, 1 - ((y - rect.top) / rect.height) * 2),\n camera,\n );\n return ray.ray.intersectPlane(plane, new THREE.Vector3());\n}\nexport function project(point: THREE.Vector3, camera: THREE.Camera): THREE.Vector3 {\n return point.clone().project(camera);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,20 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joystyk/render3d",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared Three.js rendering, lifecycle and adaptive quality.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@types/three": "0.186.0",
|
|
16
|
+
"three": "0.186.1",
|
|
17
|
+
"@joystyk/contracts": "0.2.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "5.9.3",
|
|
21
|
+
"vite": "8.3.3",
|
|
22
|
+
"vitest": "4.1.11"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"NOTICE",
|
|
32
|
+
"CHANGELOG.md"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=24.15.0"
|
|
36
|
+
},
|
|
5
37
|
"license": "MIT",
|
|
6
38
|
"repository": {
|
|
7
39
|
"type": "git",
|
|
8
40
|
"url": "git+https://github.com/joystyk-games/game-libraries.git",
|
|
9
41
|
"directory": "packages/render3d"
|
|
10
42
|
},
|
|
11
|
-
"homepage": "https://github.com/joystyk-games/game-libraries",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"access": "public"
|
|
43
|
+
"homepage": "https://github.com/joystyk-games/game-libraries/tree/main/packages/render3d#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/joystyk-games/game-libraries/issues"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"build": "node ../../scripts/build-package.ts"
|
|
19
50
|
}
|
|
20
|
-
}
|
|
51
|
+
}
|