abstract-3d 3.1.2 → 4.0.0-beta.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/README.md +21 -1
- package/lib/renderers/dxf/dxf-encoding/dxf-build.d.ts.map +1 -1
- package/lib/renderers/dxf/dxf-encoding/dxf-build.js +1 -2
- package/lib/renderers/dxf/dxf-encoding/dxf-build.js.map +1 -1
- package/lib/renderers/ifc/ifc.test.d.ts +2 -0
- package/lib/renderers/ifc/ifc.test.d.ts.map +1 -0
- package/lib/renderers/ifc/ifc.test.js +380 -0
- package/lib/renderers/ifc/ifc.test.js.map +1 -0
- package/lib/renderers/index.d.ts +1 -0
- package/lib/renderers/index.d.ts.map +1 -1
- package/lib/renderers/index.js +1 -0
- package/lib/renderers/index.js.map +1 -1
- package/lib/renderers/react/index.d.ts +2 -5
- package/lib/renderers/react/index.d.ts.map +1 -1
- package/lib/renderers/react/index.js +1 -2
- package/lib/renderers/react/index.js.map +1 -1
- package/lib/renderers/react/react.d.ts +16 -42
- package/lib/renderers/react/react.d.ts.map +1 -1
- package/lib/renderers/react/react.js +49 -32
- package/lib/renderers/react/react.js.map +1 -1
- package/lib/renderers/three/appearance/appearance.d.ts +41 -0
- package/lib/renderers/three/appearance/appearance.d.ts.map +1 -0
- package/lib/renderers/three/appearance/appearance.js +89 -0
- package/lib/renderers/three/appearance/appearance.js.map +1 -0
- package/lib/renderers/three/appearance/appearance.test.d.ts +2 -0
- package/lib/renderers/three/appearance/appearance.test.d.ts.map +1 -0
- package/lib/renderers/three/appearance/appearance.test.js +97 -0
- package/lib/renderers/three/appearance/appearance.test.js.map +1 -0
- package/lib/renderers/three/appearance.d.ts +41 -0
- package/lib/renderers/three/appearance.d.ts.map +1 -0
- package/lib/renderers/three/appearance.js +100 -0
- package/lib/renderers/three/appearance.js.map +1 -0
- package/lib/renderers/three/build/build.d.ts +51 -0
- package/lib/renderers/three/build/build.d.ts.map +1 -0
- package/lib/renderers/three/build/build.js +174 -0
- package/lib/renderers/three/build/build.js.map +1 -0
- package/lib/renderers/three/build/build.test.d.ts +2 -0
- package/lib/renderers/three/build/build.test.d.ts.map +1 -0
- package/lib/renderers/three/build/build.test.js +120 -0
- package/lib/renderers/three/build/build.test.js.map +1 -0
- package/lib/renderers/three/build/materials.d.ts +12 -0
- package/lib/renderers/three/build/materials.d.ts.map +1 -0
- package/lib/renderers/three/build/materials.js +55 -0
- package/lib/renderers/three/build/materials.js.map +1 -0
- package/lib/renderers/three/build/parts.d.ts +65 -0
- package/lib/renderers/three/build/parts.d.ts.map +1 -0
- package/lib/renderers/three/build/parts.js +203 -0
- package/lib/renderers/three/build/parts.js.map +1 -0
- package/lib/renderers/three/build.d.ts +50 -0
- package/lib/renderers/three/build.d.ts.map +1 -0
- package/lib/renderers/three/build.js +210 -0
- package/lib/renderers/three/build.js.map +1 -0
- package/lib/renderers/three/camera/camera.d.ts +40 -0
- package/lib/renderers/three/camera/camera.d.ts.map +1 -0
- package/lib/renderers/three/camera/camera.js +51 -0
- package/lib/renderers/three/camera/camera.js.map +1 -0
- package/lib/renderers/three/camera/camera.test.d.ts +2 -0
- package/lib/renderers/three/camera/camera.test.d.ts.map +1 -0
- package/lib/renderers/three/camera/camera.test.js +46 -0
- package/lib/renderers/three/camera/camera.test.js.map +1 -0
- package/lib/renderers/three/camera/controls.d.ts +24 -0
- package/lib/renderers/three/camera/controls.d.ts.map +1 -0
- package/lib/renderers/three/camera/controls.js +39 -0
- package/lib/renderers/three/camera/controls.js.map +1 -0
- package/lib/renderers/three/camera/gizmo.d.ts +44 -0
- package/lib/renderers/three/camera/gizmo.d.ts.map +1 -0
- package/lib/renderers/three/camera/gizmo.js +42 -0
- package/lib/renderers/three/camera/gizmo.js.map +1 -0
- package/lib/renderers/three/camera.d.ts +63 -0
- package/lib/renderers/three/camera.d.ts.map +1 -0
- package/lib/renderers/three/camera.js +85 -0
- package/lib/renderers/three/camera.js.map +1 -0
- package/lib/renderers/three/compile/compile.d.ts +59 -0
- package/lib/renderers/three/compile/compile.d.ts.map +1 -0
- package/lib/renderers/three/compile/compile.js +88 -0
- package/lib/renderers/three/compile/compile.js.map +1 -0
- package/lib/renderers/three/compile/compile.test.d.ts +2 -0
- package/lib/renderers/three/compile/compile.test.d.ts.map +1 -0
- package/lib/renderers/three/compile/compile.test.js +93 -0
- package/lib/renderers/three/compile/compile.test.js.map +1 -0
- package/lib/renderers/three/compile.d.ts +58 -0
- package/lib/renderers/three/compile.d.ts.map +1 -0
- package/lib/renderers/three/compile.js +89 -0
- package/lib/renderers/three/compile.js.map +1 -0
- package/lib/renderers/three/controls.d.ts +27 -0
- package/lib/renderers/three/controls.d.ts.map +1 -0
- package/lib/renderers/three/controls.js +115 -0
- package/lib/renderers/three/controls.js.map +1 -0
- package/lib/renderers/three/geometry/geometry.d.ts +21 -0
- package/lib/renderers/three/geometry/geometry.d.ts.map +1 -0
- package/lib/renderers/three/geometry/geometry.js +132 -0
- package/lib/renderers/three/geometry/geometry.js.map +1 -0
- package/lib/renderers/three/geometry/geometry.test.d.ts +2 -0
- package/lib/renderers/three/geometry/geometry.test.d.ts.map +1 -0
- package/lib/renderers/three/geometry/geometry.test.js +80 -0
- package/lib/renderers/three/geometry/geometry.test.js.map +1 -0
- package/lib/renderers/three/geometry.d.ts +20 -0
- package/lib/renderers/three/geometry.d.ts.map +1 -0
- package/lib/renderers/three/geometry.js +164 -0
- package/lib/renderers/three/geometry.js.map +1 -0
- package/lib/renderers/three/gizmo.d.ts +49 -0
- package/lib/renderers/three/gizmo.d.ts.map +1 -0
- package/lib/renderers/three/gizmo.js +172 -0
- package/lib/renderers/three/gizmo.js.map +1 -0
- package/lib/renderers/three/index.d.ts +16 -0
- package/lib/renderers/three/index.d.ts.map +1 -0
- package/lib/renderers/three/index.js +6 -0
- package/lib/renderers/three/index.js.map +1 -0
- package/lib/renderers/three/input.d.ts +25 -0
- package/lib/renderers/three/input.d.ts.map +1 -0
- package/lib/renderers/three/input.js +71 -0
- package/lib/renderers/three/input.js.map +1 -0
- package/lib/renderers/three/lines.d.ts +16 -0
- package/lib/renderers/three/lines.d.ts.map +1 -0
- package/lib/renderers/three/lines.js +63 -0
- package/lib/renderers/three/lines.js.map +1 -0
- package/lib/renderers/three/materials.d.ts +15 -0
- package/lib/renderers/three/materials.d.ts.map +1 -0
- package/lib/renderers/three/materials.js +70 -0
- package/lib/renderers/three/materials.js.map +1 -0
- package/lib/renderers/three/options.d.ts +68 -0
- package/lib/renderers/three/options.d.ts.map +1 -0
- package/lib/renderers/three/options.js +64 -0
- package/lib/renderers/three/options.js.map +1 -0
- package/lib/renderers/three/overlays.d.ts +25 -0
- package/lib/renderers/three/overlays.d.ts.map +1 -0
- package/lib/renderers/three/overlays.js +57 -0
- package/lib/renderers/three/overlays.js.map +1 -0
- package/lib/renderers/three/quality/quality.d.ts +37 -0
- package/lib/renderers/three/quality/quality.d.ts.map +1 -0
- package/lib/renderers/three/quality/quality.js +64 -0
- package/lib/renderers/three/quality/quality.js.map +1 -0
- package/lib/renderers/three/quality/quality.test.d.ts +2 -0
- package/lib/renderers/three/quality/quality.test.d.ts.map +1 -0
- package/lib/renderers/three/quality/quality.test.js +23 -0
- package/lib/renderers/three/quality/quality.test.js.map +1 -0
- package/lib/renderers/three/quality.d.ts +24 -0
- package/lib/renderers/three/quality.d.ts.map +1 -0
- package/lib/renderers/three/quality.js +67 -0
- package/lib/renderers/three/quality.js.map +1 -0
- package/lib/renderers/three/text/text.d.ts +55 -0
- package/lib/renderers/three/text/text.d.ts.map +1 -0
- package/lib/renderers/three/text/text.js +90 -0
- package/lib/renderers/three/text/text.js.map +1 -0
- package/lib/renderers/three/text/text.test.d.ts +2 -0
- package/lib/renderers/three/text/text.test.d.ts.map +1 -0
- package/lib/renderers/three/text/text.test.js +70 -0
- package/lib/renderers/three/text/text.test.js.map +1 -0
- package/lib/renderers/three/text.d.ts +53 -0
- package/lib/renderers/three/text.d.ts.map +1 -0
- package/lib/renderers/three/text.js +151 -0
- package/lib/renderers/three/text.js.map +1 -0
- package/lib/renderers/three/viewer/options.d.ts +71 -0
- package/lib/renderers/three/viewer/options.d.ts.map +1 -0
- package/lib/renderers/three/viewer/options.js +31 -0
- package/lib/renderers/three/viewer/options.js.map +1 -0
- package/lib/renderers/three/viewer/options.test.d.ts +2 -0
- package/lib/renderers/three/viewer/options.test.d.ts.map +1 -0
- package/lib/renderers/three/viewer/options.test.js +38 -0
- package/lib/renderers/three/viewer/options.test.js.map +1 -0
- package/lib/renderers/three/viewer/overlays.d.ts +31 -0
- package/lib/renderers/three/viewer/overlays.d.ts.map +1 -0
- package/lib/renderers/three/viewer/overlays.js +43 -0
- package/lib/renderers/three/viewer/overlays.js.map +1 -0
- package/lib/renderers/three/viewer/viewer.d.ts +90 -0
- package/lib/renderers/three/viewer/viewer.d.ts.map +1 -0
- package/lib/renderers/three/viewer/viewer.js +432 -0
- package/lib/renderers/three/viewer/viewer.js.map +1 -0
- package/lib/renderers/three/viewer.d.ts +38 -0
- package/lib/renderers/three/viewer.d.ts.map +1 -0
- package/lib/renderers/three/viewer.js +435 -0
- package/lib/renderers/three/viewer.js.map +1 -0
- package/package.json +36 -16
- package/src/custom_typings/n8ao.d.ts +11 -0
- package/src/renderers/dxf/dxf-encoding/dxf-build.ts +1 -2
- package/src/renderers/index.ts +1 -0
- package/src/renderers/react/index.ts +2 -5
- package/src/renderers/react/react.tsx +96 -181
- package/src/renderers/three/appearance/appearance.test.ts +116 -0
- package/src/renderers/three/appearance/appearance.ts +132 -0
- package/src/renderers/three/build/build.test.ts +141 -0
- package/src/renderers/three/build/build.ts +270 -0
- package/src/renderers/three/build/materials.ts +85 -0
- package/src/renderers/three/build/parts.ts +277 -0
- package/src/renderers/three/camera/camera.test.ts +53 -0
- package/src/renderers/three/camera/camera.ts +82 -0
- package/src/renderers/three/camera/controls.ts +65 -0
- package/src/renderers/three/camera/gizmo.ts +93 -0
- package/src/renderers/three/compile/compile.test.ts +113 -0
- package/src/renderers/three/compile/compile.ts +151 -0
- package/src/renderers/three/geometry/geometry.test.ts +95 -0
- package/src/renderers/three/geometry/geometry.ts +179 -0
- package/src/renderers/three/index.ts +15 -0
- package/src/renderers/three/quality/quality.test.ts +30 -0
- package/src/renderers/three/quality/quality.ts +95 -0
- package/src/renderers/three/text/text.test.ts +79 -0
- package/src/renderers/three/text/text.ts +128 -0
- package/src/renderers/three/viewer/options.ts +98 -0
- package/src/renderers/three/viewer/overlays.ts +71 -0
- package/src/renderers/three/viewer/viewer.ts +540 -0
- package/lib/renderers/react/react-hotspot.d.ts +0 -27
- package/lib/renderers/react/react-hotspot.d.ts.map +0 -1
- package/lib/renderers/react/react-hotspot.js +0 -37
- package/lib/renderers/react/react-hotspot.js.map +0 -1
- package/src/custom_typings/react-three-fiber.d.ts +0 -9
- package/src/custom_typings/react-three-postprocessing.d.ts +0 -1
- package/src/renderers/react/react-camera.tsx +0 -391
- package/src/renderers/react/react-dimension.tsx +0 -122
- package/src/renderers/react/react-group.test.ts +0 -56
- package/src/renderers/react/react-group.tsx +0 -202
- package/src/renderers/react/react-image-material.tsx +0 -139
- package/src/renderers/react/react-material.tsx +0 -91
- package/src/renderers/react/react-mesh.tsx +0 -512
- package/src/renderers/react/react-relation.tsx +0 -86
- package/src/renderers/react/react-scene.tsx +0 -111
- package/src/renderers/react/react-types.ts +0 -9
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { N8AOPass } from "n8ao";
|
|
2
|
+
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
|
3
|
+
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
|
|
4
|
+
const pixelRatioCaps = { low: 1, medium: 1.5, high: 2 };
|
|
5
|
+
const windowSize = 30;
|
|
6
|
+
const slowFrameMs = 25;
|
|
7
|
+
/** Longer intervals are pauses between moves, not slow frames */
|
|
8
|
+
const pauseMs = 250;
|
|
9
|
+
const fadeFrames = 6;
|
|
10
|
+
const intensity = 2;
|
|
11
|
+
/** Records the time between two frames while the camera moves; true when that dropped the tier */
|
|
12
|
+
export function recordFrame(governor, intervalMs) {
|
|
13
|
+
if (governor.quality !== "auto" || governor.tier === "low" || intervalMs > pauseMs) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
governor.sum += intervalMs;
|
|
17
|
+
governor.count += 1;
|
|
18
|
+
if (governor.count < windowSize) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const slow = governor.sum / governor.count > slowFrameMs;
|
|
22
|
+
Object.assign(governor, { sum: 0, count: 0 }, slow ? { tier: "low" } : {});
|
|
23
|
+
return slow;
|
|
24
|
+
}
|
|
25
|
+
/** Also sets the renderer's pixel ratio, capped by the tier */
|
|
26
|
+
export function createRenderPath(renderer, scene, camera, tier) {
|
|
27
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, pixelRatioCaps[tier]));
|
|
28
|
+
if (tier === "low") {
|
|
29
|
+
return { tier, composer: undefined, ao: undefined, fade: { frame: 0 } };
|
|
30
|
+
}
|
|
31
|
+
const composer = new EffectComposer(renderer);
|
|
32
|
+
const ao = new N8AOPass(scene, camera);
|
|
33
|
+
Object.assign(ao.configuration, {
|
|
34
|
+
aoRadius: 40,
|
|
35
|
+
distanceFalloff: 0.2,
|
|
36
|
+
screenSpaceRadius: true,
|
|
37
|
+
halfRes: true,
|
|
38
|
+
denoiseSamples: 4,
|
|
39
|
+
gammaCorrection: false,
|
|
40
|
+
});
|
|
41
|
+
// Multisampled, as SMAA, which N8AO suggests, would lose the antialiased alpha at edges over the transparent background
|
|
42
|
+
ao.beautyRenderTarget.samples = 4;
|
|
43
|
+
composer.addPass(ao);
|
|
44
|
+
// Tone mapping and sRGB, which rendering to the screen otherwise does
|
|
45
|
+
composer.addPass(new OutputPass());
|
|
46
|
+
return { tier, composer, ao, fade: { frame: 0 } };
|
|
47
|
+
}
|
|
48
|
+
/** Renders a frame; true while the frame at rest needs more frames to fade its ambient occlusion in */
|
|
49
|
+
export function renderPath(path, renderer, scene, camera, moving) {
|
|
50
|
+
if (!path.composer || !path.ao || (moving && path.tier === "medium")) {
|
|
51
|
+
path.fade.frame = 0;
|
|
52
|
+
renderer.render(scene, camera);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
path.fade.frame = path.tier === "high" ? fadeFrames : Math.min(fadeFrames, path.fade.frame + 1);
|
|
56
|
+
path.ao.configuration.intensity = (intensity * path.fade.frame) / fadeFrames;
|
|
57
|
+
path.composer.render();
|
|
58
|
+
return path.fade.frame < fadeFrames;
|
|
59
|
+
}
|
|
60
|
+
export function disposeRenderPath(path) {
|
|
61
|
+
path.composer?.passes.forEach((p) => p.dispose());
|
|
62
|
+
path.composer?.dispose();
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=quality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../../src/renderers/three/quality/quality.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AAsBvE,MAAM,cAAc,GAAyB,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC9E,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,iEAAiE;AACjE,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,UAAU,GAAG,CAAC,CAAC;AACrB,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,kGAAkG;AAClG,MAAM,UAAU,WAAW,CAAC,QAAsB,EAAE,UAAkB;IACpE,IAAI,QAAQ,CAAC,OAAO,KAAK,MAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,IAAI,UAAU,GAAG,OAAO,EAAE,CAAC;QACnF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,QAAQ,CAAC,GAAG,IAAI,UAAU,CAAC;IAC3B,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;IACpB,IAAI,QAAQ,CAAC,KAAK,GAAG,UAAU,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC,KAAK,GAAG,WAAW,CAAC;IACzD,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,QAAuB,EAAE,KAAY,EAAE,MAA8C,EAAE,IAAU;IAChI,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChF,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;IAC1E,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACvC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE;QAC9B,QAAQ,EAAE,EAAE;QACZ,eAAe,EAAE,GAAG;QACpB,iBAAiB,EAAE,IAAI;QACvB,OAAO,EAAE,IAAI;QACb,cAAc,EAAE,CAAC;QACjB,eAAe,EAAE,KAAK;KACvB,CAAC,CAAC;IACH,wHAAwH;IACxH,EAAE,CAAC,kBAAkB,CAAC,OAAO,GAAG,CAAC,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACrB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC;IACnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;AACpD,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,UAAU,CACxB,IAAgB,EAChB,QAAuB,EACvB,KAAY,EACZ,MAA8C,EAC9C,MAAe;IAEf,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;QACrE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACpB,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAChG,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC;IAC7E,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;IACvB,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAgB;IAChD,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.test.d.ts","sourceRoot":"","sources":["../../../../src/renderers/three/quality/quality.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { recordFrame } from "./quality.js";
|
|
3
|
+
const governor = (quality) => ({ quality, tier: quality === "auto" ? "medium" : quality, sum: 0, count: 0 });
|
|
4
|
+
const record = (g, intervals) => intervals.map((ms) => recordFrame(g, ms));
|
|
5
|
+
describe("recordFrame", () => {
|
|
6
|
+
it("keeps a fixed quality", () => {
|
|
7
|
+
const g = governor("high");
|
|
8
|
+
record(g, Array(100).fill(100));
|
|
9
|
+
expect(g.tier).toBe("high");
|
|
10
|
+
});
|
|
11
|
+
it("drops auto to low for good when moving frames are slow", () => {
|
|
12
|
+
const g = governor("auto");
|
|
13
|
+
expect(record(g, Array(30).fill(40)).filter(Boolean)).toHaveLength(1);
|
|
14
|
+
record(g, Array(100).fill(5));
|
|
15
|
+
expect(g.tier).toBe("low");
|
|
16
|
+
});
|
|
17
|
+
it("keeps auto at medium at 60 frames per second, ignoring the pauses between moves", () => {
|
|
18
|
+
const g = governor("auto");
|
|
19
|
+
record(g, Array.from({ length: 100 }, (_, i) => (i % 10 === 0 ? 2000 : 16.7)));
|
|
20
|
+
expect(g.tier).toBe("medium");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=quality.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.test.js","sourceRoot":"","sources":["../../../../src/renderers/three/quality/quality.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,QAAQ,GAAG,CAAC,OAAgB,EAAgB,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AACpI,MAAM,MAAM,GAAG,CAAC,CAAe,EAAE,SAAgC,EAA0B,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAExI,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,CAAC,CAAC,EAAE,KAAK,CAAS,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAS,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC9E,MAAM,CAAC,CAAC,EAAE,KAAK,CAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iFAAiF,EAAE,GAAG,EAAE;QACzF,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,CACJ,CAAC,EACD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CACpE,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { OrthographicCamera, PerspectiveCamera, Scene } from "three";
|
|
2
|
+
import { type WebGPURenderer } from "three/webgpu";
|
|
3
|
+
/**
|
|
4
|
+
* Quality tiers that spend the GPU where it shows: the camera is still most of the time, and a still frame can afford ambient occlusion that a moving
|
|
5
|
+
* one can't on a weak GPU
|
|
6
|
+
*/
|
|
7
|
+
export type Tier = "low" | "medium" | "high";
|
|
8
|
+
export type Quality = Tier | "auto";
|
|
9
|
+
export declare const getPixelRatio: (tier: Tier, devicePixelRatio: number) => number;
|
|
10
|
+
export type TierGovernor = {
|
|
11
|
+
readonly tier: () => Tier;
|
|
12
|
+
/** Records the time between two frames while the camera moves; returns the new tier when it changes */
|
|
13
|
+
readonly record: (intervalMs: number) => Tier | undefined;
|
|
14
|
+
};
|
|
15
|
+
/** `auto` starts at medium, and drops to low for good once moving frames average slower than 40 per second */
|
|
16
|
+
export declare function createTierGovernor(quality: Quality): TierGovernor;
|
|
17
|
+
export type RenderPath = {
|
|
18
|
+
/** Renders a frame; true while the frame at rest needs more frames to fade its ambient occlusion in */
|
|
19
|
+
readonly render: (moving: boolean) => boolean;
|
|
20
|
+
readonly dispose: () => void;
|
|
21
|
+
};
|
|
22
|
+
/** Low renders directly; medium adds ambient occlusion at rest, faded in; high has it while moving too */
|
|
23
|
+
export declare function createRenderPath(renderer: WebGPURenderer, scene: Scene, camera: PerspectiveCamera | OrthographicCamera, tier: Tier, aoRadius: number): RenderPath;
|
|
24
|
+
//# sourceMappingURL=quality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.d.ts","sourceRoot":"","sources":["../../../src/renderers/three/quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAG1E,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9E;;;GAGG;AAEH,MAAM,MAAM,IAAI,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC7C,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC;AAIpC,eAAO,MAAM,aAAa,SAAU,IAAI,oBAAoB,MAAM,KAAG,MAA0D,CAAC;AAEhI,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAC1B,uGAAuG;IACvG,QAAQ,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC;CAC3D,CAAC;AAOF,8GAA8G;AAC9G,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,CAkBjE;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,uGAAuG;IACvG,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAIF,0GAA0G;AAC1G,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,iBAAiB,GAAG,kBAAkB,EAC9C,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,GACf,UAAU,CAkCZ"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ao } from "three/addons/tsl/display/GTAONode.js";
|
|
2
|
+
import { float, mix, pass, screenUV, uniform, vec4 } from "three/tsl";
|
|
3
|
+
import { RenderPipeline } from "three/webgpu";
|
|
4
|
+
const pixelRatioCaps = { low: 1, medium: 1.5, high: 2 };
|
|
5
|
+
export const getPixelRatio = (tier, devicePixelRatio) => Math.min(devicePixelRatio, pixelRatioCaps[tier]);
|
|
6
|
+
const windowSize = 30;
|
|
7
|
+
const slowFrameMs = 25;
|
|
8
|
+
/** Longer intervals are pauses between moves, not slow frames */
|
|
9
|
+
const pauseMs = 250;
|
|
10
|
+
/** `auto` starts at medium, and drops to low for good once moving frames average slower than 40 per second */
|
|
11
|
+
export function createTierGovernor(quality) {
|
|
12
|
+
const state = { tier: quality === "auto" ? "medium" : quality, sum: 0, count: 0 };
|
|
13
|
+
return {
|
|
14
|
+
tier: () => state.tier,
|
|
15
|
+
record: (intervalMs) => {
|
|
16
|
+
if (quality !== "auto" || state.tier === "low" || intervalMs > pauseMs) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
state.sum += intervalMs;
|
|
20
|
+
state.count += 1;
|
|
21
|
+
if (state.count < windowSize) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const slow = state.sum / state.count > slowFrameMs;
|
|
25
|
+
Object.assign(state, { sum: 0, count: 0 }, slow ? { tier: "low" } : {});
|
|
26
|
+
return slow ? state.tier : undefined;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const fadeFrames = 6;
|
|
31
|
+
/** Low renders directly; medium adds ambient occlusion at rest, faded in; high has it while moving too */
|
|
32
|
+
export function createRenderPath(renderer, scene, camera, tier, aoRadius) {
|
|
33
|
+
if (tier === "low") {
|
|
34
|
+
return { render: () => (renderer.render(scene, camera), false), dispose: () => undefined };
|
|
35
|
+
}
|
|
36
|
+
const pipeline = new RenderPipeline(renderer);
|
|
37
|
+
// Multisampled, as SMAA would lose the antialiased alpha at edges over the transparent background
|
|
38
|
+
const scenePass = pass(scene, camera, { samples: 4 });
|
|
39
|
+
const color = scenePass.getTextureNode("output");
|
|
40
|
+
// Normals are reconstructed from depth, so there's no extra normal pass; the typings miss that the normal node is optional
|
|
41
|
+
const aoPass = ao(scenePass.getTextureNode("depth"), null, camera);
|
|
42
|
+
aoPass.resolutionScale = 0.5;
|
|
43
|
+
aoPass.radius.value = aoRadius;
|
|
44
|
+
const strength = uniform(0);
|
|
45
|
+
const occlusion = mix(float(1), aoPass.getTextureNode().sample(screenUV).r, strength);
|
|
46
|
+
pipeline.outputNode = vec4(color.rgb.mul(occlusion), color.a);
|
|
47
|
+
const fade = { frame: 0 };
|
|
48
|
+
return {
|
|
49
|
+
render: (moving) => {
|
|
50
|
+
if (moving && tier === "medium") {
|
|
51
|
+
fade.frame = 0;
|
|
52
|
+
renderer.render(scene, camera);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
fade.frame = tier === "high" ? fadeFrames : Math.min(fadeFrames, fade.frame + 1);
|
|
56
|
+
strength.value = fade.frame / fadeFrames;
|
|
57
|
+
pipeline.render();
|
|
58
|
+
return fade.frame < fadeFrames;
|
|
59
|
+
},
|
|
60
|
+
dispose: () => {
|
|
61
|
+
aoPass.dispose();
|
|
62
|
+
scenePass.dispose();
|
|
63
|
+
pipeline.dispose();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=quality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../src/renderers/three/quality.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import TinySDF from "@mapbox/tiny-sdf";
|
|
2
|
+
import { DataTexture } from "three";
|
|
3
|
+
/**
|
|
4
|
+
* Text as signed distance field glyphs from the system font, packed into one atlas texture, so all text draws in one call. The glyph atlas is Mapbox
|
|
5
|
+
* GL JS's approach (its @mapbox/tiny-sdf rasterizes), and the distance field text Valve's (Green, "Improved Alpha-Tested Magnification for Vector
|
|
6
|
+
* Textures and Special Effects", SIGGRAPH 2007)
|
|
7
|
+
*/
|
|
8
|
+
export declare const RASTER_SIZE = 40;
|
|
9
|
+
export declare const SDF_BUFFER = 4;
|
|
10
|
+
/** The distance field's value at the glyph's edge, from tiny-sdf's default cutoff of 0.25 */
|
|
11
|
+
export declare const SDF_EDGE = 0.75;
|
|
12
|
+
export type GlyphBitmap = ReturnType<TinySDF["draw"]>;
|
|
13
|
+
export type Rasterize = (char: string) => GlyphBitmap;
|
|
14
|
+
export type Glyph = {
|
|
15
|
+
/** The glyph's place in the atlas as a uv offset and scale, for a quad with uv (0, 0) at its bottom left */
|
|
16
|
+
readonly rect: readonly [number, number, number, number];
|
|
17
|
+
/** The bitmap's left and top edges and size, in em, from the pen at the baseline; no width for glyphs without ink */
|
|
18
|
+
readonly left: number;
|
|
19
|
+
readonly top: number;
|
|
20
|
+
readonly width: number;
|
|
21
|
+
readonly height: number;
|
|
22
|
+
readonly advance: number;
|
|
23
|
+
};
|
|
24
|
+
export type GlyphAtlas = {
|
|
25
|
+
readonly texture: DataTexture;
|
|
26
|
+
readonly size: number;
|
|
27
|
+
readonly glyphs: Map<string, Glyph>;
|
|
28
|
+
/** Where the next glyph goes: the current row's top and next x, and its height */
|
|
29
|
+
readonly shelf: {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
height: number;
|
|
33
|
+
};
|
|
34
|
+
readonly rasterize: Rasterize | undefined;
|
|
35
|
+
readonly sdf: {
|
|
36
|
+
current?: TinySDF;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
/** A quad of a laid out text, with its center and size in the text's units, centered on the origin */
|
|
40
|
+
export type GlyphQuad = {
|
|
41
|
+
readonly glyph: Glyph;
|
|
42
|
+
readonly x: number;
|
|
43
|
+
readonly y: number;
|
|
44
|
+
readonly width: number;
|
|
45
|
+
readonly height: number;
|
|
46
|
+
};
|
|
47
|
+
/** Without `rasterize`, glyphs are rasterized with the OS's built-in Helvetica or Arial, on a canvas created at first use */
|
|
48
|
+
export declare function createGlyphAtlas(rasterize?: Rasterize, size?: number): GlyphAtlas;
|
|
49
|
+
export declare function getGlyph(atlas: GlyphAtlas, char: string): Glyph;
|
|
50
|
+
/**
|
|
51
|
+
* Lays out the text centered on the origin, each line centered, as drei's Text (troika-three-text) did with the r3f renderer's line height and
|
|
52
|
+
* spacing
|
|
53
|
+
*/
|
|
54
|
+
export declare function layoutText(atlas: GlyphAtlas, text: string, fontSize: number): ReadonlyArray<GlyphQuad>;
|
|
55
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../../src/renderers/three/text/text.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,WAAW,EAA6C,MAAM,OAAO,CAAC;AAE/E;;;;GAIG;AAEH,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,OAAO,CAAC;AAM7B,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AACtD,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,WAAW,CAAC;AAEtD,MAAM,MAAM,KAAK,GAAG;IAClB,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACzD,qHAAqH;IACrH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpC,kFAAkF;IAClF,QAAQ,CAAC,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACrC,CAAC;AAEF,sGAAsG;AACtG,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,6HAA6H;AAC7H,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,SAAO,GAAG,UAAU,CAI/E;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAoB/D;AAqBD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAwBtG"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import TinySDF from "@mapbox/tiny-sdf";
|
|
2
|
+
import { DataTexture, LinearFilter, RedFormat, UnsignedByteType } from "three";
|
|
3
|
+
/**
|
|
4
|
+
* Text as signed distance field glyphs from the system font, packed into one atlas texture, so all text draws in one call. The glyph atlas is Mapbox
|
|
5
|
+
* GL JS's approach (its @mapbox/tiny-sdf rasterizes), and the distance field text Valve's (Green, "Improved Alpha-Tested Magnification for Vector
|
|
6
|
+
* Textures and Special Effects", SIGGRAPH 2007)
|
|
7
|
+
*/
|
|
8
|
+
export const RASTER_SIZE = 40;
|
|
9
|
+
export const SDF_BUFFER = 4;
|
|
10
|
+
/** The distance field's value at the glyph's edge, from tiny-sdf's default cutoff of 0.25 */
|
|
11
|
+
export const SDF_EDGE = 0.75;
|
|
12
|
+
const LINE_HEIGHT = 1.5;
|
|
13
|
+
const LETTER_SPACING = 0.001;
|
|
14
|
+
/** Of the font size, above the baseline, to center a line's glyphs in it */
|
|
15
|
+
const ASCENT = 0.8;
|
|
16
|
+
/** Without `rasterize`, glyphs are rasterized with the OS's built-in Helvetica or Arial, on a canvas created at first use */
|
|
17
|
+
export function createGlyphAtlas(rasterize, size = 1024) {
|
|
18
|
+
const texture = new DataTexture(new Uint8Array(size * size), size, size, RedFormat, UnsignedByteType);
|
|
19
|
+
Object.assign(texture, { minFilter: LinearFilter, magFilter: LinearFilter, generateMipmaps: false });
|
|
20
|
+
return { texture, size, glyphs: new Map(), shelf: { x: 0, y: 0, height: 0 }, rasterize, sdf: {} };
|
|
21
|
+
}
|
|
22
|
+
export function getGlyph(atlas, char) {
|
|
23
|
+
const cached = atlas.glyphs.get(char);
|
|
24
|
+
if (cached) {
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
atlas.sdf.current ??= atlas.rasterize
|
|
28
|
+
? undefined
|
|
29
|
+
: new TinySDF({ fontSize: RASTER_SIZE, buffer: SDF_BUFFER, radius: 10, fontFamily: "Helvetica, Arial, sans-serif" });
|
|
30
|
+
const bitmap = atlas.rasterize?.(char) ?? atlas.sdf.current.draw(char);
|
|
31
|
+
const rect = bitmap.width > 2 * SDF_BUFFER && bitmap.height > 2 * SDF_BUFFER ? packGlyph(atlas, bitmap) : undefined;
|
|
32
|
+
const glyph = {
|
|
33
|
+
rect: rect ?? [0, 0, 0, 0],
|
|
34
|
+
left: (bitmap.glyphLeft - SDF_BUFFER) / RASTER_SIZE,
|
|
35
|
+
top: (bitmap.glyphTop + SDF_BUFFER) / RASTER_SIZE,
|
|
36
|
+
width: rect ? bitmap.width / RASTER_SIZE : 0,
|
|
37
|
+
height: rect ? bitmap.height / RASTER_SIZE : 0,
|
|
38
|
+
advance: bitmap.glyphAdvance / RASTER_SIZE,
|
|
39
|
+
};
|
|
40
|
+
atlas.glyphs.set(char, glyph);
|
|
41
|
+
return glyph;
|
|
42
|
+
}
|
|
43
|
+
/** Copies the bitmap to the atlas, left to right in rows, or leaves it out when the atlas is full; shelf packing, as @mapbox/shelf-pack does */
|
|
44
|
+
function packGlyph({ texture, size, shelf }, bitmap) {
|
|
45
|
+
if (shelf.x + bitmap.width > size) {
|
|
46
|
+
Object.assign(shelf, { x: 0, y: shelf.y + shelf.height, height: 0 });
|
|
47
|
+
}
|
|
48
|
+
if (shelf.y + bitmap.height > size) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const { x, y } = shelf;
|
|
52
|
+
const data = texture.image.data;
|
|
53
|
+
for (let row = 0; row < bitmap.height; row++) {
|
|
54
|
+
data.set(bitmap.data.subarray(row * bitmap.width, (row + 1) * bitmap.width), (y + row) * size + x);
|
|
55
|
+
}
|
|
56
|
+
Object.assign(shelf, { x: x + bitmap.width, height: Math.max(shelf.height, bitmap.height) });
|
|
57
|
+
texture.needsUpdate = true;
|
|
58
|
+
// Bitmap rows run top down from the texture's v = 0, while the quad's uv runs bottom up
|
|
59
|
+
return [x / size, (y + bitmap.height) / size, bitmap.width / size, -bitmap.height / size];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Lays out the text centered on the origin, each line centered, as drei's Text (troika-three-text) did with the r3f renderer's line height and
|
|
63
|
+
* spacing
|
|
64
|
+
*/
|
|
65
|
+
export function layoutText(atlas, text, fontSize) {
|
|
66
|
+
const lineHeight = LINE_HEIGHT * fontSize;
|
|
67
|
+
const lines = text.split("\n");
|
|
68
|
+
return lines.flatMap((line, i) => {
|
|
69
|
+
const glyphs = Array.from(line, (char) => getGlyph(atlas, char));
|
|
70
|
+
const advances = glyphs.map((g) => (g.advance + LETTER_SPACING) * fontSize);
|
|
71
|
+
const baseline = (lines.length * lineHeight) / 2 - i * lineHeight - (lineHeight - fontSize) / 2 - ASCENT * fontSize;
|
|
72
|
+
const pen = { x: -advances.reduce((sum, a) => sum + a, 0) / 2 };
|
|
73
|
+
return glyphs.flatMap((glyph, j) => {
|
|
74
|
+
const x = pen.x;
|
|
75
|
+
pen.x += advances[j];
|
|
76
|
+
return glyph.width === 0
|
|
77
|
+
? []
|
|
78
|
+
: [
|
|
79
|
+
{
|
|
80
|
+
glyph,
|
|
81
|
+
x: x + (glyph.left + glyph.width / 2) * fontSize,
|
|
82
|
+
y: baseline + (glyph.top - glyph.height / 2) * fontSize,
|
|
83
|
+
width: glyph.width * fontSize,
|
|
84
|
+
height: glyph.height * fontSize,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../../src/renderers/three/text/text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.test.d.ts","sourceRoot":"","sources":["../../../../src/renderers/three/text/text.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createGlyphAtlas, getGlyph, layoutText, RASTER_SIZE, SDF_BUFFER } from "./text.js";
|
|
3
|
+
/** Glyphs of 20 px ink, 30 px of it above the baseline, a 24 px advance, filled with their char code */
|
|
4
|
+
const rasterize = (char) => {
|
|
5
|
+
const [glyphWidth, glyphHeight] = char === " " ? [0, 0] : [20, 40];
|
|
6
|
+
const [width, height] = [glyphWidth + 2 * SDF_BUFFER, glyphHeight + 2 * SDF_BUFFER];
|
|
7
|
+
return {
|
|
8
|
+
data: new Uint8ClampedArray(width * height).fill(char.charCodeAt(0)),
|
|
9
|
+
width,
|
|
10
|
+
height,
|
|
11
|
+
glyphWidth,
|
|
12
|
+
glyphHeight,
|
|
13
|
+
glyphLeft: 2,
|
|
14
|
+
glyphTop: 30,
|
|
15
|
+
glyphAdvance: 24,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
const em = (px) => px / RASTER_SIZE;
|
|
19
|
+
const round = (v) => Math.round(v * 1e4) / 1e4;
|
|
20
|
+
describe("getGlyph", () => {
|
|
21
|
+
it("packs each glyph once, left to right, and writes its bitmap", () => {
|
|
22
|
+
const atlas = createGlyphAtlas(rasterize, 64);
|
|
23
|
+
const a = getGlyph(atlas, "A");
|
|
24
|
+
expect(getGlyph(atlas, "A")).toBe(a);
|
|
25
|
+
const b = getGlyph(atlas, "B");
|
|
26
|
+
const data = atlas.texture.image.data;
|
|
27
|
+
expect([data[0], data[28]]).toEqual([65, 66]);
|
|
28
|
+
expect(a.rect.map(round)).toEqual([0, round(48 / 64), round(28 / 64), round(-48 / 64)]);
|
|
29
|
+
expect(b.rect[0]).toBe(28 / 64);
|
|
30
|
+
});
|
|
31
|
+
it("starts a new row when a row is full", () => {
|
|
32
|
+
const atlas = createGlyphAtlas(rasterize, 128);
|
|
33
|
+
["A", "B", "C", "D"].forEach((char) => getGlyph(atlas, char));
|
|
34
|
+
expect(getGlyph(atlas, "E").rect.slice(0, 2)).toEqual([0, 96 / 128]);
|
|
35
|
+
});
|
|
36
|
+
it("gives glyphs their bitmap's position and size in em, from the pen at the baseline", () => {
|
|
37
|
+
expect(getGlyph(createGlyphAtlas(rasterize, 64), "A")).toMatchObject({
|
|
38
|
+
left: em(2 - SDF_BUFFER),
|
|
39
|
+
top: em(30 + SDF_BUFFER),
|
|
40
|
+
width: em(28),
|
|
41
|
+
height: em(48),
|
|
42
|
+
advance: em(24),
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it("leaves glyphs without ink out of the atlas", () => {
|
|
46
|
+
const atlas = createGlyphAtlas(rasterize, 64);
|
|
47
|
+
expect(getGlyph(atlas, " ").width).toBe(0);
|
|
48
|
+
expect(getGlyph(atlas, "A").rect[0]).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe("layoutText", () => {
|
|
52
|
+
const atlas = createGlyphAtlas(rasterize, 256);
|
|
53
|
+
it("centers a line on the origin", () => {
|
|
54
|
+
const advance = 24 + 0.001 * 40;
|
|
55
|
+
const quads = layoutText(atlas, "AA", 40);
|
|
56
|
+
expect(quads.map((q) => round(q.x))).toEqual([round(-advance + 2 - SDF_BUFFER + 14), round(2 - SDF_BUFFER + 14)]);
|
|
57
|
+
expect(quads.map((q) => [round(q.width), round(q.height)])).toEqual([
|
|
58
|
+
[28, 48],
|
|
59
|
+
[28, 48],
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
it("stacks lines 1.5 font sizes apart, centered vertically as a block", () => {
|
|
63
|
+
// The block is 120 high; each baseline is half the 20 of leading and an ascent of 32 below its line's top, and the glyphs' centers 10 above it
|
|
64
|
+
expect(layoutText(atlas, "A\nA", 40).map((q) => round(q.y))).toEqual([28, -32]);
|
|
65
|
+
});
|
|
66
|
+
it("skips glyphs without ink but keeps their advance", () => {
|
|
67
|
+
expect(layoutText(atlas, "A A", 40)).toHaveLength(2);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=text.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.test.js","sourceRoot":"","sources":["../../../../src/renderers/three/text/text.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DataTexture, Matrix4 } from "three";
|
|
2
|
+
import type { Part } from "./build.js";
|
|
3
|
+
/** Text as signed distance field glyphs from the system font, packed into one atlas texture, so all text draws in one call */
|
|
4
|
+
export declare const RASTER_SIZE = 40;
|
|
5
|
+
export declare const SDF_BUFFER = 4;
|
|
6
|
+
/** The distance field's value at the glyph's edge, from tiny-sdf's default cutoff of 0.25 */
|
|
7
|
+
export declare const SDF_EDGE = 0.75;
|
|
8
|
+
export type GlyphBitmap = {
|
|
9
|
+
readonly data: Uint8ClampedArray;
|
|
10
|
+
readonly width: number;
|
|
11
|
+
readonly height: number;
|
|
12
|
+
readonly glyphLeft: number;
|
|
13
|
+
readonly glyphTop: number;
|
|
14
|
+
readonly glyphAdvance: number;
|
|
15
|
+
};
|
|
16
|
+
export type Rasterize = (char: string) => GlyphBitmap;
|
|
17
|
+
export type Glyph = {
|
|
18
|
+
/** The glyph's place in the atlas as a uv offset and scale, for a quad with uv (0, 0) at its bottom left */
|
|
19
|
+
readonly rect: readonly [number, number, number, number];
|
|
20
|
+
/** The bitmap's left and top edges and size, in em, from the pen at the baseline; no width for glyphs without ink */
|
|
21
|
+
readonly left: number;
|
|
22
|
+
readonly top: number;
|
|
23
|
+
readonly width: number;
|
|
24
|
+
readonly height: number;
|
|
25
|
+
readonly advance: number;
|
|
26
|
+
};
|
|
27
|
+
export type GlyphAtlas = {
|
|
28
|
+
readonly texture: DataTexture;
|
|
29
|
+
readonly getGlyph: (char: string) => Glyph;
|
|
30
|
+
readonly dispose: () => void;
|
|
31
|
+
};
|
|
32
|
+
/** A quad of a laid out text, with its center and size in the text's units, centered on the origin */
|
|
33
|
+
export type GlyphQuad = {
|
|
34
|
+
readonly glyph: Glyph;
|
|
35
|
+
readonly x: number;
|
|
36
|
+
readonly y: number;
|
|
37
|
+
readonly width: number;
|
|
38
|
+
readonly height: number;
|
|
39
|
+
};
|
|
40
|
+
/** Rasterizes with the OS's built-in Helvetica or Arial, on a canvas created at first use */
|
|
41
|
+
export declare function createSystemFontRasterizer(): Rasterize;
|
|
42
|
+
export declare function createGlyphAtlas(rasterize?: Rasterize, size?: number): GlyphAtlas;
|
|
43
|
+
/** Lays out the text centered on the origin, each line centered, as drei's Text did */
|
|
44
|
+
export declare function layoutText(text: string, fontSize: number, getGlyph: (char: string) => Glyph): ReadonlyArray<GlyphQuad>;
|
|
45
|
+
export type TextEntry = {
|
|
46
|
+
readonly text: string;
|
|
47
|
+
readonly fontSize: number;
|
|
48
|
+
/** The text's position and rotation, relative to its item's matrix */
|
|
49
|
+
readonly local: Matrix4;
|
|
50
|
+
};
|
|
51
|
+
/** All texts of a part as glyph quads of one instanced mesh, each slot a text */
|
|
52
|
+
export declare function createTextPart(entries: ReadonlyArray<TextEntry>, atlas: GlyphAtlas): Part;
|
|
53
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/renderers/three/text.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EAKX,OAAO,EAKR,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,8HAA8H;AAE9H,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,OAAO,CAAC;AAO7B,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,WAAW,CAAC;AAEtD,MAAM,MAAM,KAAK,GAAG;IAClB,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACzD,qHAAqH;IACrH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAEF,sGAAsG;AACtG,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,6FAA6F;AAC7F,wBAAgB,0BAA0B,IAAI,SAAS,CAMtD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,GAAE,SAAwC,EAAE,IAAI,SAAO,GAAG,UAAU,CA+C7G;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,GAAG,aAAa,CAAC,SAAS,CAAC,CAuBtH;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CA8CzF"}
|