@expofp/renderer 1.5.0 → 2.0.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/dist/index.d.ts +90 -32
- package/dist/index.js +645 -319
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
var _a;
|
|
5
|
-
import { Color, Matrix4, Vector3, DataTexture, RGBAFormat, FloatType, RedFormat, UnsignedIntType, IntType, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedIntegerFormat, BatchedMesh as BatchedMesh$1, BufferAttribute, StreamDrawUsage, Vector4, AlwaysDepth, DoubleSide, MeshBasicMaterial, Texture, Group, PlaneGeometry, SRGBColorSpace, Vector2, Mesh, LessEqualDepth, Quaternion, BufferGeometry, LinearSRGBColorSpace, Plane, Raycaster, Sphere, Box3, Spherical, PerspectiveCamera,
|
|
5
|
+
import { Color, Matrix4, Vector3, DataTexture, RGBAFormat, FloatType, RedFormat, UnsignedIntType, IntType, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedIntegerFormat, BatchedMesh as BatchedMesh$1, BufferAttribute, StreamDrawUsage, Vector4, AlwaysDepth, DoubleSide, MeshBasicMaterial, Texture, Group, PlaneGeometry, SRGBColorSpace, Vector2, Mesh, LessEqualDepth, Quaternion, BufferGeometry, LinearSRGBColorSpace, Plane, Raycaster, Sphere, Box3, Spherical, PerspectiveCamera, Camera, Scene, MathUtils, Clock, WebGLRenderer } from "three";
|
|
6
6
|
import { traverseAncestorsGenerator } from "three/examples/jsm/utils/SceneUtils.js";
|
|
7
7
|
import { BatchedText as BatchedText$1, Text as Text$1 } from "troika-three-text";
|
|
8
8
|
import { LineMaterial, LineSegmentsGeometry } from "three/examples/jsm/Addons.js";
|
|
@@ -336,7 +336,7 @@ const dimColorVertexImpl = (
|
|
|
336
336
|
`
|
|
337
337
|
void setDimAmount() {
|
|
338
338
|
float instanceDim = 0.;
|
|
339
|
-
#ifdef USE_BATCH_UNIFORMS
|
|
339
|
+
#ifdef USE_BATCH_UNIFORMS
|
|
340
340
|
instanceDim = batch_skipDimInstance;
|
|
341
341
|
#endif
|
|
342
342
|
#ifdef TROIKA_DERIVED_MATERIAL_1
|
|
@@ -747,7 +747,7 @@ const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
|
|
|
747
747
|
}
|
|
748
748
|
dispose() {
|
|
749
749
|
var _a2;
|
|
750
|
-
this.geometry.setIndex(this.indexBuffer
|
|
750
|
+
if (this.indexBuffer) this.geometry.setIndex(this.indexBuffer);
|
|
751
751
|
super.dispose();
|
|
752
752
|
this.uniformSchema = {};
|
|
753
753
|
(_a2 = this.uniformsTexture) == null ? void 0 : _a2.dispose();
|
|
@@ -1082,19 +1082,6 @@ function isVisible(object) {
|
|
|
1082
1082
|
if (!object.visible) return false;
|
|
1083
1083
|
return [...traverseAncestorsGenerator(object)].every((obj) => obj.visible);
|
|
1084
1084
|
}
|
|
1085
|
-
function printTree(object, fullName = false) {
|
|
1086
|
-
object.traverse((obj) => {
|
|
1087
|
-
let s = "";
|
|
1088
|
-
let obj2 = obj;
|
|
1089
|
-
while (obj2 !== object) {
|
|
1090
|
-
s = "|___ " + s;
|
|
1091
|
-
obj2 = (obj2 == null ? void 0 : obj2.parent) ?? null;
|
|
1092
|
-
}
|
|
1093
|
-
const renderOrder = obj.isGroup ? "" : `, RO: ${obj.renderOrder}`;
|
|
1094
|
-
const name = fullName ? obj.name : obj.name.split(":").at(-1);
|
|
1095
|
-
console.log(`${s}${name}<${obj.type}>${renderOrder}`);
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
1085
|
class RenderableSystem {
|
|
1099
1086
|
/**
|
|
1100
1087
|
* @param type readable name of the system's type for debugging
|
|
@@ -1825,6 +1812,7 @@ class TextSystem extends RenderableSystem {
|
|
|
1825
1812
|
__publicField(this, "initialTextScale", new Vector2(1, -1));
|
|
1826
1813
|
__publicField(this, "textColor", new Color());
|
|
1827
1814
|
__publicField(this, "pendingUpdates", /* @__PURE__ */ new Map());
|
|
1815
|
+
__publicField(this, "sdfAtlases", /* @__PURE__ */ new Set());
|
|
1828
1816
|
__publicField(this, "alignmentOffset", new Vector2());
|
|
1829
1817
|
__publicField(this, "alignmentDirection", new Vector2());
|
|
1830
1818
|
__publicField(this, "localPosition", new Vector2());
|
|
@@ -1834,6 +1822,10 @@ class TextSystem extends RenderableSystem {
|
|
|
1834
1822
|
__publicField(this, "localToMax", new Vector2());
|
|
1835
1823
|
this.materialSystem = materialSystem;
|
|
1836
1824
|
}
|
|
1825
|
+
dispose() {
|
|
1826
|
+
super.dispose();
|
|
1827
|
+
this.sdfAtlases.forEach((texture) => texture.dispose());
|
|
1828
|
+
}
|
|
1837
1829
|
buildLayer(layer) {
|
|
1838
1830
|
const group = new Group();
|
|
1839
1831
|
const batchedText = this.buildBatchedText(layer);
|
|
@@ -1911,7 +1903,12 @@ class TextSystem extends RenderableSystem {
|
|
|
1911
1903
|
for (const { textDef, instanceIds } of mappingData) {
|
|
1912
1904
|
this.registerDefObject(textDef, batchedText, instanceIds);
|
|
1913
1905
|
}
|
|
1914
|
-
batchedText.addEventListener("synccomplete", () =>
|
|
1906
|
+
batchedText.addEventListener("synccomplete", () => {
|
|
1907
|
+
var _a2;
|
|
1908
|
+
const sdfTexture = (_a2 = batchedText.textRenderInfo) == null ? void 0 : _a2.sdfTexture;
|
|
1909
|
+
if (sdfTexture) this.sdfAtlases.add(sdfTexture);
|
|
1910
|
+
this.renderer.update();
|
|
1911
|
+
});
|
|
1915
1912
|
return batchedText;
|
|
1916
1913
|
}
|
|
1917
1914
|
// TODO: Simplify
|
|
@@ -2044,6 +2041,7 @@ class LayerSystem {
|
|
|
2044
2041
|
}
|
|
2045
2042
|
/**
|
|
2046
2043
|
* Update the given defs immediately, or queue them for update if update buffering is enabled.
|
|
2044
|
+
* NOTE: Currently update buffering is disabled, as observed performance gains are negligible. Need to revisit this.
|
|
2047
2045
|
* @param defs {@link RenderableDef} array
|
|
2048
2046
|
*/
|
|
2049
2047
|
updateDefs(defs) {
|
|
@@ -2211,6 +2209,19 @@ class LayerSystem {
|
|
|
2211
2209
|
return fullName;
|
|
2212
2210
|
}
|
|
2213
2211
|
}
|
|
2212
|
+
function printTree(object, fullName = false) {
|
|
2213
|
+
object.traverse((obj) => {
|
|
2214
|
+
let s = "";
|
|
2215
|
+
let obj2 = obj;
|
|
2216
|
+
while (obj2 !== object) {
|
|
2217
|
+
s = "|___ " + s;
|
|
2218
|
+
obj2 = (obj2 == null ? void 0 : obj2.parent) ?? null;
|
|
2219
|
+
}
|
|
2220
|
+
const renderOrder = obj.isGroup ? "" : `, RO: ${obj.renderOrder}`;
|
|
2221
|
+
const name = fullName ? obj.name : obj.name.split(":").at(-1);
|
|
2222
|
+
console.log(`${s}${name}<${obj.type}>${renderOrder}`);
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2214
2225
|
/*!
|
|
2215
2226
|
* camera-controls
|
|
2216
2227
|
* https://github.com/yomotsu/camera-controls
|
|
@@ -4747,6 +4758,20 @@ class CameraController extends CameraControls {
|
|
|
4747
4758
|
constructor(camera, renderer) {
|
|
4748
4759
|
super(camera);
|
|
4749
4760
|
this.renderer = renderer;
|
|
4761
|
+
this.dollyToCursor = true;
|
|
4762
|
+
this.draggingSmoothTime = 0;
|
|
4763
|
+
void this.rotatePolarTo(0, false);
|
|
4764
|
+
this.mouseButtons = {
|
|
4765
|
+
left: CameraController.ACTION.NONE,
|
|
4766
|
+
middle: CameraController.ACTION.NONE,
|
|
4767
|
+
right: CameraController.ACTION.NONE,
|
|
4768
|
+
wheel: CameraController.ACTION.NONE
|
|
4769
|
+
};
|
|
4770
|
+
this.touches = {
|
|
4771
|
+
one: CameraController.ACTION.NONE,
|
|
4772
|
+
two: CameraController.ACTION.NONE,
|
|
4773
|
+
three: CameraController.ACTION.NONE
|
|
4774
|
+
};
|
|
4750
4775
|
}
|
|
4751
4776
|
update(delta) {
|
|
4752
4777
|
var _a2;
|
|
@@ -4767,31 +4792,30 @@ class CameraSystem {
|
|
|
4767
4792
|
* @param renderer {@link Renderer} instance
|
|
4768
4793
|
*/
|
|
4769
4794
|
constructor(renderer) {
|
|
4770
|
-
/**
|
|
4771
|
-
__publicField(this, "
|
|
4795
|
+
/** {@link PerspectiveCamera} instance. Used to render the scene in internal mode. */
|
|
4796
|
+
__publicField(this, "camera");
|
|
4772
4797
|
/** {@link CameraController} instance. Used to smoothly animate the camera. */
|
|
4773
4798
|
__publicField(this, "controller");
|
|
4774
|
-
|
|
4775
|
-
|
|
4799
|
+
/**
|
|
4800
|
+
* Cached previous viewport height used to preserve zoom across resizes.
|
|
4801
|
+
* Note: we intentionally keep this separate from the derived identity distance.
|
|
4802
|
+
*/
|
|
4803
|
+
__publicField(this, "prevViewportHeightPx");
|
|
4804
|
+
/** [min, max] zoom factors */
|
|
4776
4805
|
__publicField(this, "zoomBounds");
|
|
4806
|
+
/** Default FOV for the camera. Taken from Mapbox GL JS. */
|
|
4807
|
+
__publicField(this, "defaultFov", 36.87);
|
|
4777
4808
|
this.renderer = renderer;
|
|
4778
|
-
const
|
|
4779
|
-
this.
|
|
4809
|
+
const h = renderer.size[1];
|
|
4810
|
+
this.prevViewportHeightPx = h;
|
|
4811
|
+
this.camera = new PerspectiveCamera(this.defaultFov);
|
|
4780
4812
|
this.camera.up.set(0, 0, -1);
|
|
4781
|
-
this.
|
|
4782
|
-
this.camera.position.z = this.zoomIdentityDistance;
|
|
4783
|
-
this.controller = new CameraController(this.camera, renderer);
|
|
4784
|
-
this.controller.polarAngle = 0;
|
|
4813
|
+
this.controller = new CameraController(this.camera, this.renderer);
|
|
4785
4814
|
this.controller.distance = this.zoomIdentityDistance;
|
|
4786
4815
|
}
|
|
4787
|
-
/** Current camera instance. */
|
|
4788
|
-
get currentCamera() {
|
|
4789
|
-
return this.externalCamera ?? this.camera;
|
|
4790
|
-
}
|
|
4791
4816
|
/** Current camera zoom factor. */
|
|
4792
4817
|
get zoomFactor() {
|
|
4793
|
-
|
|
4794
|
-
return distance ? this.zoomIdentityDistance / distance : 1;
|
|
4818
|
+
return this.zoomFactorForHeight(this.renderer.size[1]);
|
|
4795
4819
|
}
|
|
4796
4820
|
/**
|
|
4797
4821
|
* Calculates the camera distance from the scene's plane for a given zoom factor.
|
|
@@ -4799,7 +4823,7 @@ class CameraSystem {
|
|
|
4799
4823
|
* @returns Corresponding camera distance on the Z axis
|
|
4800
4824
|
*/
|
|
4801
4825
|
zoomFactorToDistance(zoomFactor) {
|
|
4802
|
-
return
|
|
4826
|
+
return this.zoomIdentityDistance / zoomFactor;
|
|
4803
4827
|
}
|
|
4804
4828
|
/**
|
|
4805
4829
|
* Initializes the camera with the given zoom bounds.
|
|
@@ -4813,41 +4837,188 @@ class CameraSystem {
|
|
|
4813
4837
|
updateCamera() {
|
|
4814
4838
|
if (!this.zoomBounds) return;
|
|
4815
4839
|
const [w, h] = this.renderer.size;
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
const
|
|
4819
|
-
const
|
|
4840
|
+
if (w <= 0 || h <= 0) return;
|
|
4841
|
+
const zoomFactor = this.zoomFactorForHeight(this.prevViewportHeightPx);
|
|
4842
|
+
const newZoomIdentity = this.zoomIdentityDistanceForHeight(h);
|
|
4843
|
+
const maxDistance = Math.abs(newZoomIdentity / this.zoomBounds[0]);
|
|
4844
|
+
const minDistance = Math.abs(newZoomIdentity / this.zoomBounds[1]);
|
|
4820
4845
|
this.camera.aspect = w / (h || 1);
|
|
4821
|
-
this.
|
|
4846
|
+
this.camera.near = 0.01;
|
|
4847
|
+
this.camera.far = Math.max(maxDistance, this.camera.near) * 2;
|
|
4822
4848
|
this.camera.updateProjectionMatrix();
|
|
4823
|
-
this.syncController(minDistance, maxDistance, zoomFactor);
|
|
4824
|
-
}
|
|
4825
|
-
computeCameraClipPlanes(minDistance, maxDistance, nearSafetyFactor = 0.5, farSafetyFactor = 1.5) {
|
|
4826
|
-
const fov = this.camera.fov * DEG2RAD$1;
|
|
4827
|
-
const aspect = this.camera.aspect;
|
|
4828
|
-
const maxPolarAngle = 85 * DEG2RAD$1;
|
|
4829
|
-
const halfFovY = fov / 2;
|
|
4830
|
-
const halfFovX = Math.atan(Math.tan(halfFovY) * aspect);
|
|
4831
|
-
const diagonalFov = 2 * Math.atan(Math.sqrt(Math.tan(halfFovX) ** 2 + Math.tan(halfFovY) ** 2));
|
|
4832
|
-
const minHeight = minDistance * Math.cos(maxPolarAngle);
|
|
4833
|
-
const near = Math.max(0.1, minHeight * nearSafetyFactor);
|
|
4834
|
-
const criticalHeight = minHeight;
|
|
4835
|
-
const horizontalDistToOrbit = minDistance * Math.sin(maxPolarAngle);
|
|
4836
|
-
const distToOrbit = minDistance;
|
|
4837
|
-
const visibleRadiusAtOrbit = distToOrbit * Math.tan(diagonalFov / 2);
|
|
4838
|
-
const planeExtent = Math.max(maxDistance, visibleRadiusAtOrbit);
|
|
4839
|
-
const horizontalDistToFarEdge = horizontalDistToOrbit + planeExtent;
|
|
4840
|
-
const maxViewDistance = Math.sqrt(criticalHeight ** 2 + horizontalDistToFarEdge ** 2);
|
|
4841
|
-
const far = maxViewDistance * farSafetyFactor;
|
|
4842
|
-
this.camera.near = near;
|
|
4843
|
-
this.camera.far = far;
|
|
4844
|
-
if (this.renderer.debugLog) console.log("camera clip planes", near, far);
|
|
4845
|
-
}
|
|
4846
|
-
syncController(minDistance, maxDistance, zoomFactor) {
|
|
4847
|
-
if (this.renderer.debugLog) console.log("syncController", minDistance, maxDistance, zoomFactor);
|
|
4848
4849
|
this.controller.minDistance = minDistance;
|
|
4849
4850
|
this.controller.maxDistance = maxDistance;
|
|
4850
4851
|
void this.controller.dollyTo(this.zoomFactorToDistance(zoomFactor), false);
|
|
4852
|
+
this.prevViewportHeightPx = h;
|
|
4853
|
+
}
|
|
4854
|
+
/**
|
|
4855
|
+
* Distance from the scene plane corresponding to zoomFactor = 1 for the current viewport height.
|
|
4856
|
+
* Derived from camera FOV and renderer height (in pixels).
|
|
4857
|
+
*/
|
|
4858
|
+
get zoomIdentityDistance() {
|
|
4859
|
+
return this.zoomIdentityDistanceForHeight(this.renderer.size[1]);
|
|
4860
|
+
}
|
|
4861
|
+
/**
|
|
4862
|
+
* Calculates the zoom identity distance for a given viewport height.
|
|
4863
|
+
* @param viewportHeightPx Renderer height in pixels
|
|
4864
|
+
* @returns Zoom identity distance
|
|
4865
|
+
*/
|
|
4866
|
+
zoomIdentityDistanceForHeight(viewportHeightPx) {
|
|
4867
|
+
if (viewportHeightPx <= 0) return 0;
|
|
4868
|
+
return viewportHeightPx * 0.5 / Math.tan(this.camera.fov * DEG2RAD$1 / 2);
|
|
4869
|
+
}
|
|
4870
|
+
/**
|
|
4871
|
+
* Calculates the zoom factor for a given viewport height.
|
|
4872
|
+
* @param viewportHeightPx Renderer height in pixels
|
|
4873
|
+
* @returns Zoom factor
|
|
4874
|
+
*/
|
|
4875
|
+
zoomFactorForHeight(viewportHeightPx) {
|
|
4876
|
+
const zid = this.zoomIdentityDistanceForHeight(viewportHeightPx);
|
|
4877
|
+
if (zid === 0) return 1;
|
|
4878
|
+
return zid / (this.controller.distance || zid);
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
class ExternalSystem {
|
|
4882
|
+
/**
|
|
4883
|
+
* @param pickingSystem {@link PickingSystem} instance
|
|
4884
|
+
*/
|
|
4885
|
+
constructor(pickingSystem) {
|
|
4886
|
+
/** External camera instance */
|
|
4887
|
+
__publicField(this, "camera", new Camera());
|
|
4888
|
+
__publicField(this, "staticTransformMatrix", new Matrix4());
|
|
4889
|
+
__publicField(this, "intersectionPoint", new Vector3());
|
|
4890
|
+
/**
|
|
4891
|
+
* Scratch NDC coordinate used by external ptScale. Kept as a field to avoid allocating a Vector2 each frame.
|
|
4892
|
+
* (This is always screen center: NDC (0,0))
|
|
4893
|
+
*/
|
|
4894
|
+
__publicField(this, "ndcCenter", new Vector2(0, 0));
|
|
4895
|
+
/**
|
|
4896
|
+
* Scratch clip-space vector used when projecting SVG points through the external camera matrix.
|
|
4897
|
+
* Kept as a field to avoid allocating a Vector4 each frame.
|
|
4898
|
+
*/
|
|
4899
|
+
__publicField(this, "clipPoint", new Vector4());
|
|
4900
|
+
/**
|
|
4901
|
+
* Scratch pixel points used by external ptScale estimation.
|
|
4902
|
+
* p0 is the screen center in drawing-buffer pixels; p1/p2 are projected offsets from the anchor point.
|
|
4903
|
+
*/
|
|
4904
|
+
__publicField(this, "px0", new Vector2());
|
|
4905
|
+
__publicField(this, "px1", new Vector2());
|
|
4906
|
+
__publicField(this, "px2", new Vector2());
|
|
4907
|
+
this.pickingSystem = pickingSystem;
|
|
4908
|
+
}
|
|
4909
|
+
/**
|
|
4910
|
+
* Set static part of an svg -> px transform matrix
|
|
4911
|
+
* @param staticTransformMatrix static transform matrix to apply to the scene
|
|
4912
|
+
*/
|
|
4913
|
+
setStaticTransform(staticTransformMatrix) {
|
|
4914
|
+
if (!this.validateMatrix(staticTransformMatrix, "setStaticTransform")) return;
|
|
4915
|
+
this.staticTransformMatrix.fromArray(staticTransformMatrix);
|
|
4916
|
+
}
|
|
4917
|
+
/**
|
|
4918
|
+
* Set dynamic part of an svg -> px transform matrix. Should be called every frame.
|
|
4919
|
+
* @param dynamicTransformMatrix dynamic transform matrix (changes every frame)
|
|
4920
|
+
*/
|
|
4921
|
+
setDynamicTransform(dynamicTransformMatrix) {
|
|
4922
|
+
if (!this.validateMatrix(dynamicTransformMatrix, "setDynamicTransform")) return;
|
|
4923
|
+
this.camera.projectionMatrix.fromArray(dynamicTransformMatrix).multiply(this.staticTransformMatrix);
|
|
4924
|
+
this.camera.projectionMatrixInverse.copy(this.camera.projectionMatrix).invert();
|
|
4925
|
+
}
|
|
4926
|
+
/**
|
|
4927
|
+
* Estimates pixel→SVG scale for external context rendering (e.g. Mapbox).
|
|
4928
|
+
*
|
|
4929
|
+
* In external mode we don't own a camera rig/controller, so we can't derive scale from "camera distance".
|
|
4930
|
+
* Instead, we:
|
|
4931
|
+
* 1) Find the point on the SVG plane that is currently under the screen center.
|
|
4932
|
+
* 2) Measure how much the screen position changes when moving 1 SVG unit in X/Y around that point.
|
|
4933
|
+
* 3) Convert that local plane→screen mapping into a single "zoom-like" scalar that is stable under tilt+rotate.
|
|
4934
|
+
*
|
|
4935
|
+
* This matches internal mode semantics better because the internal orbit target is also kept under screen center.
|
|
4936
|
+
* @param viewportSize Size of the viewport in drawing-buffer pixels
|
|
4937
|
+
* @returns Pixel-to-SVG scale factor (px → svg units)
|
|
4938
|
+
*/
|
|
4939
|
+
pxToSvgScale(viewportSize) {
|
|
4940
|
+
const M = this.camera.projectionMatrix;
|
|
4941
|
+
const [viewportW, viewportH] = viewportSize;
|
|
4942
|
+
if (viewportW <= 0 || viewportH <= 0) return;
|
|
4943
|
+
const intersectionPoint = this.pickingSystem.intersectPlane(this.ndcCenter, this.camera, this.intersectionPoint);
|
|
4944
|
+
if (!intersectionPoint) return;
|
|
4945
|
+
const anchorX = intersectionPoint.x;
|
|
4946
|
+
const anchorY = intersectionPoint.y;
|
|
4947
|
+
const clip = this.clipPoint;
|
|
4948
|
+
const p0 = this.px0.set(viewportW * 0.5, viewportH * 0.5);
|
|
4949
|
+
const p1 = this.px1;
|
|
4950
|
+
const p2 = this.px2;
|
|
4951
|
+
const svgToPixels = (x, y, out) => {
|
|
4952
|
+
clip.set(x, y, 0, 1).applyMatrix4(M);
|
|
4953
|
+
if (clip.w === 0) return false;
|
|
4954
|
+
const ndcX = clip.x / clip.w;
|
|
4955
|
+
const ndcY = clip.y / clip.w;
|
|
4956
|
+
out.set((ndcX + 1) * 0.5 * viewportW, (1 - ndcY) * 0.5 * viewportH);
|
|
4957
|
+
return true;
|
|
4958
|
+
};
|
|
4959
|
+
const svgStep = 1;
|
|
4960
|
+
if (!svgToPixels(anchorX + svgStep, anchorY, p1)) return;
|
|
4961
|
+
if (!svgToPixels(anchorX, anchorY + svgStep, p2)) return;
|
|
4962
|
+
const pxDeltaPerSvgX = p1.sub(p0).divideScalar(svgStep);
|
|
4963
|
+
const pxDeltaPerSvgY = p2.sub(p0).divideScalar(svgStep);
|
|
4964
|
+
const pixelsSqPerSvgX = pxDeltaPerSvgX.dot(pxDeltaPerSvgX);
|
|
4965
|
+
const pixelsSqPerSvgY = pxDeltaPerSvgY.dot(pxDeltaPerSvgY);
|
|
4966
|
+
const pixelsSqCross = pxDeltaPerSvgX.dot(pxDeltaPerSvgY);
|
|
4967
|
+
const sumPixelsSq = pixelsSqPerSvgX + pixelsSqPerSvgY;
|
|
4968
|
+
const areaPixelsSq = pixelsSqPerSvgX * pixelsSqPerSvgY - pixelsSqCross * pixelsSqCross;
|
|
4969
|
+
const maxStretchDiscriminant = Math.max(0, sumPixelsSq * sumPixelsSq - 4 * areaPixelsSq);
|
|
4970
|
+
const maxPixelsSqPerSvg = 0.5 * (sumPixelsSq + Math.sqrt(maxStretchDiscriminant));
|
|
4971
|
+
const pxPerSvg = Math.sqrt(maxPixelsSqPerSvg);
|
|
4972
|
+
if (!Number.isFinite(pxPerSvg) || pxPerSvg <= 0) return;
|
|
4973
|
+
return 1 / pxPerSvg;
|
|
4974
|
+
}
|
|
4975
|
+
validateMatrix(matrix, name) {
|
|
4976
|
+
if (matrix.length !== 16) {
|
|
4977
|
+
console.warn(`[ViewportSystem.${name}]: Matrix must be 16 elements long`);
|
|
4978
|
+
return false;
|
|
4979
|
+
}
|
|
4980
|
+
return true;
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
class PickingSystem {
|
|
4984
|
+
/** */
|
|
4985
|
+
constructor() {
|
|
4986
|
+
__publicField(this, "raycaster", new Raycaster());
|
|
4987
|
+
__publicField(this, "ndcPoint", new Vector2());
|
|
4988
|
+
__publicField(this, "viewboxPlane", new Plane(new Vector3(0, 0, 1), 0));
|
|
4989
|
+
this.raycaster.layers.set(INTERACTIVE_LAYER);
|
|
4990
|
+
}
|
|
4991
|
+
/**
|
|
4992
|
+
* Gets the objects intersected by the raycaster.
|
|
4993
|
+
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
4994
|
+
* @param scene {@link Scene} instance
|
|
4995
|
+
* @param camera {@link Camera} instance
|
|
4996
|
+
* @returns Array of {@link Intersection} instances
|
|
4997
|
+
*/
|
|
4998
|
+
getIntersectedObjects(ndcCoords, scene, camera) {
|
|
4999
|
+
this.setRaycasterFromCamera(ndcCoords, camera);
|
|
5000
|
+
const intersections = this.raycaster.intersectObject(scene, true);
|
|
5001
|
+
return intersections.filter((i) => isVisible(i.object));
|
|
5002
|
+
}
|
|
5003
|
+
/**
|
|
5004
|
+
* Intersects the xy-plane with the raycaster.
|
|
5005
|
+
* @param ndcCoords raycast point in NDC (normalized device coordinates
|
|
5006
|
+
* @param camera {@link Camera} instance
|
|
5007
|
+
* @param out Output vector
|
|
5008
|
+
* @returns Intersection point in world space or null if no intersection.
|
|
5009
|
+
*/
|
|
5010
|
+
intersectPlane(ndcCoords, camera, out) {
|
|
5011
|
+
this.setRaycasterFromCamera(ndcCoords, camera);
|
|
5012
|
+
return this.raycaster.ray.intersectPlane(this.viewboxPlane, out) ?? void 0;
|
|
5013
|
+
}
|
|
5014
|
+
setRaycasterFromCamera(ndcCoords, camera) {
|
|
5015
|
+
if (camera.isPerspectiveCamera || camera.isOrthographicCamera) {
|
|
5016
|
+
this.ndcPoint.set(ndcCoords.x, ndcCoords.y);
|
|
5017
|
+
this.raycaster.setFromCamera(this.ndcPoint, camera);
|
|
5018
|
+
} else {
|
|
5019
|
+
this.raycaster.ray.origin.set(0, 0, 0).unproject(camera);
|
|
5020
|
+
this.raycaster.ray.direction.set(ndcCoords.x, ndcCoords.y, 1).unproject(camera).sub(this.raycaster.ray.origin).normalize();
|
|
5021
|
+
}
|
|
4851
5022
|
}
|
|
4852
5023
|
}
|
|
4853
5024
|
class SceneSystem {
|
|
@@ -4857,29 +5028,22 @@ class SceneSystem {
|
|
|
4857
5028
|
constructor(renderer) {
|
|
4858
5029
|
/** {@link Scene} instance */
|
|
4859
5030
|
__publicField(this, "scene");
|
|
4860
|
-
/** World matrix -
|
|
5031
|
+
/** World matrix - model → world transform */
|
|
4861
5032
|
__publicField(this, "worldMatrix", new Matrix4());
|
|
4862
|
-
/** Inverse world matrix -
|
|
5033
|
+
/** Inverse world matrix - world → model transform */
|
|
4863
5034
|
__publicField(this, "inverseWorldMatrix", new Matrix4());
|
|
5035
|
+
__publicField(this, "tempVector3", new Vector3());
|
|
4864
5036
|
__publicField(this, "translationMatrix", new Matrix4());
|
|
4865
5037
|
__publicField(this, "scaleMatrix", new Matrix4());
|
|
4866
|
-
__publicField(this, "scaleVector", new Vector3());
|
|
4867
5038
|
__publicField(this, "visibleRectOffsetMatrix", new Matrix4());
|
|
4868
5039
|
__publicField(this, "viewbox");
|
|
4869
5040
|
this.renderer = renderer;
|
|
4870
5041
|
this.scene = new Scene();
|
|
4871
5042
|
this.scene.matrixAutoUpdate = false;
|
|
4872
5043
|
}
|
|
4873
|
-
/** Scene scale factor (
|
|
5044
|
+
/** Scene scale factor (model space to world space) */
|
|
4874
5045
|
get scaleFactor() {
|
|
4875
|
-
this.
|
|
4876
|
-
if (this.scaleVector.z === 1) {
|
|
4877
|
-
return this.scaleVector.x;
|
|
4878
|
-
} else {
|
|
4879
|
-
const perspectiveW = this.scene.matrix.elements[15];
|
|
4880
|
-
const halfViewportWidth = this.renderer.size[0] / 2;
|
|
4881
|
-
return halfViewportWidth * this.scaleVector.x / perspectiveW;
|
|
4882
|
-
}
|
|
5046
|
+
return this.scene.matrix.elements[0];
|
|
4883
5047
|
}
|
|
4884
5048
|
/**
|
|
4885
5049
|
* Initializes the scene with the given SVG viewbox.
|
|
@@ -4889,28 +5053,48 @@ class SceneSystem {
|
|
|
4889
5053
|
this.viewbox = viewbox;
|
|
4890
5054
|
this.updateScene();
|
|
4891
5055
|
}
|
|
4892
|
-
/**
|
|
4893
|
-
* Updates the scene transform when the renderer size changes.
|
|
4894
|
-
*/
|
|
5056
|
+
/** Updates the scene transform from the current viewbox and renderer size. */
|
|
4895
5057
|
updateScene() {
|
|
4896
5058
|
if (!this.viewbox) return;
|
|
5059
|
+
this.composeMatrices(this.viewbox);
|
|
5060
|
+
}
|
|
5061
|
+
/**
|
|
5062
|
+
* Converts a point from model coordinates to world coordinates.
|
|
5063
|
+
* @param modelCoords Point in model coordinates
|
|
5064
|
+
* @param out Output vector
|
|
5065
|
+
* @returns Point in world coordinates
|
|
5066
|
+
*/
|
|
5067
|
+
modelToWorld(modelCoords, out) {
|
|
5068
|
+
const worldPoint = this.tempVector3.set(modelCoords.x, modelCoords.y, 0).applyMatrix4(this.worldMatrix);
|
|
5069
|
+
out.set(worldPoint.x, worldPoint.y, 0);
|
|
5070
|
+
return out;
|
|
5071
|
+
}
|
|
5072
|
+
/**
|
|
5073
|
+
* Converts a point from world coordinates to model coordinates. Z axis is ignored.
|
|
5074
|
+
* @param worldCoords Point in world coordinates
|
|
5075
|
+
* @param out Output vector
|
|
5076
|
+
* @returns Point in SVG coordinates
|
|
5077
|
+
*/
|
|
5078
|
+
worldToModel(worldCoords, out) {
|
|
5079
|
+
const modelPoint = this.tempVector3.copy(worldCoords).applyMatrix4(this.inverseWorldMatrix);
|
|
5080
|
+
out.set(modelPoint.x, modelPoint.y);
|
|
5081
|
+
return out;
|
|
5082
|
+
}
|
|
5083
|
+
composeMatrices(viewbox) {
|
|
4897
5084
|
const dpr = this.renderer.context.getPixelRatio();
|
|
4898
5085
|
const visibleRect = this.renderer.visibleRect;
|
|
4899
|
-
const [viewBoxWidth, viewBoxHeight] =
|
|
5086
|
+
const [viewBoxWidth, viewBoxHeight] = viewbox.size;
|
|
4900
5087
|
const [visibleRectWidth, visibleRectHeight] = (visibleRect == null ? void 0 : visibleRect.size.clone().multiplyScalar(dpr)) ?? this.renderer.size;
|
|
4901
5088
|
const scaleFactor = Math.min(visibleRectWidth / viewBoxWidth, visibleRectHeight / viewBoxHeight);
|
|
4902
|
-
const [centerX, centerY] =
|
|
5089
|
+
const [centerX, centerY] = viewbox.center;
|
|
4903
5090
|
this.translationMatrix.makeTranslation(-centerX, -centerY, 0);
|
|
4904
5091
|
this.scaleMatrix.makeScale(scaleFactor, scaleFactor, 1);
|
|
4905
5092
|
if (visibleRect) {
|
|
4906
5093
|
const visibleRectCenter = visibleRect.center.clone().multiplyScalar(dpr);
|
|
4907
|
-
const canvasCenter =
|
|
5094
|
+
const canvasCenter = { x: this.renderer.size[0] / 2, y: this.renderer.size[1] / 2 };
|
|
4908
5095
|
const offset = visibleRectCenter.sub(canvasCenter);
|
|
4909
5096
|
this.visibleRectOffsetMatrix.makeTranslation(offset.x, offset.y, 0);
|
|
4910
5097
|
}
|
|
4911
|
-
this.composeMatrices();
|
|
4912
|
-
}
|
|
4913
|
-
composeMatrices() {
|
|
4914
5098
|
this.worldMatrix.copy(this.translationMatrix).premultiply(this.scaleMatrix);
|
|
4915
5099
|
if (this.renderer.visibleRect) this.worldMatrix.premultiply(this.visibleRectOffsetMatrix);
|
|
4916
5100
|
this.scene.matrix.copy(this.worldMatrix);
|
|
@@ -4924,19 +5108,18 @@ class ViewportSystem {
|
|
|
4924
5108
|
* @param eventSystem {@link EventSystem} instance
|
|
4925
5109
|
*/
|
|
4926
5110
|
constructor(renderer, eventSystem) {
|
|
5111
|
+
__publicField(this, "pickingSystem");
|
|
5112
|
+
__publicField(this, "externalSystem");
|
|
4927
5113
|
__publicField(this, "sceneSystem");
|
|
4928
5114
|
__publicField(this, "cameraSystem");
|
|
4929
|
-
__publicField(this, "raycaster", new Raycaster());
|
|
4930
|
-
__publicField(this, "intersectionPoint", new Vector3());
|
|
4931
|
-
__publicField(this, "viewboxPlane", new Plane(new Vector3(0, 0, 1), 0));
|
|
4932
5115
|
__publicField(this, "pxToSvgScaleThreshold", 1e-4);
|
|
4933
5116
|
__publicField(this, "prevPxToSvgScale");
|
|
4934
|
-
__publicField(this, "externalStaticTransformMatrix", new Matrix4());
|
|
4935
5117
|
this.renderer = renderer;
|
|
4936
5118
|
this.eventSystem = eventSystem;
|
|
5119
|
+
this.pickingSystem = new PickingSystem();
|
|
4937
5120
|
this.sceneSystem = new SceneSystem(renderer);
|
|
4938
5121
|
this.cameraSystem = new CameraSystem(renderer);
|
|
4939
|
-
this.
|
|
5122
|
+
this.externalSystem = new ExternalSystem(this.pickingSystem);
|
|
4940
5123
|
}
|
|
4941
5124
|
/** {@link Scene} instance */
|
|
4942
5125
|
get scene() {
|
|
@@ -4944,10 +5127,10 @@ class ViewportSystem {
|
|
|
4944
5127
|
}
|
|
4945
5128
|
/** Current {@link Camera} instance */
|
|
4946
5129
|
get camera() {
|
|
4947
|
-
return this.cameraSystem.
|
|
5130
|
+
return this.renderer.isExternalMode ? this.externalSystem.camera : this.cameraSystem.camera;
|
|
4948
5131
|
}
|
|
4949
5132
|
/** {@link CameraController} instance */
|
|
4950
|
-
get
|
|
5133
|
+
get controller() {
|
|
4951
5134
|
return this.cameraSystem.controller;
|
|
4952
5135
|
}
|
|
4953
5136
|
/** Current camera zoom factor. */
|
|
@@ -4960,14 +5143,22 @@ class ViewportSystem {
|
|
|
4960
5143
|
}
|
|
4961
5144
|
/** Pixel to SVG scale factor */
|
|
4962
5145
|
get pxToSvgScale() {
|
|
4963
|
-
return 1 / (this.scaleFactor * this.zoomFactor);
|
|
5146
|
+
return this.renderer.isExternalMode ? this.externalSystem.pxToSvgScale(this.renderer.size) ?? this.prevPxToSvgScale ?? 1 : 1 / (this.scaleFactor * this.zoomFactor);
|
|
5147
|
+
}
|
|
5148
|
+
/**
|
|
5149
|
+
* Get bearing angle between current camera orientation and true north (in radians).
|
|
5150
|
+
* Angle is in range [0, 2π), going clockwise from north.
|
|
5151
|
+
*/
|
|
5152
|
+
get bearing() {
|
|
5153
|
+
const tau = Math.PI * 2;
|
|
5154
|
+
return MathUtils.euclideanModulo(-this.controller.azimuthAngle, tau);
|
|
4964
5155
|
}
|
|
4965
5156
|
/**
|
|
4966
5157
|
* Initializes the viewport and zoom bounds with the given scene definition.
|
|
4967
5158
|
* @param sceneDef {@link SceneDef} scene definition
|
|
4968
5159
|
*/
|
|
4969
5160
|
initViewport(sceneDef) {
|
|
4970
|
-
this.sceneSystem.initScene(sceneDef.viewbox);
|
|
5161
|
+
if (!this.renderer.isExternalMode) this.sceneSystem.initScene(sceneDef.viewbox);
|
|
4971
5162
|
this.cameraSystem.initCamera([0.1, sceneDef.viewbox.size.width > 1e5 ? 100 : 35]);
|
|
4972
5163
|
}
|
|
4973
5164
|
/** Updates the viewport when the renderer size changes. */
|
|
@@ -4987,71 +5178,99 @@ class ViewportSystem {
|
|
|
4987
5178
|
}
|
|
4988
5179
|
/**
|
|
4989
5180
|
* Gets the objects intersected by the raycaster.
|
|
4990
|
-
* @param
|
|
5181
|
+
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
4991
5182
|
* @returns Array of {@link Intersection} instances
|
|
4992
5183
|
*/
|
|
4993
|
-
getIntersectedObjects(
|
|
4994
|
-
|
|
4995
|
-
this.raycaster.setFromCamera(normalizedCoords, camera);
|
|
4996
|
-
const intersections = this.raycaster.intersectObject(scene, true).filter((i) => isVisible(i.object));
|
|
4997
|
-
return intersections;
|
|
5184
|
+
getIntersectedObjects(ndcCoords) {
|
|
5185
|
+
return this.pickingSystem.getIntersectedObjects(ndcCoords, this.scene, this.camera);
|
|
4998
5186
|
}
|
|
4999
5187
|
/**
|
|
5000
|
-
* Converts a point from
|
|
5001
|
-
* @param
|
|
5188
|
+
* Converts a point from model coordinates to world coordinates.
|
|
5189
|
+
* @param modelCoords Point in model coordinates
|
|
5190
|
+
* @param out Optional output vector
|
|
5002
5191
|
* @returns Point in world coordinates
|
|
5003
5192
|
*/
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
svg3D.applyMatrix4(this.sceneSystem.worldMatrix);
|
|
5007
|
-
return new Vector2(svg3D.x, svg3D.y);
|
|
5193
|
+
modelToWorld(modelCoords, out = new Vector3()) {
|
|
5194
|
+
return this.sceneSystem.modelToWorld(modelCoords, out);
|
|
5008
5195
|
}
|
|
5009
5196
|
/**
|
|
5010
|
-
* Converts a point from world coordinates to
|
|
5197
|
+
* Converts a point from world coordinates to model coordinates. Z axis is ignored.
|
|
5011
5198
|
* @param worldCoords Point in world coordinates
|
|
5012
|
-
* @
|
|
5199
|
+
* @param out Optional output vector
|
|
5200
|
+
* @returns Point in model coordinates
|
|
5013
5201
|
*/
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
return new Vector2(svgCoords.x, svgCoords.y);
|
|
5202
|
+
worldToModel(worldCoords, out = new Vector2()) {
|
|
5203
|
+
return this.sceneSystem.worldToModel(worldCoords, out);
|
|
5017
5204
|
}
|
|
5018
5205
|
/**
|
|
5019
|
-
* Converts a point from screen coordinates to
|
|
5020
|
-
* @param
|
|
5021
|
-
* @param
|
|
5022
|
-
* @returns Point in
|
|
5206
|
+
* Converts a point from screen coordinates to world space.
|
|
5207
|
+
* @param ndcCoords Point in NDC (normalized device coordinates)
|
|
5208
|
+
* @param out Optional output vector
|
|
5209
|
+
* @returns Point in world space
|
|
5023
5210
|
*/
|
|
5024
|
-
|
|
5025
|
-
this.
|
|
5026
|
-
this.raycaster.ray.intersectPlane(this.viewboxPlane, this.intersectionPoint);
|
|
5027
|
-
if (space === "svg") this.intersectionPoint.applyMatrix4(this.sceneSystem.inverseWorldMatrix);
|
|
5028
|
-
return { x: this.intersectionPoint.x, y: this.intersectionPoint.y };
|
|
5211
|
+
ndcToWorld(ndcCoords, out = new Vector3()) {
|
|
5212
|
+
return this.pickingSystem.intersectPlane(ndcCoords, this.camera, out);
|
|
5029
5213
|
}
|
|
5030
5214
|
/**
|
|
5031
|
-
*
|
|
5032
|
-
*
|
|
5033
|
-
* @
|
|
5215
|
+
* Convert canvas coordinates (relative to the canvas's top left corner)
|
|
5216
|
+
* to NDC (normalized device coordinates).
|
|
5217
|
+
* @param point object defining the coordinates relative to the canvas's top left corner
|
|
5218
|
+
* @param out Optional output vector
|
|
5219
|
+
* @returns Point in NDC space
|
|
5034
5220
|
*/
|
|
5035
|
-
|
|
5036
|
-
|
|
5221
|
+
canvasToNDC(point, out = new Vector2()) {
|
|
5222
|
+
const dpr = this.renderer.context.getPixelRatio();
|
|
5223
|
+
const [width, height] = [this.renderer.size[0] / dpr, this.renderer.size[1] / dpr];
|
|
5224
|
+
const uv = [point.x / width, point.y / height];
|
|
5225
|
+
const ndc = [uv[0] * 2 - 1, -uv[1] * 2 + 1];
|
|
5226
|
+
return out.set(MathUtils.clamp(ndc[0], -1, 1), MathUtils.clamp(ndc[1], -1, 1));
|
|
5227
|
+
}
|
|
5228
|
+
/**
|
|
5229
|
+
* Convert canvas coordinates (CSS pixels, relative to canvas top-left) to SVG coordinates.
|
|
5230
|
+
* @param point point in canvas space (CSS pixels)
|
|
5231
|
+
* @returns point in SVG coordinates or undefined if point is outside the SVG plane
|
|
5232
|
+
*/
|
|
5233
|
+
canvasToSvg(point) {
|
|
5234
|
+
const vec2 = new Vector2();
|
|
5235
|
+
const vec3 = new Vector3();
|
|
5236
|
+
const ndcPoint = this.canvasToNDC(point, vec2);
|
|
5237
|
+
const worldPoint = this.ndcToWorld(ndcPoint, vec3);
|
|
5238
|
+
if (!worldPoint) return;
|
|
5239
|
+
return this.worldToModel(worldPoint, vec2);
|
|
5037
5240
|
}
|
|
5038
5241
|
/**
|
|
5039
|
-
*
|
|
5242
|
+
* Set static part of an svg -> px transform matrix
|
|
5040
5243
|
* @param staticTransformMatrix static transform matrix to apply to the scene
|
|
5041
5244
|
*/
|
|
5042
|
-
|
|
5043
|
-
this.
|
|
5044
|
-
this.externalStaticTransformMatrix.fromArray(staticTransformMatrix);
|
|
5245
|
+
setStaticTransform(staticTransformMatrix) {
|
|
5246
|
+
this.externalSystem.setStaticTransform(staticTransformMatrix);
|
|
5045
5247
|
}
|
|
5046
5248
|
/**
|
|
5047
|
-
*
|
|
5048
|
-
* @param dynamicTransformMatrix dynamic transform matrix
|
|
5249
|
+
* Set dynamic part of an svg -> px transform matrix. Should be called every frame.
|
|
5250
|
+
* @param dynamicTransformMatrix dynamic transform matrix (changes every frame)
|
|
5049
5251
|
*/
|
|
5050
|
-
|
|
5051
|
-
this.
|
|
5052
|
-
|
|
5252
|
+
setDynamicTransform(dynamicTransformMatrix) {
|
|
5253
|
+
this.externalSystem.setDynamicTransform(dynamicTransformMatrix);
|
|
5254
|
+
}
|
|
5255
|
+
/**
|
|
5256
|
+
* Calculates the camera distance from the scene's plane for a given zoom factor.
|
|
5257
|
+
* @param zoomFactor Zoom factor
|
|
5258
|
+
* @returns Corresponding camera distance on the Z axis
|
|
5259
|
+
*/
|
|
5260
|
+
zoomFactorToDistance(zoomFactor) {
|
|
5261
|
+
return this.cameraSystem.zoomFactorToDistance(zoomFactor);
|
|
5053
5262
|
}
|
|
5054
5263
|
}
|
|
5264
|
+
function asViewportAPI(viewportSystem) {
|
|
5265
|
+
return {
|
|
5266
|
+
canvasToSvg: viewportSystem.canvasToSvg.bind(viewportSystem),
|
|
5267
|
+
setStaticTransform: viewportSystem.setStaticTransform.bind(viewportSystem),
|
|
5268
|
+
setDynamicTransform: viewportSystem.setDynamicTransform.bind(viewportSystem)
|
|
5269
|
+
};
|
|
5270
|
+
}
|
|
5271
|
+
function eventToCanvas(event) {
|
|
5272
|
+
return { x: event.offsetX, y: event.offsetY };
|
|
5273
|
+
}
|
|
5055
5274
|
class ControlsSystem {
|
|
5056
5275
|
/**
|
|
5057
5276
|
* @param renderer {@link Renderer} instance
|
|
@@ -5063,7 +5282,7 @@ class ControlsSystem {
|
|
|
5063
5282
|
this.renderer = renderer;
|
|
5064
5283
|
this.viewportSystem = viewportSystem;
|
|
5065
5284
|
this.interactionsSystem = interactionsSystem;
|
|
5066
|
-
this.controller = viewportSystem.
|
|
5285
|
+
this.controller = viewportSystem.controller;
|
|
5067
5286
|
}
|
|
5068
5287
|
/** Gesture handlers for camera controls. */
|
|
5069
5288
|
get handlers() {
|
|
@@ -5092,7 +5311,9 @@ class ControlsSystem {
|
|
|
5092
5311
|
const dpr = this.renderer.context.getPixelRatio();
|
|
5093
5312
|
const visibleRect = this.renderer.visibleRect;
|
|
5094
5313
|
const bearingAngle = -this.controller.azimuthAngle;
|
|
5095
|
-
const
|
|
5314
|
+
const worldMin = this.viewportSystem.modelToWorld(rect.min);
|
|
5315
|
+
const worldMax = this.viewportSystem.modelToWorld(rect.max);
|
|
5316
|
+
const worldRect = new Rect(worldMin, worldMax);
|
|
5096
5317
|
const worldPolygon = Polygon.fromRect(worldRect).rotate(bearingAngle, worldRect.center);
|
|
5097
5318
|
const xValues = worldPolygon.vertices.map((p) => p.x);
|
|
5098
5319
|
const yValues = worldPolygon.vertices.map((p) => p.y);
|
|
@@ -5100,7 +5321,7 @@ class ControlsSystem {
|
|
|
5100
5321
|
[Math.min(...xValues), Math.min(...yValues)],
|
|
5101
5322
|
[Math.max(...xValues), Math.max(...yValues)]
|
|
5102
5323
|
);
|
|
5103
|
-
const targetRect = visibleRect ? new Rect(visibleRect.min.clone().multiplyScalar(dpr), visibleRect.max.clone().multiplyScalar(dpr)) : new Rect([0, 0], this.renderer.size);
|
|
5324
|
+
const targetRect = visibleRect ? new Rect(visibleRect.min.clone().multiplyScalar(dpr), visibleRect.max.clone().multiplyScalar(dpr)) : new Rect([0, 0], [...this.renderer.size]);
|
|
5104
5325
|
if (paddingPercent) targetRect.addPadding(targetRect.size.x * paddingPercent, targetRect.size.y * paddingPercent);
|
|
5105
5326
|
const zoomByWidth = targetRect.size.x / sourceRect.size.x;
|
|
5106
5327
|
const zoomByHeight = targetRect.size.y / sourceRect.size.y;
|
|
@@ -5125,8 +5346,8 @@ class ControlsSystem {
|
|
|
5125
5346
|
* @returns Promise that resolves when the pan animation completes
|
|
5126
5347
|
*/
|
|
5127
5348
|
panBy(x, y, immediate) {
|
|
5128
|
-
const svgOrigin = this.viewportSystem.
|
|
5129
|
-
const svgOffset = this.viewportSystem.
|
|
5349
|
+
const svgOrigin = this.viewportSystem.modelToWorld({ x: 0, y: 0 });
|
|
5350
|
+
const svgOffset = this.viewportSystem.modelToWorld({ x, y });
|
|
5130
5351
|
const worldOffset = new Vector3(svgOffset.x - svgOrigin.x, svgOffset.y - svgOrigin.y, 0);
|
|
5131
5352
|
const currentTarget = this.controller.getTarget(new Vector3());
|
|
5132
5353
|
const newTarget = currentTarget.add(worldOffset);
|
|
@@ -5140,7 +5361,7 @@ class ControlsSystem {
|
|
|
5140
5361
|
* @returns Promise that resolves when the pan animation completes
|
|
5141
5362
|
*/
|
|
5142
5363
|
panTo(x, y, immediate) {
|
|
5143
|
-
const worldCoords = this.viewportSystem.
|
|
5364
|
+
const worldCoords = this.viewportSystem.modelToWorld({ x, y });
|
|
5144
5365
|
return this.controller.moveTo(worldCoords.x, worldCoords.y, 0, !immediate);
|
|
5145
5366
|
}
|
|
5146
5367
|
/**
|
|
@@ -5238,8 +5459,8 @@ class ControlsSystem {
|
|
|
5238
5459
|
this.controller.setBoundary(void 0);
|
|
5239
5460
|
return;
|
|
5240
5461
|
}
|
|
5241
|
-
const worldMin = this.viewportSystem.
|
|
5242
|
-
const worldMax = this.viewportSystem.
|
|
5462
|
+
const worldMin = this.viewportSystem.modelToWorld(rect.min);
|
|
5463
|
+
const worldMax = this.viewportSystem.modelToWorld(rect.max);
|
|
5243
5464
|
const boundary = new Box3(new Vector3(worldMin.x, worldMin.y, 0), new Vector3(worldMax.x, worldMax.y, 0));
|
|
5244
5465
|
this.controller.setBoundary(boundary);
|
|
5245
5466
|
}
|
|
@@ -5249,9 +5470,9 @@ class ControlsSystem {
|
|
|
5249
5470
|
*/
|
|
5250
5471
|
getCameraState() {
|
|
5251
5472
|
const target = this.controller.getTarget(new Vector3());
|
|
5252
|
-
const center = this.viewportSystem.
|
|
5473
|
+
const center = this.viewportSystem.worldToModel(target);
|
|
5253
5474
|
const zoom = this.viewportSystem.zoomFactor;
|
|
5254
|
-
const roll = this.
|
|
5475
|
+
const roll = this.viewportSystem.bearing * RAD2DEG;
|
|
5255
5476
|
const pitch = this.controller.polarAngle * RAD2DEG;
|
|
5256
5477
|
const ptScale = this.viewportSystem.pxToSvgScale;
|
|
5257
5478
|
return { center, zoom, roll, pitch, ptScale };
|
|
@@ -5348,21 +5569,6 @@ function asEventAPI(system) {
|
|
|
5348
5569
|
clear: system.clear.bind(system)
|
|
5349
5570
|
};
|
|
5350
5571
|
}
|
|
5351
|
-
function clientToCanvas(event, domElement, target) {
|
|
5352
|
-
target = target ?? new Vector2();
|
|
5353
|
-
const { left, top } = domElement.getBoundingClientRect();
|
|
5354
|
-
const clientX = "clientX" in event ? event.clientX : event.x;
|
|
5355
|
-
const clientY = "clientY" in event ? event.clientY : event.y;
|
|
5356
|
-
target.set(clientX - left, clientY - top);
|
|
5357
|
-
return target;
|
|
5358
|
-
}
|
|
5359
|
-
function canvasToNDC(coordinates, canvas, target) {
|
|
5360
|
-
target = target ?? new Vector2();
|
|
5361
|
-
const { width, height } = canvas.getBoundingClientRect();
|
|
5362
|
-
const uv = [coordinates.x / width, coordinates.y / height];
|
|
5363
|
-
target.set(uv[0] * 2 - 1, -uv[1] * 2 + 1);
|
|
5364
|
-
return target;
|
|
5365
|
-
}
|
|
5366
5572
|
class Handler {
|
|
5367
5573
|
/**
|
|
5368
5574
|
* @param viewportSystem The viewport system instance
|
|
@@ -5377,7 +5583,7 @@ class Handler {
|
|
|
5377
5583
|
this.viewportSystem = viewportSystem;
|
|
5378
5584
|
this.domElement = domElement;
|
|
5379
5585
|
this.eventManager = eventManager;
|
|
5380
|
-
this.controller = viewportSystem.
|
|
5586
|
+
this.controller = viewportSystem.controller;
|
|
5381
5587
|
}
|
|
5382
5588
|
/**
|
|
5383
5589
|
* Per-frame update for this handler.
|
|
@@ -5588,13 +5794,15 @@ class PitchHandler extends Handler {
|
|
|
5588
5794
|
__publicField(this, "isValid");
|
|
5589
5795
|
__publicField(this, "firstMove");
|
|
5590
5796
|
__publicField(this, "lastPoints");
|
|
5797
|
+
__publicField(this, "p0", new Vector2());
|
|
5798
|
+
__publicField(this, "p1", new Vector2());
|
|
5591
5799
|
__publicField(this, "prevTwoFingerAction");
|
|
5592
5800
|
__publicField(this, "onPitchStart", (e) => {
|
|
5593
5801
|
const pointers = e.pointers.sort((a, b) => a.pointerId - b.pointerId);
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
this.lastPoints = [p0, p1];
|
|
5597
|
-
if (this.isVertical(p0.
|
|
5802
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5803
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5804
|
+
this.lastPoints = [new Vector2().copy(this.p0), new Vector2().copy(this.p1)];
|
|
5805
|
+
if (this.isVertical(this.p0.sub(this.p1))) {
|
|
5598
5806
|
this.isValid = false;
|
|
5599
5807
|
}
|
|
5600
5808
|
});
|
|
@@ -5609,18 +5817,19 @@ class PitchHandler extends Handler {
|
|
|
5609
5817
|
const lastPoints = this.lastPoints;
|
|
5610
5818
|
if (!lastPoints) return;
|
|
5611
5819
|
const pointers = e.pointers.sort((a, b) => a.pointerId - b.pointerId);
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
this.isValid = this.gestureBeginsVertically(
|
|
5820
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5821
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5822
|
+
this.p0.sub(lastPoints[0]);
|
|
5823
|
+
this.p1.sub(lastPoints[1]);
|
|
5824
|
+
this.isValid = this.gestureBeginsVertically(this.p0, this.p1, e.timeStamp);
|
|
5617
5825
|
if (!this.isValid) return;
|
|
5618
5826
|
if (this.prevTwoFingerAction === void 0) {
|
|
5619
5827
|
this.prevTwoFingerAction = this.controller.touches.two;
|
|
5620
5828
|
this.controller.touches.two = CameraController.ACTION.NONE;
|
|
5621
5829
|
}
|
|
5622
|
-
this.
|
|
5623
|
-
|
|
5830
|
+
lastPoints[0].add(this.p0);
|
|
5831
|
+
lastPoints[1].add(this.p1);
|
|
5832
|
+
const yDeltaAverage = (this.p0.y + this.p1.y) / 2;
|
|
5624
5833
|
const degreesPerPixelMoved = -0.5;
|
|
5625
5834
|
const deltaAngle = yDeltaAverage * degreesPerPixelMoved * DEG2RAD$1;
|
|
5626
5835
|
void this.controller.rotatePolarTo(this.controller.polarAngle + deltaAngle, false);
|
|
@@ -5665,12 +5874,10 @@ class PitchHandler extends Handler {
|
|
|
5665
5874
|
updatePolarAngles() {
|
|
5666
5875
|
this.controller.minPolarAngle = this.minPitch * DEG2RAD$1;
|
|
5667
5876
|
this.controller.maxPolarAngle = this.maxPitch * DEG2RAD$1;
|
|
5668
|
-
if (this.controller.polarAngle < this.controller.minPolarAngle)
|
|
5877
|
+
if (this.controller.polarAngle < this.controller.minPolarAngle)
|
|
5669
5878
|
void this.controller.rotatePolarTo(this.controller.minPolarAngle, false);
|
|
5670
|
-
|
|
5671
|
-
if (this.controller.polarAngle > this.controller.maxPolarAngle) {
|
|
5879
|
+
if (this.controller.polarAngle > this.controller.maxPolarAngle)
|
|
5672
5880
|
void this.controller.rotatePolarTo(this.controller.maxPolarAngle, false);
|
|
5673
|
-
}
|
|
5674
5881
|
}
|
|
5675
5882
|
gestureBeginsVertically(vectorA, vectorB, timeStamp) {
|
|
5676
5883
|
if (this.isValid !== void 0) return this.isValid;
|
|
@@ -5702,35 +5909,36 @@ class RollHandler extends Handler {
|
|
|
5702
5909
|
__publicField(this, "rotationThreshold", 25);
|
|
5703
5910
|
// Threshold tracking (Mapbox-style)
|
|
5704
5911
|
__publicField(this, "startVector");
|
|
5705
|
-
__publicField(this, "vector");
|
|
5912
|
+
__publicField(this, "vector", new Vector2());
|
|
5913
|
+
__publicField(this, "p0", new Vector2());
|
|
5914
|
+
__publicField(this, "p1", new Vector2());
|
|
5706
5915
|
__publicField(this, "minDiameter", 0);
|
|
5707
5916
|
__publicField(this, "prevAngle", 0);
|
|
5708
5917
|
// Camera and pivot vectors
|
|
5918
|
+
__publicField(this, "pivotNDC", new Vector2());
|
|
5709
5919
|
__publicField(this, "pivotWorld", new Vector3());
|
|
5710
5920
|
__publicField(this, "targetWorld", new Vector3());
|
|
5711
5921
|
__publicField(this, "cameraPosition", new Vector3());
|
|
5712
5922
|
__publicField(this, "cameraForward", new Vector3());
|
|
5713
5923
|
__publicField(this, "rotationMatrix", new Matrix4());
|
|
5714
5924
|
__publicField(this, "onRotateStart", (e) => {
|
|
5715
|
-
console.log("onRotateStart");
|
|
5716
5925
|
const pointers = e.pointers;
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
this.startVector = p0.sub(p1);
|
|
5926
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5927
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5928
|
+
this.startVector = new Vector2().copy(this.p0).sub(this.p1);
|
|
5720
5929
|
this.minDiameter = this.startVector.length();
|
|
5721
5930
|
});
|
|
5722
5931
|
__publicField(this, "onRotateEnd", () => {
|
|
5723
5932
|
this.isRolling = false;
|
|
5724
5933
|
this.startVector = void 0;
|
|
5725
|
-
this.vector = void 0;
|
|
5726
5934
|
this.minDiameter = 0;
|
|
5727
5935
|
});
|
|
5728
5936
|
__publicField(this, "onRotate", (e) => {
|
|
5729
5937
|
const pointers = e.pointers;
|
|
5730
5938
|
if (!this.isRolling) {
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
this.vector
|
|
5939
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5940
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5941
|
+
this.vector.copy(this.p0).sub(this.p1);
|
|
5734
5942
|
if (this.isBelowThreshold(this.vector)) return;
|
|
5735
5943
|
this.isRolling = true;
|
|
5736
5944
|
this.prevAngle = e.rotation;
|
|
@@ -5738,7 +5946,7 @@ class RollHandler extends Handler {
|
|
|
5738
5946
|
const deltaAngle = (e.rotation - this.prevAngle) * -DEG2RAD$1;
|
|
5739
5947
|
this.prevAngle = e.rotation;
|
|
5740
5948
|
if (Math.abs(deltaAngle) < 1e-3) return;
|
|
5741
|
-
this.setPivot(e);
|
|
5949
|
+
if (!this.setPivot(e)) return;
|
|
5742
5950
|
this.rotationMatrix.makeRotationZ(deltaAngle);
|
|
5743
5951
|
this.cameraPosition.sub(this.pivotWorld).applyMatrix4(this.rotationMatrix).add(this.pivotWorld);
|
|
5744
5952
|
this.cameraForward.applyMatrix4(this.rotationMatrix);
|
|
@@ -5786,13 +5994,12 @@ class RollHandler extends Handler {
|
|
|
5786
5994
|
this.eventManager.off("rotateend", this.onRotateEnd);
|
|
5787
5995
|
}
|
|
5788
5996
|
setPivot(e) {
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
const pivotWorld2D = this.viewportSystem.screenTo("world", pivotNDC);
|
|
5792
|
-
this.pivotWorld.set(pivotWorld2D.x, pivotWorld2D.y, 0);
|
|
5997
|
+
this.viewportSystem.canvasToNDC(e.offsetCenter, this.pivotNDC);
|
|
5998
|
+
if (!this.viewportSystem.ndcToWorld(this.pivotNDC, this.pivotWorld)) return false;
|
|
5793
5999
|
this.controller.getPosition(this.cameraPosition);
|
|
5794
6000
|
this.controller.getTarget(this.targetWorld);
|
|
5795
6001
|
this.cameraForward.copy(this.targetWorld).sub(this.cameraPosition);
|
|
6002
|
+
return true;
|
|
5796
6003
|
}
|
|
5797
6004
|
/**
|
|
5798
6005
|
* Check if rotation is below threshold (Mapbox-style).
|
|
@@ -5812,7 +6019,6 @@ class RollHandler extends Handler {
|
|
|
5812
6019
|
const startVector = this.startVector;
|
|
5813
6020
|
if (!startVector) return false;
|
|
5814
6021
|
const bearingDeltaSinceStart = this.getBearingDelta(vector, startVector);
|
|
5815
|
-
console.log("bearingDeltaSinceStart", vector, startVector);
|
|
5816
6022
|
return Math.abs(bearingDeltaSinceStart) < threshold;
|
|
5817
6023
|
}
|
|
5818
6024
|
/**
|
|
@@ -5824,16 +6030,6 @@ class RollHandler extends Handler {
|
|
|
5824
6030
|
getBearingDelta(a, b) {
|
|
5825
6031
|
return a.angleTo(b) * RAD2DEG;
|
|
5826
6032
|
}
|
|
5827
|
-
/**
|
|
5828
|
-
* Normalize angle to be between -π and π
|
|
5829
|
-
* @param angle Angle in radians
|
|
5830
|
-
* @returns Normalized angle in radians
|
|
5831
|
-
*/
|
|
5832
|
-
normalizeAngle(angle) {
|
|
5833
|
-
while (angle > Math.PI) angle -= 2 * Math.PI;
|
|
5834
|
-
while (angle < -Math.PI) angle += 2 * Math.PI;
|
|
5835
|
-
return angle;
|
|
5836
|
-
}
|
|
5837
6033
|
}
|
|
5838
6034
|
class ZoomHandler extends Handler {
|
|
5839
6035
|
reset(enableTransition = true) {
|
|
@@ -5868,12 +6064,16 @@ class InteractionsSystem {
|
|
|
5868
6064
|
__publicField(this, "handlers");
|
|
5869
6065
|
__publicField(this, "handlerArray");
|
|
5870
6066
|
__publicField(this, "canvas");
|
|
5871
|
-
__publicField(this, "mousePointer", new Vector2());
|
|
5872
6067
|
__publicField(this, "eventManager");
|
|
6068
|
+
__publicField(this, "mousePointerNDC", new Vector2());
|
|
6069
|
+
__publicField(this, "mousePointerWorld", new Vector3());
|
|
6070
|
+
__publicField(this, "mousePointerModel", new Vector2());
|
|
6071
|
+
__publicField(this, "canvasListeners");
|
|
5873
6072
|
__publicField(this, "dragStart");
|
|
5874
6073
|
__publicField(this, "dragThreshold", 15);
|
|
5875
6074
|
__publicField(this, "isDragging", false);
|
|
5876
6075
|
__publicField(this, "prevBearing", 0);
|
|
6076
|
+
this.renderer = renderer;
|
|
5877
6077
|
this.events = events;
|
|
5878
6078
|
this.viewportSystem = viewportSystem;
|
|
5879
6079
|
this.layerSystem = layerSystem;
|
|
@@ -5881,8 +6081,6 @@ class InteractionsSystem {
|
|
|
5881
6081
|
this.eventManager = new EventManager(this.canvas, {
|
|
5882
6082
|
recognizers: [Rotate, [Pan, { event: "pitch", pointers: 2 }, "rotate"]]
|
|
5883
6083
|
});
|
|
5884
|
-
this.configureCameraControls();
|
|
5885
|
-
this.attachCanvasListeners();
|
|
5886
6084
|
const handlers = {
|
|
5887
6085
|
pan: new PanHandler(viewportSystem, this.canvas, this.eventManager),
|
|
5888
6086
|
zoom: new ZoomHandler(viewportSystem, this.canvas, this.eventManager),
|
|
@@ -5891,17 +6089,32 @@ class InteractionsSystem {
|
|
|
5891
6089
|
};
|
|
5892
6090
|
this.handlers = handlers;
|
|
5893
6091
|
this.handlerArray = Object.values(handlers);
|
|
5894
|
-
this.handlers.pan.enable();
|
|
5895
|
-
this.handlers.zoom.enable();
|
|
5896
6092
|
}
|
|
5897
6093
|
/**
|
|
5898
|
-
*
|
|
5899
|
-
*
|
|
6094
|
+
* Initializes the interactions system and attaches event listeners.
|
|
6095
|
+
* Should be called once as part of renderer initialization.
|
|
5900
6096
|
*/
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
6097
|
+
init() {
|
|
6098
|
+
this.attachCanvasListeners();
|
|
6099
|
+
if (!this.renderer.isExternalMode) {
|
|
6100
|
+
const controller = this.viewportSystem.controller;
|
|
6101
|
+
controller.connect(this.canvas);
|
|
6102
|
+
controller.addEventListener("transitionstart", () => {
|
|
6103
|
+
this.events.emit("navigation:change");
|
|
6104
|
+
});
|
|
6105
|
+
this.handlers.pan.enable();
|
|
6106
|
+
this.handlers.zoom.enable();
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
/**
|
|
6110
|
+
* Disposes the interactions system.
|
|
6111
|
+
* WARNING: This method is final and cannot be undone. To re-enable interactions, create a new renderer instance.
|
|
6112
|
+
*/
|
|
6113
|
+
dispose() {
|
|
6114
|
+
for (const handler of this.handlerArray) handler.disable();
|
|
6115
|
+
this.viewportSystem.controller.disconnect();
|
|
6116
|
+
this.detachCanvasListeners();
|
|
6117
|
+
this.eventManager.destroy();
|
|
5905
6118
|
}
|
|
5906
6119
|
/**
|
|
5907
6120
|
* Update camera position and directions.
|
|
@@ -5910,77 +6123,67 @@ class InteractionsSystem {
|
|
|
5910
6123
|
* @returns true if re-rendering is needed
|
|
5911
6124
|
*/
|
|
5912
6125
|
updateControls(delta) {
|
|
5913
|
-
let needsUpdate = this.viewportSystem.
|
|
6126
|
+
let needsUpdate = this.viewportSystem.controller.update(delta);
|
|
5914
6127
|
for (const handler of this.handlerArray) {
|
|
5915
6128
|
if (handler.isEnabled()) {
|
|
5916
6129
|
needsUpdate = handler.update(delta) || needsUpdate;
|
|
5917
6130
|
}
|
|
5918
6131
|
}
|
|
5919
|
-
if (this.bearing !== this.prevBearing) {
|
|
5920
|
-
this.prevBearing = this.bearing;
|
|
5921
|
-
this.events.emit("navigation:roll", this.bearing);
|
|
6132
|
+
if (this.viewportSystem.bearing !== this.prevBearing) {
|
|
6133
|
+
this.prevBearing = this.viewportSystem.bearing;
|
|
6134
|
+
this.events.emit("navigation:roll", this.viewportSystem.bearing);
|
|
5922
6135
|
}
|
|
5923
6136
|
return needsUpdate;
|
|
5924
6137
|
}
|
|
5925
|
-
/** Disconnect the interactions system. */
|
|
5926
|
-
disconnect() {
|
|
5927
|
-
this.viewportSystem.cameraController.disconnect();
|
|
5928
|
-
for (const handler of this.handlerArray) {
|
|
5929
|
-
handler.disable();
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
configureCameraControls() {
|
|
5933
|
-
const controller = this.viewportSystem.cameraController;
|
|
5934
|
-
controller.draggingSmoothTime = 0;
|
|
5935
|
-
controller.dollyToCursor = true;
|
|
5936
|
-
controller.mouseButtons = {
|
|
5937
|
-
left: CameraController.ACTION.NONE,
|
|
5938
|
-
middle: CameraController.ACTION.NONE,
|
|
5939
|
-
right: CameraController.ACTION.NONE,
|
|
5940
|
-
wheel: CameraController.ACTION.NONE
|
|
5941
|
-
};
|
|
5942
|
-
controller.touches = {
|
|
5943
|
-
one: CameraController.ACTION.NONE,
|
|
5944
|
-
two: CameraController.ACTION.NONE,
|
|
5945
|
-
three: CameraController.ACTION.NONE
|
|
5946
|
-
};
|
|
5947
|
-
controller.connect(this.canvas);
|
|
5948
|
-
controller.addEventListener("transitionstart", () => {
|
|
5949
|
-
this.events.emit("navigation:change");
|
|
5950
|
-
});
|
|
5951
|
-
}
|
|
5952
6138
|
attachCanvasListeners() {
|
|
5953
|
-
this.
|
|
6139
|
+
if (this.canvasListeners) return;
|
|
6140
|
+
const pointerdown = (event) => {
|
|
5954
6141
|
this.isDragging = false;
|
|
5955
6142
|
this.dragStart = { x: event.offsetX, y: event.offsetY };
|
|
5956
|
-
}
|
|
5957
|
-
|
|
6143
|
+
};
|
|
6144
|
+
const pointerup = (event) => {
|
|
5958
6145
|
if (!this.dragStart) return;
|
|
5959
6146
|
const dX = event.offsetX - this.dragStart.x;
|
|
5960
6147
|
const dY = event.offsetY - this.dragStart.y;
|
|
5961
6148
|
this.dragStart = void 0;
|
|
5962
6149
|
if (dX * dX + dY * dY > this.dragThreshold) this.isDragging = true;
|
|
5963
|
-
}
|
|
6150
|
+
};
|
|
5964
6151
|
const mouseEventsMap = {
|
|
5965
6152
|
mousemove: "pointer:move",
|
|
5966
6153
|
mouseout: "pointer:out",
|
|
5967
6154
|
click: "pointer:click"
|
|
5968
6155
|
};
|
|
5969
6156
|
const mouseEventKeys = Object.keys(mouseEventsMap);
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
6157
|
+
const sharedMouseHandler = (type, event) => {
|
|
6158
|
+
const eventType = mouseEventsMap[type];
|
|
6159
|
+
const isDragging = type === "click" && this.isDragging;
|
|
6160
|
+
const hasListeners = this.events.hasListeners(eventType);
|
|
6161
|
+
if (isDragging || !hasListeners) return;
|
|
6162
|
+
const mousePointer = eventToCanvas(event);
|
|
6163
|
+
this.viewportSystem.canvasToNDC(mousePointer, this.mousePointerNDC);
|
|
6164
|
+
if (!this.viewportSystem.ndcToWorld(this.mousePointerNDC, this.mousePointerWorld)) return;
|
|
6165
|
+
const intersections = this.viewportSystem.getIntersectedObjects(this.mousePointerNDC);
|
|
6166
|
+
const point = this.viewportSystem.worldToModel(this.mousePointerWorld, this.mousePointerModel);
|
|
6167
|
+
const defs = this.layerSystem.getIntersectedDefs(intersections);
|
|
6168
|
+
this.events.emit(eventType, { event, point: { x: point.x, y: point.y }, defs });
|
|
6169
|
+
};
|
|
6170
|
+
const mousemove = (event) => sharedMouseHandler("mousemove", event);
|
|
6171
|
+
const mouseout = (event) => sharedMouseHandler("mouseout", event);
|
|
6172
|
+
const click = (event) => sharedMouseHandler("click", event);
|
|
6173
|
+
const canvasListeners = { pointerdown, pointerup, mousemove, mouseout, click };
|
|
6174
|
+
this.canvas.addEventListener("pointerdown", pointerdown);
|
|
6175
|
+
this.canvas.addEventListener("pointerup", pointerup);
|
|
6176
|
+
mouseEventKeys.forEach((type) => this.canvas.addEventListener(type, canvasListeners[type]));
|
|
6177
|
+
this.canvasListeners = canvasListeners;
|
|
6178
|
+
}
|
|
6179
|
+
detachCanvasListeners() {
|
|
6180
|
+
if (!this.canvasListeners) return;
|
|
6181
|
+
this.canvas.removeEventListener("pointerdown", this.canvasListeners.pointerdown);
|
|
6182
|
+
this.canvas.removeEventListener("pointerup", this.canvasListeners.pointerup);
|
|
6183
|
+
this.canvas.removeEventListener("mousemove", this.canvasListeners.mousemove);
|
|
6184
|
+
this.canvas.removeEventListener("mouseout", this.canvasListeners.mouseout);
|
|
6185
|
+
this.canvas.removeEventListener("click", this.canvasListeners.click);
|
|
6186
|
+
this.canvasListeners = void 0;
|
|
5984
6187
|
}
|
|
5985
6188
|
}
|
|
5986
6189
|
class Renderer {
|
|
@@ -6000,13 +6203,17 @@ class Renderer {
|
|
|
6000
6203
|
__publicField(this, "viewportSystem");
|
|
6001
6204
|
__publicField(this, "interactionsSystem");
|
|
6002
6205
|
__publicField(this, "controlsSystem");
|
|
6206
|
+
__publicField(this, "controlsAPI");
|
|
6207
|
+
__publicField(this, "eventsAPI");
|
|
6208
|
+
__publicField(this, "viewportAPI");
|
|
6003
6209
|
__publicField(this, "clock");
|
|
6004
6210
|
__publicField(this, "renderer");
|
|
6005
|
-
__publicField(this, "
|
|
6211
|
+
__publicField(this, "visibleRectValue");
|
|
6212
|
+
__publicField(this, "memoryInfoExtension", null);
|
|
6213
|
+
__publicField(this, "memoryInfo");
|
|
6214
|
+
__publicField(this, "initialized", false);
|
|
6215
|
+
__publicField(this, "disposed", false);
|
|
6006
6216
|
__publicField(this, "needsRedraw", true);
|
|
6007
|
-
__publicField(this, "memoryInfoExtension");
|
|
6008
|
-
__publicField(this, "memoryInfo", "");
|
|
6009
|
-
var _a2, _b;
|
|
6010
6217
|
const { canvas, gl, debugLog = false, ui } = opts;
|
|
6011
6218
|
this.canvas = canvas;
|
|
6012
6219
|
this.debugLog = debugLog;
|
|
@@ -6021,42 +6228,84 @@ class Renderer {
|
|
|
6021
6228
|
this.renderer = new WebGLRenderer(rendererOptions);
|
|
6022
6229
|
this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight, false);
|
|
6023
6230
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
6231
|
+
this.renderer.autoClear = !this.isExternalMode;
|
|
6024
6232
|
this.eventSystem = new EventSystem();
|
|
6025
6233
|
this.viewportSystem = new ViewportSystem(this, this.eventSystem);
|
|
6026
6234
|
this.layerSystem = new LayerSystem(this);
|
|
6027
6235
|
this.interactionsSystem = new InteractionsSystem(this, this.eventSystem, this.viewportSystem, this.layerSystem);
|
|
6028
6236
|
this.controlsSystem = new ControlsSystem(this, this.viewportSystem, this.interactionsSystem);
|
|
6029
|
-
this.
|
|
6030
|
-
this.canvas.addEventListener("webglcontextlost", (e) => this.onContextLost(e), false);
|
|
6031
|
-
this.canvas.addEventListener("webglcontextrestored", (e) => this.onContextRestored(e), false);
|
|
6032
|
-
void ((_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.init(this.renderer.getContext()));
|
|
6237
|
+
this.initContext(this.renderer.getContext());
|
|
6033
6238
|
BatchedMesh.useMultiDraw = this.renderer.extensions.has("WEBGL_multi_draw");
|
|
6034
6239
|
}
|
|
6035
6240
|
/**
|
|
6036
6241
|
* {@link ControlsAPI} instance for controlling the viewport
|
|
6037
6242
|
*/
|
|
6038
6243
|
get controls() {
|
|
6039
|
-
|
|
6244
|
+
if (this.controlsAPI) return this.controlsAPI;
|
|
6245
|
+
const api = asControlsAPI(this.controlsSystem);
|
|
6246
|
+
const guard = (name) => this.assertInitialized(`controls.${name}`) && this.assertNotDisposed(`controls.${name}`) && this.assertNotExternalMode(`controls.${name}`);
|
|
6247
|
+
this.controlsAPI = {
|
|
6248
|
+
handlers: api.handlers,
|
|
6249
|
+
configure: api.configure,
|
|
6250
|
+
zoomBy: guardFn(guard, api.zoomBy, Promise.resolve()),
|
|
6251
|
+
zoomTo: guardFn(guard, api.zoomTo, Promise.resolve()),
|
|
6252
|
+
panBy: guardFn(guard, api.panBy, Promise.resolve()),
|
|
6253
|
+
panTo: guardFn(guard, api.panTo, Promise.resolve()),
|
|
6254
|
+
rollBy: guardFn(guard, api.rollBy, Promise.resolve()),
|
|
6255
|
+
rollTo: guardFn(guard, api.rollTo, Promise.resolve()),
|
|
6256
|
+
pitchBy: guardFn(guard, api.pitchBy, Promise.resolve()),
|
|
6257
|
+
pitchTo: guardFn(guard, api.pitchTo, Promise.resolve()),
|
|
6258
|
+
resetCamera: guardFn(guard, api.resetCamera, Promise.resolve()),
|
|
6259
|
+
setCameraBounds: guardFn(guard, api.setCameraBounds),
|
|
6260
|
+
getCameraState: guardFn(guard, api.getCameraState, {
|
|
6261
|
+
center: { x: 0, y: 0 },
|
|
6262
|
+
roll: 0,
|
|
6263
|
+
pitch: 0,
|
|
6264
|
+
zoom: 1,
|
|
6265
|
+
ptScale: 1
|
|
6266
|
+
})
|
|
6267
|
+
};
|
|
6268
|
+
return this.controlsAPI;
|
|
6040
6269
|
}
|
|
6041
6270
|
/**
|
|
6042
6271
|
* {@link EventsAPI} instance for subscribing to internal events
|
|
6043
6272
|
*/
|
|
6044
6273
|
get events() {
|
|
6045
|
-
|
|
6274
|
+
if (this.eventsAPI) return this.eventsAPI;
|
|
6275
|
+
this.eventsAPI = asEventAPI(this.eventSystem);
|
|
6276
|
+
return this.eventsAPI;
|
|
6277
|
+
}
|
|
6278
|
+
/**
|
|
6279
|
+
* {@link ViewportAPI} instance for view transforms and external transforms.
|
|
6280
|
+
*/
|
|
6281
|
+
get viewport() {
|
|
6282
|
+
if (this.viewportAPI) return this.viewportAPI;
|
|
6283
|
+
const api = asViewportAPI(this.viewportSystem);
|
|
6284
|
+
const guard = (name) => this.assertInitialized(`viewport.${name}`) && this.assertNotDisposed(`viewport.${name}`);
|
|
6285
|
+
const guardExternal = (name) => guard(name) && this.assertExternalMode(`viewport.${name}`);
|
|
6286
|
+
this.viewportAPI = {
|
|
6287
|
+
canvasToSvg: guardFn(guard, api.canvasToSvg, { x: 0, y: 0 }),
|
|
6288
|
+
setStaticTransform: guardFn(guardExternal, api.setStaticTransform),
|
|
6289
|
+
setDynamicTransform: guardFn(guardExternal, api.setDynamicTransform)
|
|
6290
|
+
};
|
|
6291
|
+
return this.viewportAPI;
|
|
6046
6292
|
}
|
|
6047
6293
|
/**
|
|
6048
6294
|
* Optional sub-rectangle of the viewport that is used for positioning scene's viewbox
|
|
6049
6295
|
*/
|
|
6050
6296
|
get visibleRect() {
|
|
6051
|
-
return this.
|
|
6297
|
+
return this.visibleRectValue;
|
|
6052
6298
|
}
|
|
6053
6299
|
/**
|
|
6054
6300
|
* Optional sub-rectangle of the viewport that is used for positioning scene's viewbox
|
|
6055
6301
|
*/
|
|
6056
6302
|
set visibleRect(rect) {
|
|
6057
|
-
this.
|
|
6058
|
-
this.
|
|
6059
|
-
this.
|
|
6303
|
+
if (!this.assertNotExternalMode("visibleRect")) return;
|
|
6304
|
+
this.visibleRectValue = rect;
|
|
6305
|
+
if (this.initialized && !this.disposed) {
|
|
6306
|
+
this.viewportSystem.updateViewport();
|
|
6307
|
+
this.update();
|
|
6308
|
+
}
|
|
6060
6309
|
}
|
|
6061
6310
|
/**
|
|
6062
6311
|
* Underlying {@link WebGLRenderer} instance
|
|
@@ -6072,64 +6321,73 @@ class Renderer {
|
|
|
6072
6321
|
return [this.canvas.width, this.canvas.height];
|
|
6073
6322
|
}
|
|
6074
6323
|
/**
|
|
6075
|
-
*
|
|
6076
|
-
*
|
|
6324
|
+
* Returns true if the renderer is in external mode, meaning that webgl context is managed outside of the renderer
|
|
6325
|
+
* (for example, when using Mapbox GL JS as a host context). In this mode renderer will not clear the canvas before
|
|
6326
|
+
* rendering, and will not automatically compute scene and camera transformations. Clients are responsible for setting
|
|
6327
|
+
* the matrices manually by using {@link Renderer.viewport} methods.
|
|
6077
6328
|
*/
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
this.renderer.autoClear = false;
|
|
6081
|
-
this.interactionsSystem.disconnect();
|
|
6082
|
-
this.viewportSystem.setExternalTransform(staticTransformMatrix);
|
|
6329
|
+
get isExternalMode() {
|
|
6330
|
+
return this.gl !== void 0;
|
|
6083
6331
|
}
|
|
6084
6332
|
/**
|
|
6085
|
-
*
|
|
6086
|
-
|
|
6333
|
+
* Returns true if the renderer is initialized, meaning that the viewport and scene have been set up.
|
|
6334
|
+
*/
|
|
6335
|
+
get isInitialized() {
|
|
6336
|
+
return this.initialized;
|
|
6337
|
+
}
|
|
6338
|
+
/**
|
|
6339
|
+
* Returns true if the renderer is disposed, meaning that all WebGL resources have been released.
|
|
6087
6340
|
*/
|
|
6088
|
-
|
|
6089
|
-
this.
|
|
6341
|
+
get isDisposed() {
|
|
6342
|
+
return this.disposed;
|
|
6090
6343
|
}
|
|
6091
6344
|
/**
|
|
6092
|
-
*
|
|
6345
|
+
* Initializes viewport and scene with the given scene definition.
|
|
6346
|
+
* Should be called once on startup. Repeated calls will produce console warnings.
|
|
6093
6347
|
* @param sceneDef {@link SceneDef} to render
|
|
6094
|
-
* @param startLoop whether to start the rendering loop
|
|
6095
6348
|
*/
|
|
6096
|
-
|
|
6097
|
-
this.
|
|
6349
|
+
init(sceneDef) {
|
|
6350
|
+
if (!this.assertNotDisposed("init")) return;
|
|
6351
|
+
if (!this.assertNotInitialized("init")) return;
|
|
6098
6352
|
this.viewportSystem.initViewport(sceneDef);
|
|
6353
|
+
this.interactionsSystem.init();
|
|
6099
6354
|
this.viewportSystem.scene.add(this.layerSystem.buildScene(sceneDef));
|
|
6100
|
-
|
|
6355
|
+
this.initialized = true;
|
|
6356
|
+
}
|
|
6357
|
+
/**
|
|
6358
|
+
* Start the rendering loop
|
|
6359
|
+
*/
|
|
6360
|
+
start() {
|
|
6361
|
+
if (!this.assertNotDisposed("start")) return;
|
|
6362
|
+
if (!this.assertInitialized("start")) return;
|
|
6363
|
+
if (this.clock.running) return;
|
|
6364
|
+
this.clock.start();
|
|
6365
|
+
this.renderer.setAnimationLoop(() => this.render());
|
|
6101
6366
|
}
|
|
6102
6367
|
/**
|
|
6103
6368
|
* Update the given defs to make them reflect the current state
|
|
6104
6369
|
* @param defs {@link RenderableDef} array to update
|
|
6105
6370
|
*/
|
|
6106
6371
|
update(...defs) {
|
|
6372
|
+
if (!this.assertNotDisposed("update")) return;
|
|
6373
|
+
if (!this.assertInitialized("update")) return;
|
|
6107
6374
|
this.layerSystem.updateDefs(defs);
|
|
6108
6375
|
this.needsRedraw = true;
|
|
6109
6376
|
}
|
|
6110
6377
|
/**
|
|
6111
|
-
*
|
|
6112
|
-
* @param point point in canvas space (relative to the canvas's top left corner), in css pixels
|
|
6113
|
-
* @returns point in SVG space
|
|
6114
|
-
*/
|
|
6115
|
-
screenToSvg(point) {
|
|
6116
|
-
const vector2 = new Vector2(point.x, point.y);
|
|
6117
|
-
canvasToNDC(vector2, this.canvas, vector2);
|
|
6118
|
-
return this.viewportSystem.screenTo("svg", vector2);
|
|
6119
|
-
}
|
|
6120
|
-
/**
|
|
6121
|
-
* Main rendering loop
|
|
6378
|
+
* Render a single frame
|
|
6122
6379
|
*/
|
|
6123
6380
|
render() {
|
|
6124
6381
|
var _a2, _b, _c, _d, _e, _f;
|
|
6382
|
+
if (!this.assertNotDisposed("render")) return;
|
|
6383
|
+
if (!this.assertInitialized("render")) return;
|
|
6125
6384
|
(_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.begin();
|
|
6126
|
-
if (this.
|
|
6385
|
+
if (this.isExternalMode) this.renderer.resetState();
|
|
6127
6386
|
else this.resizeCanvasToDisplaySize();
|
|
6128
6387
|
this.viewportSystem.updatePtScale();
|
|
6129
|
-
const delta = this.clock.getDelta();
|
|
6130
6388
|
const hasDefsUpdated = this.layerSystem.processPendingUpdates();
|
|
6131
|
-
const hasControlsUpdated = this.interactionsSystem.updateControls(
|
|
6132
|
-
const needsRedraw = this.needsRedraw || hasControlsUpdated || hasDefsUpdated || this.ui;
|
|
6389
|
+
const hasControlsUpdated = this.interactionsSystem.updateControls(this.clock.getDelta());
|
|
6390
|
+
const needsRedraw = this.needsRedraw || hasControlsUpdated || hasDefsUpdated || this.isExternalMode || this.ui;
|
|
6133
6391
|
if (needsRedraw) {
|
|
6134
6392
|
this.renderer.render(this.viewportSystem.scene, this.viewportSystem.camera);
|
|
6135
6393
|
this.needsRedraw = false;
|
|
@@ -6146,11 +6404,21 @@ class Renderer {
|
|
|
6146
6404
|
this.clock.stop();
|
|
6147
6405
|
}
|
|
6148
6406
|
/**
|
|
6149
|
-
* Dispose all WebGL resources
|
|
6407
|
+
* Dispose all WebGL resources. This calls {@link Renderer.stop} internally.
|
|
6408
|
+
* WARNING: This method is final and cannot be undone. Attempting to use the renderer after calling this method
|
|
6409
|
+
* will result in a console warning and no-op methods. If you need to re-initialize the renderer, create a new instance.
|
|
6150
6410
|
*/
|
|
6151
6411
|
dispose() {
|
|
6412
|
+
if (this.disposed) return;
|
|
6413
|
+
this.stop();
|
|
6414
|
+
this.interactionsSystem.dispose();
|
|
6152
6415
|
this.layerSystem.disposeScene();
|
|
6153
6416
|
this.viewportSystem.scene.clear();
|
|
6417
|
+
this.renderer.clear();
|
|
6418
|
+
this.renderer.info.reset();
|
|
6419
|
+
this.renderer.dispose();
|
|
6420
|
+
this.updateMemoryInfo();
|
|
6421
|
+
this.disposed = true;
|
|
6154
6422
|
}
|
|
6155
6423
|
// https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
|
|
6156
6424
|
resizeCanvasToDisplaySize() {
|
|
@@ -6167,35 +6435,93 @@ class Renderer {
|
|
|
6167
6435
|
}
|
|
6168
6436
|
}
|
|
6169
6437
|
updateMemoryInfo() {
|
|
6170
|
-
var _a2;
|
|
6438
|
+
var _a2, _b, _c, _d;
|
|
6171
6439
|
if (this.memoryInfoExtension && ((_a2 = this.ui) == null ? void 0 : _a2.memoryInfoPanel)) {
|
|
6172
6440
|
const memoryInfo = this.memoryInfoExtension.getMemoryInfo();
|
|
6173
6441
|
memoryInfo.resources["drawCalls"] = this.renderer.info.render.calls;
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
if (memoryInfoContent !== this.memoryInfo) {
|
|
6442
|
+
if (memoryInfo.memory["texture"] !== ((_b = this.memoryInfo) == null ? void 0 : _b.memory["texture"]) || memoryInfo.memory["buffer"] !== ((_c = this.memoryInfo) == null ? void 0 : _c.memory["buffer"]) || memoryInfo.memory["renderbuffer"] !== ((_d = this.memoryInfo) == null ? void 0 : _d.memory["renderbuffer"])) {
|
|
6443
|
+
const elapsedTime = this.clock.getElapsedTime() * 1e3;
|
|
6177
6444
|
const logMarker = `memoryInfo [${elapsedTime.toFixed(2)}ms since start]`;
|
|
6178
6445
|
if (this.debugLog) console.log(logMarker, memoryInfo);
|
|
6179
|
-
|
|
6180
|
-
this.
|
|
6446
|
+
this.memoryInfo = memoryInfo;
|
|
6447
|
+
this.ui.memoryInfoPanel.textContent = JSON.stringify(memoryInfo, null, 2);
|
|
6181
6448
|
}
|
|
6182
|
-
this.ui.memoryInfoPanel.textContent = JSON.stringify(memoryInfo, null, 2);
|
|
6183
6449
|
}
|
|
6184
6450
|
}
|
|
6185
|
-
// FIXME: Test with mapbox
|
|
6186
6451
|
onContextLost(event) {
|
|
6452
|
+
var _a2, _b;
|
|
6187
6453
|
event.preventDefault();
|
|
6188
6454
|
console.log("webglcontextlost event", event);
|
|
6189
|
-
this.
|
|
6190
|
-
this.
|
|
6191
|
-
if (
|
|
6455
|
+
const stats = (_a2 = this.ui) == null ? void 0 : _a2.stats;
|
|
6456
|
+
const context = this.renderer.getContext();
|
|
6457
|
+
if (stats && "deleteQuery" in context) {
|
|
6458
|
+
const gpuQueries = stats.gpuQueries;
|
|
6459
|
+
for (const queryInfo of gpuQueries) {
|
|
6460
|
+
this.renderer.getContext().deleteQuery(queryInfo.query);
|
|
6461
|
+
}
|
|
6462
|
+
stats.gpuQueries = [];
|
|
6463
|
+
if (stats.gpuPanel) {
|
|
6464
|
+
stats.dom.removeChild((_b = stats.gpuPanel) == null ? void 0 : _b.canvas);
|
|
6465
|
+
stats.gpuPanel = null;
|
|
6466
|
+
stats._panelId--;
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
this.stop();
|
|
6192
6470
|
}
|
|
6193
6471
|
onContextRestored(event) {
|
|
6194
6472
|
event.preventDefault();
|
|
6195
6473
|
console.log("webglcontextrestored event", event);
|
|
6196
|
-
this.
|
|
6197
|
-
this.
|
|
6474
|
+
this.initContext(this.renderer.getContext());
|
|
6475
|
+
this.needsRedraw = true;
|
|
6476
|
+
this.start();
|
|
6477
|
+
}
|
|
6478
|
+
initContext(context) {
|
|
6479
|
+
var _a2, _b;
|
|
6480
|
+
this.memoryInfoExtension = context.getExtension("GMAN_webgl_memory");
|
|
6481
|
+
void ((_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.init(context));
|
|
6482
|
+
}
|
|
6483
|
+
assertNotDisposed(funcName) {
|
|
6484
|
+
if (this.disposed) {
|
|
6485
|
+
console.warn(`[Renderer.${funcName}]: Renderer is used after being disposed. Please create a new instance.`);
|
|
6486
|
+
return false;
|
|
6487
|
+
}
|
|
6488
|
+
return true;
|
|
6489
|
+
}
|
|
6490
|
+
assertInitialized(funcName) {
|
|
6491
|
+
if (!this.initialized) {
|
|
6492
|
+
console.warn(`[Renderer.${funcName}]: Renderer is not initialized. Please call init() before using it.`);
|
|
6493
|
+
return false;
|
|
6494
|
+
}
|
|
6495
|
+
return true;
|
|
6198
6496
|
}
|
|
6497
|
+
assertNotInitialized(funcName) {
|
|
6498
|
+
if (this.initialized) {
|
|
6499
|
+
console.warn(`[Renderer.${funcName}]: Renderer is already initialized. Please call init() only once.`);
|
|
6500
|
+
return false;
|
|
6501
|
+
}
|
|
6502
|
+
return true;
|
|
6503
|
+
}
|
|
6504
|
+
assertNotExternalMode(funcName) {
|
|
6505
|
+
if (this.isExternalMode) {
|
|
6506
|
+
console.warn(`[Renderer.${funcName}]: This operation is not supported in external mode.`);
|
|
6507
|
+
return false;
|
|
6508
|
+
}
|
|
6509
|
+
return true;
|
|
6510
|
+
}
|
|
6511
|
+
assertExternalMode(funcName) {
|
|
6512
|
+
if (!this.isExternalMode) {
|
|
6513
|
+
console.warn(`[Renderer.${funcName}]: This operation is only supported in external mode.`);
|
|
6514
|
+
return false;
|
|
6515
|
+
}
|
|
6516
|
+
return true;
|
|
6517
|
+
}
|
|
6518
|
+
}
|
|
6519
|
+
function guardFn(guard, fn, ...fallback) {
|
|
6520
|
+
return (...args) => {
|
|
6521
|
+
const name = fn.name.split(" ").at(-1);
|
|
6522
|
+
if (!guard(name)) return fallback[0];
|
|
6523
|
+
return fn(...args);
|
|
6524
|
+
};
|
|
6199
6525
|
}
|
|
6200
6526
|
export {
|
|
6201
6527
|
Polygon,
|