@expofp/renderer 3.0.1 → 3.1.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 +21 -6
- package/dist/index.js +223 -126
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -274,8 +274,14 @@ export declare interface LayerDef extends SharedDef {
|
|
|
274
274
|
interactive?: boolean;
|
|
275
275
|
/** Collection of layer's children. Mixing different types of children is not supported. */
|
|
276
276
|
children: RenderableDefCollection;
|
|
277
|
-
/**
|
|
278
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Layer's rendering mode. Propagates to children: child layers inherit the parent's mode
|
|
279
|
+
* unless they specify their own. Defaults to `"2D"` at the root.
|
|
280
|
+
* Resolved once during scene build and should not be changed afterwards.
|
|
281
|
+
* - `"2D"`: painter's algorithm (AlwaysDepth). Layer order determines overlap.
|
|
282
|
+
* - `"3D"`: depth testing (LessDepth). Closer geometry occludes farther geometry.
|
|
283
|
+
*/
|
|
284
|
+
readonly mode?: "2D" | "3D";
|
|
279
285
|
}
|
|
280
286
|
|
|
281
287
|
/** Line definition */
|
|
@@ -338,7 +344,11 @@ export declare class Polygon {
|
|
|
338
344
|
get vertices(): readonly Vector3Like[];
|
|
339
345
|
/** Array of polygon indices. Each index is a triplet of vertex indices forming a triangle. */
|
|
340
346
|
get indices(): readonly Index[];
|
|
341
|
-
/**
|
|
347
|
+
/**
|
|
348
|
+
* Bounding rectangle of the polygon.
|
|
349
|
+
* This is a 2D axis-aligned bounding box computed from the X/Y projection of vertices.
|
|
350
|
+
* The Z extent of the polygon is not tracked.
|
|
351
|
+
*/
|
|
342
352
|
get bounds(): Rect;
|
|
343
353
|
/**
|
|
344
354
|
* Converts a {@link Rect} to a {@link Polygon}.
|
|
@@ -367,7 +377,8 @@ export declare class Polygon {
|
|
|
367
377
|
rotate(rotation: number, center?: Vector2Like): Polygon;
|
|
368
378
|
/**
|
|
369
379
|
* Scales the polygon around the given origin.
|
|
370
|
-
* @param scaleFactor Can be a single number or a 2D vector. If a single number is provided, both horizontal and
|
|
380
|
+
* @param scaleFactor Can be a single number or a 2D vector. If a single number is provided, both horizontal and
|
|
381
|
+
* vertical axes will be scaled by the same factor. Z is unchanged in both cases.
|
|
371
382
|
* @param origin Origin of the scaling. If omitted, defaults to the bounding rectangle center.
|
|
372
383
|
* @returns this {@link Polygon} instance
|
|
373
384
|
*/
|
|
@@ -387,6 +398,8 @@ export declare interface PtScaleEventData {
|
|
|
387
398
|
export declare class Rect {
|
|
388
399
|
/** Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise. */
|
|
389
400
|
rotation: number;
|
|
401
|
+
/** Optional elevation of the rectangle. Positive values raise geometry toward the viewer. Defaults to 0. */
|
|
402
|
+
elevation: number;
|
|
390
403
|
private _min;
|
|
391
404
|
private _max;
|
|
392
405
|
private _center;
|
|
@@ -395,8 +408,9 @@ export declare class Rect {
|
|
|
395
408
|
* @param min Top left corner of the rectangle.
|
|
396
409
|
* @param max Bottom right corner of the rectangle.
|
|
397
410
|
* @param rotation Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise.
|
|
411
|
+
* @param elevation Optional elevation. Positive values raise geometry toward the viewer. Defaults to 0.
|
|
398
412
|
*/
|
|
399
|
-
constructor(min: IVector2, max: IVector2, rotation?: number);
|
|
413
|
+
constructor(min: IVector2, max: IVector2, rotation?: number, elevation?: number);
|
|
400
414
|
/** Top left corner of the rectangle. */
|
|
401
415
|
get min(): Vector2Like;
|
|
402
416
|
/** Set top left corner of the rectangle. */
|
|
@@ -417,9 +431,10 @@ export declare class Rect {
|
|
|
417
431
|
* Creates a rectangle from an SVG rectangle element.
|
|
418
432
|
* @param rect {@link SVGRectElement} or {@link SVGImageElement}
|
|
419
433
|
* @param rotation Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise.
|
|
434
|
+
* @param elevation Optional elevation. Positive values raise geometry toward the viewer. Defaults to 0.
|
|
420
435
|
* @returns new {@link Rect} instance
|
|
421
436
|
*/
|
|
422
|
-
static fromSvg(rect: SVGRectElement | SVGImageElement, rotation?: number): Rect;
|
|
437
|
+
static fromSvg(rect: SVGRectElement | SVGImageElement, rotation?: number, elevation?: number): Rect;
|
|
423
438
|
/**
|
|
424
439
|
* Moves the rectangle by the given offset.
|
|
425
440
|
* @param offset Offset to move the rectangle by.
|
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 { DataTexture, FloatType, UnsignedIntType, IntType, RGBAFormat, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedFormat, RedIntegerFormat, BatchedMesh as BatchedMesh$1, BufferAttribute, StreamDrawUsage, Color, Matrix4, Vector3, Frustum, Sphere, Box3, DynamicDrawUsage, Vector4, AlwaysDepth, DoubleSide, MeshBasicMaterial, Texture, Group, PlaneGeometry, SRGBColorSpace, Vector2, Quaternion,
|
|
5
|
+
import { DataTexture, FloatType, UnsignedIntType, IntType, RGBAFormat, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedFormat, RedIntegerFormat, BatchedMesh as BatchedMesh$1, BufferAttribute, StreamDrawUsage, Color, Matrix4, Vector3, Frustum, Sphere, Box3, DynamicDrawUsage, Vector4, AlwaysDepth, DoubleSide, MeshBasicMaterial, LessEqualDepth, Texture, Group, PlaneGeometry, SRGBColorSpace, Vector2, Quaternion, BufferGeometry, LinearSRGBColorSpace, Mesh, Camera, Spherical, MathUtils, Plane, Raycaster, PerspectiveCamera, Scene, Clock, WebGLRenderer } from "three";
|
|
6
6
|
import { traverseAncestorsGenerator } from "three/examples/jsm/utils/SceneUtils.js";
|
|
7
7
|
import createLog from "debug";
|
|
8
8
|
import { BatchedText as BatchedText$1, Text as Text$1 } from "troika-three-text";
|
|
@@ -867,8 +867,6 @@ class Text extends Text$1 {
|
|
|
867
867
|
if (!this._batchParent) this.geometry.applyClipRect(uniforms.uTroikaClipRect.value);
|
|
868
868
|
}
|
|
869
869
|
uniforms.uTroikaSDFDebug.value = !!this.debugSDF;
|
|
870
|
-
material.polygonOffset = !!this.depthOffset;
|
|
871
|
-
material.polygonOffsetFactor = material.polygonOffsetUnits = this.depthOffset || 0;
|
|
872
870
|
const color = isOutline ? this.outlineColor || 0 : this.color;
|
|
873
871
|
if (color == null) {
|
|
874
872
|
delete material.color;
|
|
@@ -1050,6 +1048,7 @@ const dimColorFrag = (
|
|
|
1050
1048
|
return vec4(m * col.a, col.a);
|
|
1051
1049
|
}`
|
|
1052
1050
|
);
|
|
1051
|
+
const POLYGON_OFFSET_MULTIPLIER = 12;
|
|
1053
1052
|
const sharedParameters = {
|
|
1054
1053
|
side: DoubleSide,
|
|
1055
1054
|
transparent: true,
|
|
@@ -1094,47 +1093,47 @@ class MaterialSystem {
|
|
|
1094
1093
|
}
|
|
1095
1094
|
};
|
|
1096
1095
|
addDimToMaterial(material);
|
|
1096
|
+
this.addPolygonOffset(material);
|
|
1097
1097
|
return material;
|
|
1098
1098
|
}
|
|
1099
1099
|
/**
|
|
1100
1100
|
* Creates a color material.
|
|
1101
|
-
* @param
|
|
1101
|
+
* @param params {@link MaterialColorParams}
|
|
1102
1102
|
* @returns MeshBasicMaterial instance
|
|
1103
1103
|
*/
|
|
1104
|
-
createColorMaterial(
|
|
1105
|
-
const params = {
|
|
1106
|
-
color: partialParams.color ?? 16777215,
|
|
1107
|
-
opacity: partialParams.opacity ?? 1
|
|
1108
|
-
};
|
|
1104
|
+
createColorMaterial(params = {}) {
|
|
1109
1105
|
const material = new MeshBasicMaterial({
|
|
1110
1106
|
...sharedParameters,
|
|
1111
|
-
color: params.color,
|
|
1112
|
-
opacity: params.opacity
|
|
1107
|
+
color: params.color ?? 16777215,
|
|
1108
|
+
opacity: params.opacity ?? 1
|
|
1113
1109
|
});
|
|
1110
|
+
if (params.is3D) material.depthFunc = LessEqualDepth;
|
|
1114
1111
|
addDimToMaterial(material);
|
|
1112
|
+
this.addPolygonOffset(material);
|
|
1115
1113
|
return material;
|
|
1116
1114
|
}
|
|
1117
1115
|
/**
|
|
1118
1116
|
* Creates a texture material.
|
|
1119
|
-
* @param
|
|
1120
|
-
* @param uvOffset whether to enable uv offset with per instance uniforms (for texture atlases)
|
|
1117
|
+
* @param params {@link MaterialTextureParams}
|
|
1121
1118
|
* @returns MeshBasicMaterial instance
|
|
1122
1119
|
*/
|
|
1123
|
-
createTextureMaterial(
|
|
1124
|
-
const material = new MeshBasicMaterial({ ...sharedParameters, map });
|
|
1125
|
-
if (
|
|
1120
|
+
createTextureMaterial(params) {
|
|
1121
|
+
const material = new MeshBasicMaterial({ ...sharedParameters, map: params.map });
|
|
1122
|
+
if (params.is3D) material.depthFunc = LessEqualDepth;
|
|
1123
|
+
if (params.uvOffset) {
|
|
1126
1124
|
material.onBeforeCompile = (shader) => {
|
|
1127
1125
|
shader.vertexShader = shader.vertexShader.replace(
|
|
1128
1126
|
"#include <uv_vertex>",
|
|
1129
1127
|
/*glsl*/
|
|
1130
1128
|
`
|
|
1131
1129
|
#include <uv_vertex>
|
|
1132
|
-
vMapUv = uv * uvOffset.zw + uvOffset.xy;
|
|
1130
|
+
vMapUv = uv * uvOffset.zw + uvOffset.xy;
|
|
1133
1131
|
`
|
|
1134
1132
|
);
|
|
1135
1133
|
};
|
|
1136
1134
|
}
|
|
1137
1135
|
addDimToMaterial(material);
|
|
1136
|
+
this.addPolygonOffset(material);
|
|
1138
1137
|
return material;
|
|
1139
1138
|
}
|
|
1140
1139
|
/**
|
|
@@ -1158,6 +1157,40 @@ class MaterialSystem {
|
|
|
1158
1157
|
}
|
|
1159
1158
|
return this.backgroundMaterial;
|
|
1160
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Patches a material's onBeforeRender to dynamically set polygonOffset based on the
|
|
1162
|
+
* object's renderOrder. Later layers (higher renderOrder) get a larger negative offset,
|
|
1163
|
+
* giving them a slight depth buffer advantage over earlier layers at the same elevation.
|
|
1164
|
+
* This prevents z-fighting between coplanar 3D content across different layers.
|
|
1165
|
+
*
|
|
1166
|
+
* The units multiplier is derived from {@link CameraControls.maxPolarAngle}:
|
|
1167
|
+
*
|
|
1168
|
+
* multiplier = ⌈tan(maxPolarAngle)⌉
|
|
1169
|
+
*
|
|
1170
|
+
* At steep camera pitch, coplanar surfaces from different draw calls (different
|
|
1171
|
+
* BatchedMesh instances) compute slightly different depth values at the same pixel
|
|
1172
|
+
* due to floating-point interpolation differences in the vertex shader. These errors
|
|
1173
|
+
* are proportional to the depth gradient across the surface, which for a horizontal
|
|
1174
|
+
* plane at pitch angle θ is proportional to tan(θ). The multiplier ensures the
|
|
1175
|
+
* per-layer polygon offset exceeds these errors at the configured maximum pitch.
|
|
1176
|
+
*
|
|
1177
|
+
* For elevated content the effective viewing angle can exceed maxPolarAngle, but this
|
|
1178
|
+
* is self-correcting: the screen-space height of the surface shrinks as cos(angle),
|
|
1179
|
+
* so the total visible z-fighting (pixels × severity ∝ cos × tan = sin) is bounded
|
|
1180
|
+
* and becomes subpixel as the effective angle approaches 90°.
|
|
1181
|
+
*
|
|
1182
|
+
* With maxPolarAngle = 85°: ⌈tan(85°)⌉ = ⌈11.43⌉ = 12.
|
|
1183
|
+
* @param material Material to patch
|
|
1184
|
+
*/
|
|
1185
|
+
addPolygonOffset(material) {
|
|
1186
|
+
const onBeforeRender = material.onBeforeRender.bind(material);
|
|
1187
|
+
material.onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
|
|
1188
|
+
onBeforeRender(renderer, scene, camera, geometry, object, group);
|
|
1189
|
+
material.polygonOffset = true;
|
|
1190
|
+
material.polygonOffsetFactor = 0;
|
|
1191
|
+
material.polygonOffsetUnits = -object.renderOrder * POLYGON_OFFSET_MULTIPLIER;
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1161
1194
|
}
|
|
1162
1195
|
function isShapeDef(def) {
|
|
1163
1196
|
return def.shape !== void 0;
|
|
@@ -1189,36 +1222,14 @@ function isLineLayer(layer) {
|
|
|
1189
1222
|
function isLayerLayer(layer) {
|
|
1190
1223
|
return layer.children[0] && isLayerDef(layer.children[0]);
|
|
1191
1224
|
}
|
|
1192
|
-
function groupBy(list, keyGetter) {
|
|
1193
|
-
const map = /* @__PURE__ */ new Map();
|
|
1194
|
-
list.forEach((item) => {
|
|
1195
|
-
const key = keyGetter(item);
|
|
1196
|
-
const collection = map.get(key);
|
|
1197
|
-
if (!collection) {
|
|
1198
|
-
map.set(key, [item]);
|
|
1199
|
-
} else {
|
|
1200
|
-
collection.push(item);
|
|
1201
|
-
}
|
|
1202
|
-
});
|
|
1203
|
-
return map;
|
|
1204
|
-
}
|
|
1205
|
-
function partition(list, pred) {
|
|
1206
|
-
const truthy = [];
|
|
1207
|
-
const falsy = [];
|
|
1208
|
-
for (const item of list) {
|
|
1209
|
-
if (pred(item)) {
|
|
1210
|
-
truthy.push(item);
|
|
1211
|
-
} else {
|
|
1212
|
-
falsy.push(item);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
return [truthy, falsy];
|
|
1216
|
-
}
|
|
1217
1225
|
const INTERACTIVE_LAYER = 1;
|
|
1218
|
-
function
|
|
1219
|
-
|
|
1226
|
+
function isInteractive(object) {
|
|
1227
|
+
return object.layers.isEnabled(INTERACTIVE_LAYER);
|
|
1228
|
+
}
|
|
1229
|
+
function setInteractive(object, isInteractive2) {
|
|
1230
|
+
if (isInteractive2) object.layers.enable(INTERACTIVE_LAYER);
|
|
1220
1231
|
else object.layers.disable(INTERACTIVE_LAYER);
|
|
1221
|
-
object.children.forEach((child) => setInteractive(child,
|
|
1232
|
+
object.children.forEach((child) => setInteractive(child, isInteractive2));
|
|
1222
1233
|
}
|
|
1223
1234
|
function isVisible(object) {
|
|
1224
1235
|
if (!object.visible) return false;
|
|
@@ -1483,6 +1494,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1483
1494
|
}
|
|
1484
1495
|
buildLayer(layer) {
|
|
1485
1496
|
var _a2;
|
|
1497
|
+
const is3D = layer.mode === "3D";
|
|
1486
1498
|
const group = new Group();
|
|
1487
1499
|
const images = layer.children;
|
|
1488
1500
|
const bins = this.packImages(images);
|
|
@@ -1497,7 +1509,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1497
1509
|
).sort((a, b) => a.originalIndex - b.originalIndex);
|
|
1498
1510
|
const instanceCount = rectsWithDef.length;
|
|
1499
1511
|
const texture = createAtlas(bin);
|
|
1500
|
-
const instanceMaterial = this.materialSystem.createTextureMaterial(texture, true);
|
|
1512
|
+
const instanceMaterial = this.materialSystem.createTextureMaterial({ map: texture, uvOffset: true, is3D });
|
|
1501
1513
|
const instanceGeometry = new PlaneGeometry();
|
|
1502
1514
|
const vertexCount = instanceGeometry.attributes["position"].count;
|
|
1503
1515
|
const indexCount = ((_a2 = instanceGeometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
@@ -1582,7 +1594,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1582
1594
|
const bounds = imageDef.bounds;
|
|
1583
1595
|
const origin2 = imageDef.origin ?? [0.5, 0.5];
|
|
1584
1596
|
this.originTranslationMatrix.makeTranslation(0.5 - origin2[0], 0.5 - origin2[1], 0);
|
|
1585
|
-
this.globalTranslationMatrix.makeTranslation(bounds.center.x, bounds.center.y,
|
|
1597
|
+
this.globalTranslationMatrix.makeTranslation(bounds.center.x, bounds.center.y, bounds.elevation);
|
|
1586
1598
|
this.scaleMatrix.makeScale(bounds.size.x, bounds.size.y, 1);
|
|
1587
1599
|
this.rotationMatrix.makeRotationZ(bounds.rotation);
|
|
1588
1600
|
const matrix = this.originTranslationMatrix.premultiply(this.scaleMatrix).premultiply(this.rotationMatrix).premultiply(this.globalTranslationMatrix);
|
|
@@ -1732,10 +1744,13 @@ class Rect {
|
|
|
1732
1744
|
* @param min Top left corner of the rectangle.
|
|
1733
1745
|
* @param max Bottom right corner of the rectangle.
|
|
1734
1746
|
* @param rotation Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise.
|
|
1747
|
+
* @param elevation Optional elevation. Positive values raise geometry toward the viewer. Defaults to 0.
|
|
1735
1748
|
*/
|
|
1736
|
-
constructor(min, max, rotation) {
|
|
1749
|
+
constructor(min, max, rotation, elevation) {
|
|
1737
1750
|
/** Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise. */
|
|
1738
1751
|
__publicField(this, "rotation");
|
|
1752
|
+
/** Optional elevation of the rectangle. Positive values raise geometry toward the viewer. Defaults to 0. */
|
|
1753
|
+
__publicField(this, "elevation");
|
|
1739
1754
|
__publicField(this, "_min");
|
|
1740
1755
|
__publicField(this, "_max");
|
|
1741
1756
|
__publicField(this, "_center", new Vector2());
|
|
@@ -1743,6 +1758,7 @@ class Rect {
|
|
|
1743
1758
|
this._min = createVector2(min);
|
|
1744
1759
|
this._max = createVector2(max);
|
|
1745
1760
|
this.rotation = rotation ?? 0;
|
|
1761
|
+
this.elevation = elevation ?? 0;
|
|
1746
1762
|
this.updateCenterAndSize();
|
|
1747
1763
|
}
|
|
1748
1764
|
/** Top left corner of the rectangle. */
|
|
@@ -1785,14 +1801,15 @@ class Rect {
|
|
|
1785
1801
|
* Creates a rectangle from an SVG rectangle element.
|
|
1786
1802
|
* @param rect {@link SVGRectElement} or {@link SVGImageElement}
|
|
1787
1803
|
* @param rotation Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise.
|
|
1804
|
+
* @param elevation Optional elevation. Positive values raise geometry toward the viewer. Defaults to 0.
|
|
1788
1805
|
* @returns new {@link Rect} instance
|
|
1789
1806
|
*/
|
|
1790
|
-
static fromSvg(rect, rotation) {
|
|
1807
|
+
static fromSvg(rect, rotation, elevation) {
|
|
1791
1808
|
const x = rect.x.baseVal.value;
|
|
1792
1809
|
const y = rect.y.baseVal.value;
|
|
1793
1810
|
const width = rect.width.baseVal.value;
|
|
1794
1811
|
const height = rect.height.baseVal.value;
|
|
1795
|
-
return new Rect([x, y], [x + width, y + height], rotation);
|
|
1812
|
+
return new Rect([x, y], [x + width, y + height], rotation, elevation);
|
|
1796
1813
|
}
|
|
1797
1814
|
/**
|
|
1798
1815
|
* Moves the rectangle by the given offset.
|
|
@@ -1848,7 +1865,11 @@ class Polygon {
|
|
|
1848
1865
|
get indices() {
|
|
1849
1866
|
return this._indices;
|
|
1850
1867
|
}
|
|
1851
|
-
/**
|
|
1868
|
+
/**
|
|
1869
|
+
* Bounding rectangle of the polygon.
|
|
1870
|
+
* This is a 2D axis-aligned bounding box computed from the X/Y projection of vertices.
|
|
1871
|
+
* The Z extent of the polygon is not tracked.
|
|
1872
|
+
*/
|
|
1852
1873
|
get bounds() {
|
|
1853
1874
|
return this._bbox;
|
|
1854
1875
|
}
|
|
@@ -1858,11 +1879,12 @@ class Polygon {
|
|
|
1858
1879
|
* @returns new {@link Polygon} instance
|
|
1859
1880
|
*/
|
|
1860
1881
|
static fromRect(rect) {
|
|
1882
|
+
const z = rect.elevation;
|
|
1861
1883
|
const vertices = [
|
|
1862
|
-
{ x: rect.min.x, y: rect.min.y },
|
|
1863
|
-
{ x: rect.max.x, y: rect.min.y },
|
|
1864
|
-
{ x: rect.max.x, y: rect.max.y },
|
|
1865
|
-
{ x: rect.min.x, y: rect.max.y }
|
|
1884
|
+
{ x: rect.min.x, y: rect.min.y, z },
|
|
1885
|
+
{ x: rect.max.x, y: rect.min.y, z },
|
|
1886
|
+
{ x: rect.max.x, y: rect.max.y, z },
|
|
1887
|
+
{ x: rect.min.x, y: rect.max.y, z }
|
|
1866
1888
|
];
|
|
1867
1889
|
const indices = [
|
|
1868
1890
|
[0, 1, 2],
|
|
@@ -1914,7 +1936,8 @@ class Polygon {
|
|
|
1914
1936
|
}
|
|
1915
1937
|
/**
|
|
1916
1938
|
* Scales the polygon around the given origin.
|
|
1917
|
-
* @param scaleFactor Can be a single number or a 2D vector. If a single number is provided, both horizontal and
|
|
1939
|
+
* @param scaleFactor Can be a single number or a 2D vector. If a single number is provided, both horizontal and
|
|
1940
|
+
* vertical axes will be scaled by the same factor. Z is unchanged in both cases.
|
|
1918
1941
|
* @param origin Origin of the scaling. If omitted, defaults to the bounding rectangle center.
|
|
1919
1942
|
* @returns this {@link Polygon} instance
|
|
1920
1943
|
*/
|
|
@@ -1942,6 +1965,31 @@ class Polygon {
|
|
|
1942
1965
|
}
|
|
1943
1966
|
}
|
|
1944
1967
|
const tempVector2 = new Vector2();
|
|
1968
|
+
function groupBy(list, keyGetter) {
|
|
1969
|
+
const map = /* @__PURE__ */ new Map();
|
|
1970
|
+
list.forEach((item) => {
|
|
1971
|
+
const key = keyGetter(item);
|
|
1972
|
+
const collection = map.get(key);
|
|
1973
|
+
if (!collection) {
|
|
1974
|
+
map.set(key, [item]);
|
|
1975
|
+
} else {
|
|
1976
|
+
collection.push(item);
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
return map;
|
|
1980
|
+
}
|
|
1981
|
+
function partition(list, pred) {
|
|
1982
|
+
const truthy = [];
|
|
1983
|
+
const falsy = [];
|
|
1984
|
+
for (const item of list) {
|
|
1985
|
+
if (pred(item)) {
|
|
1986
|
+
truthy.push(item);
|
|
1987
|
+
} else {
|
|
1988
|
+
falsy.push(item);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
return [truthy, falsy];
|
|
1992
|
+
}
|
|
1945
1993
|
function countGeometry(geometry) {
|
|
1946
1994
|
var _a2;
|
|
1947
1995
|
if (geometry.index == null) return { vertices: geometry.getAttribute("position").count, indices: 0 };
|
|
@@ -1950,6 +1998,18 @@ function countGeometry(geometry) {
|
|
|
1950
1998
|
indices: ((_a2 = geometry.index) == null ? void 0 : _a2.count) ?? 0
|
|
1951
1999
|
};
|
|
1952
2000
|
}
|
|
2001
|
+
const sphereCenter = new Vector3();
|
|
2002
|
+
function computeBoundingSphere(bounds, origin2, out = new Sphere()) {
|
|
2003
|
+
const ox = (origin2 == null ? void 0 : origin2[0]) ?? 0.5;
|
|
2004
|
+
const oy = (origin2 == null ? void 0 : origin2[1]) ?? 0.5;
|
|
2005
|
+
const w = bounds.size.x;
|
|
2006
|
+
const h = bounds.size.y;
|
|
2007
|
+
const dx = Math.max(ox, 1 - ox) * w;
|
|
2008
|
+
const dy = Math.max(oy, 1 - oy) * h;
|
|
2009
|
+
sphereCenter.set(bounds.center.x, bounds.center.y, bounds.elevation);
|
|
2010
|
+
out.set(sphereCenter, Math.hypot(dx, dy));
|
|
2011
|
+
return out;
|
|
2012
|
+
}
|
|
1953
2013
|
const logger$a = createLogger("mesh");
|
|
1954
2014
|
extend([namesPlugin]);
|
|
1955
2015
|
class MeshSystem extends RenderableSystem {
|
|
@@ -1971,6 +2031,7 @@ class MeshSystem extends RenderableSystem {
|
|
|
1971
2031
|
this.rectGeometry.deleteAttribute("uv");
|
|
1972
2032
|
}
|
|
1973
2033
|
buildLayer(layer) {
|
|
2034
|
+
const is3D = layer.mode === "3D";
|
|
1974
2035
|
const shapes = layer.children;
|
|
1975
2036
|
const mapShapeToNormColor = /* @__PURE__ */ new Map();
|
|
1976
2037
|
for (const shapeDef of shapes) {
|
|
@@ -1987,21 +2048,15 @@ class MeshSystem extends RenderableSystem {
|
|
|
1987
2048
|
const transparentShapesGrouped = groupBy(transparentShapes, (shapeDef) => mapShapeToNormColor.get(shapeDef).a);
|
|
1988
2049
|
const group = new Group();
|
|
1989
2050
|
for (const [opacity, shapes2] of transparentShapesGrouped) {
|
|
1990
|
-
const transparentMesh = this.buildBatchedMesh(shapes2, opacity);
|
|
2051
|
+
const transparentMesh = this.buildBatchedMesh(shapes2, is3D, opacity);
|
|
1991
2052
|
transparentMesh.name = "transparent";
|
|
1992
2053
|
group.add(transparentMesh);
|
|
1993
2054
|
}
|
|
1994
2055
|
if (opaqueShapes.length) {
|
|
1995
|
-
const opaqueMesh = this.buildBatchedMesh(opaqueShapes);
|
|
2056
|
+
const opaqueMesh = this.buildBatchedMesh(opaqueShapes, is3D);
|
|
1996
2057
|
opaqueMesh.name = "opaque";
|
|
1997
2058
|
group.add(opaqueMesh);
|
|
1998
2059
|
}
|
|
1999
|
-
if (layer.mode === "3d") {
|
|
2000
|
-
group.children.filter((child) => child instanceof Mesh).forEach((mesh) => {
|
|
2001
|
-
const material = mesh.material;
|
|
2002
|
-
material.depthFunc = LessEqualDepth;
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
2060
|
return group;
|
|
2006
2061
|
}
|
|
2007
2062
|
updateDefImpl(shapeDef, mesh, instanceIds, firstUpdate) {
|
|
@@ -2040,13 +2095,13 @@ class MeshSystem extends RenderableSystem {
|
|
|
2040
2095
|
mesh.setColorAt(instanceId, this.color.setRGB(color.r / 255, color.g / 255, color.b / 255, SRGBColorSpace));
|
|
2041
2096
|
}
|
|
2042
2097
|
updateRect(shape, mesh, instanceId) {
|
|
2043
|
-
this.position.set(shape.center.x, shape.center.y,
|
|
2044
|
-
this.rotation.setFromAxisAngle(new Vector3(0, 0, 1), shape.rotation
|
|
2098
|
+
this.position.set(shape.center.x, shape.center.y, shape.elevation);
|
|
2099
|
+
this.rotation.setFromAxisAngle(new Vector3(0, 0, 1), shape.rotation);
|
|
2045
2100
|
this.scale.set(shape.size.x, shape.size.y, 1);
|
|
2046
2101
|
this.matrix.compose(this.position, this.rotation, this.scale);
|
|
2047
2102
|
mesh.setMatrixAt(instanceId, this.matrix);
|
|
2048
2103
|
}
|
|
2049
|
-
buildBatchedMesh(shapes, opacity = 1) {
|
|
2104
|
+
buildBatchedMesh(shapes, is3D, opacity = 1) {
|
|
2050
2105
|
let vertexCount = 0;
|
|
2051
2106
|
let indexCount = 0;
|
|
2052
2107
|
let rectAdded = false;
|
|
@@ -2065,7 +2120,7 @@ class MeshSystem extends RenderableSystem {
|
|
|
2065
2120
|
vertexCount += vertices;
|
|
2066
2121
|
indexCount += indices;
|
|
2067
2122
|
}
|
|
2068
|
-
const material = this.materialSystem.createColorMaterial({ opacity });
|
|
2123
|
+
const material = this.materialSystem.createColorMaterial({ opacity, is3D });
|
|
2069
2124
|
const batchedMesh = new BatchedMesh(shapes.length, vertexCount, indexCount, material);
|
|
2070
2125
|
const rectGeometryId = rectAdded ? batchedMesh.addGeometry(this.rectGeometry) : void 0;
|
|
2071
2126
|
batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, list));
|
|
@@ -2201,9 +2256,10 @@ class TextSystem extends RenderableSystem {
|
|
|
2201
2256
|
}
|
|
2202
2257
|
}
|
|
2203
2258
|
buildBatchedText(layer) {
|
|
2259
|
+
const is3D = layer.mode === "3D";
|
|
2204
2260
|
const textDefs = layer.children;
|
|
2205
2261
|
const batchedText = new BatchedText();
|
|
2206
|
-
batchedText.material = this.materialSystem.createColorMaterial();
|
|
2262
|
+
batchedText.material = this.materialSystem.createColorMaterial({ is3D });
|
|
2207
2263
|
const mappingData = [];
|
|
2208
2264
|
let instanceId = 0;
|
|
2209
2265
|
for (const textDef of textDefs) {
|
|
@@ -2247,7 +2303,7 @@ class TextSystem extends RenderableSystem {
|
|
|
2247
2303
|
this.worldPosition.copy(this.localPosition).rotateAround({ x: 0, y: 0 }, textDef.bounds.rotation).add(textDef.bounds.center);
|
|
2248
2304
|
this.textScale.copy(this.initialTextScale).multiplyScalar(fontSize * dpr);
|
|
2249
2305
|
text.scale.set(this.textScale.x, this.textScale.y, 1);
|
|
2250
|
-
text.position.set(this.worldPosition.x, this.worldPosition.y,
|
|
2306
|
+
text.position.set(this.worldPosition.x, this.worldPosition.y, textDef.bounds.elevation);
|
|
2251
2307
|
text.rotation.set(0, 0, textDef.bounds.rotation);
|
|
2252
2308
|
this.calculateClipRect(text, textDef, this.localPosition, this.textScale, this.localToMin, this.localToMax);
|
|
2253
2309
|
this.localPosition.y += height * dpr;
|
|
@@ -2482,22 +2538,24 @@ class LayerSystem {
|
|
|
2482
2538
|
* @returns sorted leaf layers for debug logging
|
|
2483
2539
|
*/
|
|
2484
2540
|
initRenderOrder(rootLayer) {
|
|
2485
|
-
const
|
|
2486
|
-
const
|
|
2541
|
+
const twoDLayers = [];
|
|
2542
|
+
const threeDLayers = [];
|
|
2543
|
+
const stack = [
|
|
2544
|
+
{ layer: rootLayer, inheritedMode: rootLayer.mode ?? "2D" }
|
|
2545
|
+
];
|
|
2487
2546
|
while (stack.length) {
|
|
2488
|
-
const layer = stack.pop();
|
|
2547
|
+
const { layer, inheritedMode } = stack.pop();
|
|
2489
2548
|
if (isLayerLayer(layer)) {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
for (const child of children) {
|
|
2549
|
+
for (let i = layer.children.length - 1; i >= 0; i--) {
|
|
2550
|
+
const child = layer.children[i];
|
|
2493
2551
|
this.mapLayerDefToParent.set(child, layer);
|
|
2494
|
-
stack.push(child);
|
|
2552
|
+
stack.push({ layer: child, inheritedMode: child.mode ?? inheritedMode });
|
|
2495
2553
|
}
|
|
2496
2554
|
} else {
|
|
2497
|
-
|
|
2555
|
+
layer.mode ?? (layer.mode = inheritedMode);
|
|
2556
|
+
(layer.mode === "3D" ? threeDLayers : twoDLayers).push(layer);
|
|
2498
2557
|
}
|
|
2499
2558
|
}
|
|
2500
|
-
const [threeDLayers, twoDLayers] = partition(leafLayers, (layer) => layer.mode === "3d");
|
|
2501
2559
|
const sorted = [...twoDLayers, ...threeDLayers];
|
|
2502
2560
|
for (let i = 0; i < sorted.length; i++) {
|
|
2503
2561
|
this.renderOrderMap.set(sorted[i], i + 1);
|
|
@@ -2612,10 +2670,12 @@ class ExternalCameraSystem {
|
|
|
2612
2670
|
}
|
|
2613
2671
|
}
|
|
2614
2672
|
const originalProjectionMatrix = new Matrix4();
|
|
2673
|
+
const patchedMeshes = /* @__PURE__ */ new WeakSet();
|
|
2615
2674
|
function patchMatricesInExternalMode(scene) {
|
|
2616
2675
|
scene.traverse((child) => {
|
|
2617
2676
|
const mesh = child;
|
|
2618
|
-
if (!mesh.isMesh) return;
|
|
2677
|
+
if (!mesh.isMesh || patchedMeshes.has(mesh)) return;
|
|
2678
|
+
patchedMeshes.add(mesh);
|
|
2619
2679
|
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
2620
2680
|
for (const material of materials) {
|
|
2621
2681
|
const onBeforeRender = material.onBeforeRender.bind(material);
|
|
@@ -3011,12 +3071,15 @@ class CoordinatesSystem {
|
|
|
3011
3071
|
return out;
|
|
3012
3072
|
}
|
|
3013
3073
|
/**
|
|
3014
|
-
* Converts a point from
|
|
3074
|
+
* Converts a point from NDC to world space by intersecting the ground plane (z=0).
|
|
3075
|
+
* Returns 2D world coordinates (z is always 0). This is appropriate for coordinate
|
|
3076
|
+
* conversions that don't need to account for 3D geometry (e.g. canvasToSvg, camera controls).
|
|
3077
|
+
* For pointer events that should land on 3D geometry, use scene raycasting instead.
|
|
3015
3078
|
* @param ndcCoords Point in NDC (normalized device coordinates)
|
|
3016
3079
|
* @param out Optional output vector
|
|
3017
|
-
* @returns Point in world space
|
|
3080
|
+
* @returns Point on the ground plane in world space, or undefined if the ray is parallel to the plane
|
|
3018
3081
|
*/
|
|
3019
|
-
|
|
3082
|
+
ndcToWorldPlane(ndcCoords, out = new Vector3()) {
|
|
3020
3083
|
return this.pickingSystem.intersectPlane(ndcCoords, this.cameraSystem.camera, out);
|
|
3021
3084
|
}
|
|
3022
3085
|
/**
|
|
@@ -3046,7 +3109,7 @@ class CoordinatesSystem {
|
|
|
3046
3109
|
const vec2 = new Vector2();
|
|
3047
3110
|
const vec3 = new Vector3();
|
|
3048
3111
|
const ndcPoint = this.canvasToNDC(point, vec2);
|
|
3049
|
-
const worldPoint = this.
|
|
3112
|
+
const worldPoint = this.ndcToWorldPlane(ndcPoint, vec3);
|
|
3050
3113
|
if (!worldPoint) return [];
|
|
3051
3114
|
const result = [];
|
|
3052
3115
|
for (const sceneState of this.sceneSystem.sceneStates) {
|
|
@@ -5665,6 +5728,9 @@ const subsetOfTHREE = {
|
|
|
5665
5728
|
};
|
|
5666
5729
|
CameraControls.install({ THREE: subsetOfTHREE });
|
|
5667
5730
|
const logger$4 = createLogger("cameraController");
|
|
5731
|
+
const ELEVATION_PRECISION = 0.1;
|
|
5732
|
+
const ELEVATION_HEADROOM_RATIO = 0.98;
|
|
5733
|
+
const DEPTH_BUFFER_LEVELS = 2 ** 24;
|
|
5668
5734
|
class CameraController extends CameraControls {
|
|
5669
5735
|
/**
|
|
5670
5736
|
* @param camera {@link PerspectiveCamera} instance
|
|
@@ -5686,6 +5752,7 @@ class CameraController extends CameraControls {
|
|
|
5686
5752
|
three: CameraController.ACTION.NONE
|
|
5687
5753
|
};
|
|
5688
5754
|
this.touchCancelListener = () => this.cancel();
|
|
5755
|
+
void this.rotatePolarTo(0, false);
|
|
5689
5756
|
}
|
|
5690
5757
|
/**
|
|
5691
5758
|
* Get bearing angle between current camera orientation and true north (in radians).
|
|
@@ -5696,15 +5763,50 @@ class CameraController extends CameraControls {
|
|
|
5696
5763
|
}
|
|
5697
5764
|
update(delta) {
|
|
5698
5765
|
const needsUpdate = super.update(delta);
|
|
5699
|
-
if (needsUpdate
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5766
|
+
if (needsUpdate) {
|
|
5767
|
+
this.updateNearPlane();
|
|
5768
|
+
if (delta > 0) {
|
|
5769
|
+
const position = this.camera.position.toArray().map((value) => +value.toFixed(2));
|
|
5770
|
+
const target = this._target.toArray().map((value) => +value.toFixed(2));
|
|
5771
|
+
const { theta, phi, radius } = this._spherical;
|
|
5772
|
+
const spherical = [theta * RAD2DEG, phi * RAD2DEG, radius].map((value) => +value.toFixed(2));
|
|
5773
|
+
const clippingPlanes = [this.camera.near, this.camera.far];
|
|
5774
|
+
logger$4.debug("camera update %O", { position, target, spherical, clippingPlanes });
|
|
5775
|
+
}
|
|
5705
5776
|
}
|
|
5706
5777
|
return needsUpdate;
|
|
5707
5778
|
}
|
|
5779
|
+
/**
|
|
5780
|
+
* Updates the near clipping plane to maintain consistent depth buffer precision
|
|
5781
|
+
* at the scene plane regardless of zoom level.
|
|
5782
|
+
*
|
|
5783
|
+
* Two constraints determine the near plane:
|
|
5784
|
+
* - **Headroom**: near = D × (1 − headroom ratio). Dominates at close/default zoom,
|
|
5785
|
+
* keeping headroom at exactly the target ratio. Precision exceeds the target.
|
|
5786
|
+
* - **Precision**: near = D² / (2²⁴ × P). Dominates at far zoom, maintaining target
|
|
5787
|
+
* precision at the cost of reduced headroom.
|
|
5788
|
+
*
|
|
5789
|
+
* The larger of the two is used (precision is never sacrificed for headroom).
|
|
5790
|
+
* The depth precision P is derived from {@link ELEVATION_PRECISION} and the current
|
|
5791
|
+
* {@link CameraControls.maxPolarAngle}, so it adapts when pitch limits change.
|
|
5792
|
+
*
|
|
5793
|
+
* Depth precision at the floor plane (z=0) is the guaranteed minimum. Content above
|
|
5794
|
+
* (closer to camera) has quadratically better precision: P × (d/D)². Content below the
|
|
5795
|
+
* floor degrades quadratically but remains within ~1% for practical depths (< 1% of D).
|
|
5796
|
+
*/
|
|
5797
|
+
updateNearPlane() {
|
|
5798
|
+
const camera = this.camera;
|
|
5799
|
+
const D = this.distance;
|
|
5800
|
+
if (D <= 0) return;
|
|
5801
|
+
const cosPitch = Math.max(Math.cos(this.maxPolarAngle), 0.01);
|
|
5802
|
+
const depthPrecision = ELEVATION_PRECISION * cosPitch;
|
|
5803
|
+
const headroomNear = D * (1 - ELEVATION_HEADROOM_RATIO);
|
|
5804
|
+
const precisionNear = D * D / (DEPTH_BUFFER_LEVELS * depthPrecision);
|
|
5805
|
+
const near = Math.max(headroomNear, precisionNear);
|
|
5806
|
+
if (camera.near === near) return;
|
|
5807
|
+
camera.near = near;
|
|
5808
|
+
camera.updateProjectionMatrix();
|
|
5809
|
+
}
|
|
5708
5810
|
connect(domElement) {
|
|
5709
5811
|
super.connect(domElement);
|
|
5710
5812
|
domElement.addEventListener("touchcancel", this.touchCancelListener);
|
|
@@ -6074,7 +6176,7 @@ class RollHandler extends Handler {
|
|
|
6074
6176
|
}
|
|
6075
6177
|
setPivot(e) {
|
|
6076
6178
|
this.coordinatesSystem.canvasToNDC(e.offsetCenter, this.pivotNDC);
|
|
6077
|
-
if (!this.coordinatesSystem.
|
|
6179
|
+
if (!this.coordinatesSystem.ndcToWorldPlane(this.pivotNDC, this.pivotWorld)) return false;
|
|
6078
6180
|
this.controller.getPosition(this.cameraPosition);
|
|
6079
6181
|
this.controller.getTarget(this.targetWorld);
|
|
6080
6182
|
this.cameraForward.copy(this.targetWorld).sub(this.cameraPosition);
|
|
@@ -6160,7 +6262,7 @@ class InteractionsSystem {
|
|
|
6160
6262
|
if (internalCameraSystem) {
|
|
6161
6263
|
this.eventManager = new EventManager(this.canvas, {
|
|
6162
6264
|
recognizers: [Rotate, [Pan, { event: "pitch", pointers: 2 }, "rotate"]]
|
|
6163
|
-
//
|
|
6265
|
+
// TODO: Double click to zoom
|
|
6164
6266
|
});
|
|
6165
6267
|
this.handlers = {
|
|
6166
6268
|
pan: new PanHandler(this.canvas, this.eventManager, internalCameraSystem, coordinatesSystem),
|
|
@@ -6188,8 +6290,6 @@ class InteractionsSystem {
|
|
|
6188
6290
|
});
|
|
6189
6291
|
this.handlers.pan.enable();
|
|
6190
6292
|
this.handlers.zoom.enable();
|
|
6191
|
-
this.handlers.roll.enable();
|
|
6192
|
-
this.handlers.pitch.enable();
|
|
6193
6293
|
}
|
|
6194
6294
|
}
|
|
6195
6295
|
/**
|
|
@@ -6251,15 +6351,18 @@ class InteractionsSystem {
|
|
|
6251
6351
|
if (isDragging || !hasListeners) return;
|
|
6252
6352
|
const mousePointer = eventToCanvas(event);
|
|
6253
6353
|
coordinatesSystem.canvasToNDC(mousePointer, this.mousePointerNDC);
|
|
6254
|
-
|
|
6255
|
-
const intersectionsByScene = this.viewportAccess.getIntersectedObjectsByScene(this.mousePointerNDC);
|
|
6354
|
+
const raycastResults = this.viewportAccess.raycastByScene(this.mousePointerNDC);
|
|
6256
6355
|
const data = [];
|
|
6257
|
-
for (const { sceneId, intersections } of
|
|
6258
|
-
const
|
|
6259
|
-
const
|
|
6356
|
+
for (const { sceneId, intersections } of raycastResults) {
|
|
6357
|
+
const interactive = intersections.filter((i) => isInteractive(i.object));
|
|
6358
|
+
const closestHit = interactive[0] ?? intersections[0];
|
|
6359
|
+
const worldPoint = closestHit ? this.mousePointerWorld.copy(closestHit.point) : coordinatesSystem.ndcToWorldPlane(this.mousePointerNDC, this.mousePointerWorld);
|
|
6360
|
+
if (!worldPoint) continue;
|
|
6361
|
+
const point = coordinatesSystem.worldToModel(worldPoint, this.mousePointerModel, sceneId);
|
|
6362
|
+
const defs = this.layerSystem.getIntersectedDefs(interactive);
|
|
6260
6363
|
data.push({ point: { x: point.x, y: point.y }, defs, sceneId });
|
|
6261
6364
|
}
|
|
6262
|
-
this.events.emit(eventType, { event, data });
|
|
6365
|
+
if (data.length > 0) this.events.emit(eventType, { event, data });
|
|
6263
6366
|
};
|
|
6264
6367
|
const mousemove = (event) => sharedMouseHandler("mousemove", event);
|
|
6265
6368
|
const mouseout = (event) => sharedMouseHandler("mouseout", event);
|
|
@@ -6295,7 +6398,7 @@ class UpdatesSystem {
|
|
|
6295
6398
|
constructor(ctx, layerSystem) {
|
|
6296
6399
|
__publicField(this, "pendingDefs", /* @__PURE__ */ new Set());
|
|
6297
6400
|
__publicField(this, "culledDefs", /* @__PURE__ */ new Set());
|
|
6298
|
-
__publicField(this, "
|
|
6401
|
+
__publicField(this, "defSphere", new Sphere());
|
|
6299
6402
|
__publicField(this, "useUpdateBuffering", true);
|
|
6300
6403
|
this.ctx = ctx;
|
|
6301
6404
|
this.layerSystem = layerSystem;
|
|
@@ -6336,10 +6439,9 @@ class UpdatesSystem {
|
|
|
6336
6439
|
let culled = false;
|
|
6337
6440
|
this.pendingDefs.delete(def);
|
|
6338
6441
|
if (isTextDef(def) || isImageDef(def)) {
|
|
6339
|
-
const
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
culled = !frustum2.intersectsBox(this.defBounds);
|
|
6442
|
+
const origin2 = isImageDef(def) ? def.origin : void 0;
|
|
6443
|
+
computeBoundingSphere(def.bounds, origin2, this.defSphere);
|
|
6444
|
+
culled = !frustum2.intersectsSphere(this.defSphere);
|
|
6343
6445
|
}
|
|
6344
6446
|
if (culled) this.culledDefs.add(def);
|
|
6345
6447
|
else {
|
|
@@ -6384,8 +6486,8 @@ class InternalCameraSystem {
|
|
|
6384
6486
|
const h = ctx.getDrawingBufferSizePx()[1];
|
|
6385
6487
|
this.prevViewportHeightPx = h;
|
|
6386
6488
|
this.camera = new PerspectiveCamera(this.defaultFov);
|
|
6489
|
+
this.camera.up.set(0, 0, -1);
|
|
6387
6490
|
this.controller = new CameraController(this.camera);
|
|
6388
|
-
void this.controller.rotatePolarTo(0, false);
|
|
6389
6491
|
}
|
|
6390
6492
|
/** Current camera zoom factor. */
|
|
6391
6493
|
get zoomFactor() {
|
|
@@ -6418,12 +6520,8 @@ class InternalCameraSystem {
|
|
|
6418
6520
|
initCamera(viewbox) {
|
|
6419
6521
|
this.zoomBounds = [0.1, viewbox.size.x > 1e4 ? 100 : 35];
|
|
6420
6522
|
this.updateCamera();
|
|
6421
|
-
this.
|
|
6422
|
-
this.camera.position.set(0, 0, -this.zoomIdentityDistance);
|
|
6423
|
-
this.camera.lookAt(0, 0, 0);
|
|
6523
|
+
this.controller.update(0);
|
|
6424
6524
|
this.camera.updateMatrixWorld();
|
|
6425
|
-
this.camera.up.set(0, 0, -1);
|
|
6426
|
-
this.controller.updateCameraUp();
|
|
6427
6525
|
}
|
|
6428
6526
|
/** Updates the camera when the renderer size changes. */
|
|
6429
6527
|
updateCamera() {
|
|
@@ -6435,8 +6533,7 @@ class InternalCameraSystem {
|
|
|
6435
6533
|
const maxDistance = Math.abs(newZoomIdentity / this.zoomBounds[0]);
|
|
6436
6534
|
const minDistance = Math.abs(newZoomIdentity / this.zoomBounds[1]);
|
|
6437
6535
|
this.camera.aspect = w / (h || 1);
|
|
6438
|
-
this.camera.
|
|
6439
|
-
this.camera.far = Math.max(maxDistance, this.camera.near) * 2;
|
|
6536
|
+
this.camera.far = Math.max(maxDistance, 0.1) * 2;
|
|
6440
6537
|
this.camera.updateProjectionMatrix();
|
|
6441
6538
|
this.controller.minDistance = minDistance;
|
|
6442
6539
|
this.controller.maxDistance = maxDistance;
|
|
@@ -6471,21 +6568,21 @@ class InternalCameraSystem {
|
|
|
6471
6568
|
}
|
|
6472
6569
|
}
|
|
6473
6570
|
class PickingSystem {
|
|
6474
|
-
/** */
|
|
6475
6571
|
constructor() {
|
|
6476
6572
|
__publicField(this, "raycaster", new Raycaster());
|
|
6477
6573
|
__publicField(this, "ndcPoint", new Vector2());
|
|
6478
6574
|
__publicField(this, "viewboxPlane", new Plane(new Vector3(0, 0, 1), 0));
|
|
6479
|
-
this.raycaster.layers.set(INTERACTIVE_LAYER);
|
|
6480
6575
|
}
|
|
6481
6576
|
/**
|
|
6482
|
-
*
|
|
6577
|
+
* Raycasts the scene and returns all visible intersections sorted by distance.
|
|
6578
|
+
* Does not filter by interaction layer — consumers are responsible for partitioning
|
|
6579
|
+
* interactive vs non-interactive results using {@link isInteractive}.
|
|
6483
6580
|
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
6484
6581
|
* @param scene {@link Scene} instance
|
|
6485
6582
|
* @param camera {@link Camera} instance
|
|
6486
|
-
* @returns Array of {@link Intersection} instances
|
|
6583
|
+
* @returns Array of {@link Intersection} instances, nearest first
|
|
6487
6584
|
*/
|
|
6488
|
-
|
|
6585
|
+
raycast(ndcCoords, scene, camera) {
|
|
6489
6586
|
this.setRaycasterFromCamera(ndcCoords, camera);
|
|
6490
6587
|
const intersections = this.raycaster.intersectObject(scene, true);
|
|
6491
6588
|
return intersections.filter((i) => isVisible(i.object));
|
|
@@ -6608,7 +6705,7 @@ class SceneSystem {
|
|
|
6608
6705
|
const scaleFactor = Math.min(visibleRectSize.width, visibleRectSize.height);
|
|
6609
6706
|
const { x: centerX, y: centerY } = viewbox.center;
|
|
6610
6707
|
this.translationMatrix.makeTranslation(-centerX, -centerY, 0);
|
|
6611
|
-
this.scaleMatrix.makeScale(scaleFactor, scaleFactor, 1);
|
|
6708
|
+
this.scaleMatrix.makeScale(scaleFactor, scaleFactor, -1);
|
|
6612
6709
|
targetMatrix.copy(this.translationMatrix).premultiply(this.scaleMatrix);
|
|
6613
6710
|
const viewportRectCenter = this.tempVector2.copy(viewportRectPx.center);
|
|
6614
6711
|
const offset = viewportRectCenter.sub({ x: bufferW / 2, y: bufferH / 2 });
|
|
@@ -6680,15 +6777,16 @@ class ViewportSystem {
|
|
|
6680
6777
|
this.visibleRectCss = rect;
|
|
6681
6778
|
}
|
|
6682
6779
|
/**
|
|
6683
|
-
*
|
|
6780
|
+
* Raycasts all loaded scenes and returns all visible intersections per scene.
|
|
6781
|
+
* Results are not filtered by interaction layer — consumers partition as needed.
|
|
6684
6782
|
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
6685
|
-
* @returns Array of {@link
|
|
6783
|
+
* @returns Array of {@link SceneRaycastResult} instances
|
|
6686
6784
|
*/
|
|
6687
|
-
|
|
6785
|
+
raycastByScene(ndcCoords) {
|
|
6688
6786
|
const result = [];
|
|
6689
6787
|
for (const sceneState of this.sceneSystem.sceneStates) {
|
|
6690
6788
|
if (!sceneState.loaded) continue;
|
|
6691
|
-
const intersections = this.pickingSystem.
|
|
6789
|
+
const intersections = this.pickingSystem.raycast(ndcCoords, sceneState.scene, this.camera);
|
|
6692
6790
|
result.push({ intersections, sceneId: sceneState.id });
|
|
6693
6791
|
}
|
|
6694
6792
|
return result;
|
|
@@ -7046,7 +7144,6 @@ class Renderer {
|
|
|
7046
7144
|
} else if (sceneState.scene.children.length === 0) {
|
|
7047
7145
|
const root = this.layerSystem.buildScene(sceneState.sceneDef);
|
|
7048
7146
|
scene.add(root);
|
|
7049
|
-
if (this.isExternalMode) patchMatricesInExternalMode(scene);
|
|
7050
7147
|
}
|
|
7051
7148
|
}
|
|
7052
7149
|
const justLoaded = loadedChanged && sceneState.loaded;
|
|
@@ -7054,6 +7151,7 @@ class Renderer {
|
|
|
7054
7151
|
this.viewportSystem.updatePtScale(id);
|
|
7055
7152
|
const hasDefsUpdated = this.updatesSystem.processPendingUpdates(frustum2, 3);
|
|
7056
7153
|
const forceRedraw = hasControlsUpdated || hasDefsUpdated || justLoaded || this.isExternalMode || this.ui;
|
|
7154
|
+
if (this.isExternalMode) patchMatricesInExternalMode(scene);
|
|
7057
7155
|
if (this.needsRedraw || forceRedraw) this.renderer.render(scene, camera);
|
|
7058
7156
|
if (hasDefsUpdated || this.needsRedraw) this.viewportSystem.invalidateSceneBounds(sceneState);
|
|
7059
7157
|
}
|
|
@@ -7090,8 +7188,7 @@ class Renderer {
|
|
|
7090
7188
|
}
|
|
7091
7189
|
initScene(sceneDef, sceneId) {
|
|
7092
7190
|
const root = this.layerSystem.buildScene(sceneDef);
|
|
7093
|
-
|
|
7094
|
-
if (this.isExternalMode) patchMatricesInExternalMode(scene);
|
|
7191
|
+
this.viewportSystem.initScene(sceneDef, sceneId).add(root);
|
|
7095
7192
|
}
|
|
7096
7193
|
// https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
|
|
7097
7194
|
resizeCanvasToDisplaySize() {
|