@expofp/renderer 3.0.1 → 3.1.1
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 +22 -6
- package/dist/index.js +279 -135
- 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.
|
|
@@ -455,6 +470,7 @@ export declare class Renderer {
|
|
|
455
470
|
private memoryInfoExtension;
|
|
456
471
|
private memoryInfo?;
|
|
457
472
|
private eventSystem;
|
|
473
|
+
private lightsSystem;
|
|
458
474
|
private layerSystem;
|
|
459
475
|
private viewportSystem;
|
|
460
476
|
private updatesSystem;
|
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, MeshPhongMaterial, MeshBasicMaterial, LessEqualDepth, Texture, Group, PlaneGeometry, SRGBColorSpace, Vector2, Quaternion, BufferGeometry, LinearSRGBColorSpace, Mesh, AmbientLight, DirectionalLight, 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,48 @@ 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
|
|
1106
|
-
|
|
1107
|
-
opacity: partialParams.opacity ?? 1
|
|
1108
|
-
};
|
|
1109
|
-
const material = new MeshBasicMaterial({
|
|
1104
|
+
createColorMaterial(params = {}) {
|
|
1105
|
+
const materialConstructor = params.lightingEnable ? MeshPhongMaterial : MeshBasicMaterial;
|
|
1106
|
+
const material = new materialConstructor({
|
|
1110
1107
|
...sharedParameters,
|
|
1111
|
-
color: params.color,
|
|
1112
|
-
opacity: params.opacity
|
|
1108
|
+
color: params.color ?? 16777215,
|
|
1109
|
+
opacity: params.opacity ?? 1
|
|
1113
1110
|
});
|
|
1111
|
+
if (params.depthEnable) material.depthFunc = LessEqualDepth;
|
|
1114
1112
|
addDimToMaterial(material);
|
|
1113
|
+
this.addPolygonOffset(material);
|
|
1115
1114
|
return material;
|
|
1116
1115
|
}
|
|
1117
1116
|
/**
|
|
1118
1117
|
* Creates a texture material.
|
|
1119
|
-
* @param
|
|
1120
|
-
* @param uvOffset whether to enable uv offset with per instance uniforms (for texture atlases)
|
|
1118
|
+
* @param params {@link MaterialTextureParams}
|
|
1121
1119
|
* @returns MeshBasicMaterial instance
|
|
1122
1120
|
*/
|
|
1123
|
-
createTextureMaterial(
|
|
1124
|
-
const material = new MeshBasicMaterial({ ...sharedParameters, map });
|
|
1125
|
-
if (
|
|
1121
|
+
createTextureMaterial(params) {
|
|
1122
|
+
const material = new MeshBasicMaterial({ ...sharedParameters, map: params.map });
|
|
1123
|
+
if (params.depthEnable) material.depthFunc = LessEqualDepth;
|
|
1124
|
+
if (params.uvOffset) {
|
|
1126
1125
|
material.onBeforeCompile = (shader) => {
|
|
1127
1126
|
shader.vertexShader = shader.vertexShader.replace(
|
|
1128
1127
|
"#include <uv_vertex>",
|
|
1129
1128
|
/*glsl*/
|
|
1130
1129
|
`
|
|
1131
1130
|
#include <uv_vertex>
|
|
1132
|
-
vMapUv = uv * uvOffset.zw + uvOffset.xy;
|
|
1131
|
+
vMapUv = uv * uvOffset.zw + uvOffset.xy;
|
|
1133
1132
|
`
|
|
1134
1133
|
);
|
|
1135
1134
|
};
|
|
1136
1135
|
}
|
|
1137
1136
|
addDimToMaterial(material);
|
|
1137
|
+
this.addPolygonOffset(material);
|
|
1138
1138
|
return material;
|
|
1139
1139
|
}
|
|
1140
1140
|
/**
|
|
@@ -1158,6 +1158,40 @@ class MaterialSystem {
|
|
|
1158
1158
|
}
|
|
1159
1159
|
return this.backgroundMaterial;
|
|
1160
1160
|
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Patches a material's onBeforeRender to dynamically set polygonOffset based on the
|
|
1163
|
+
* object's renderOrder. Later layers (higher renderOrder) get a larger negative offset,
|
|
1164
|
+
* giving them a slight depth buffer advantage over earlier layers at the same elevation.
|
|
1165
|
+
* This prevents z-fighting between coplanar 3D content across different layers.
|
|
1166
|
+
*
|
|
1167
|
+
* The units multiplier is derived from {@link CameraControls.maxPolarAngle}:
|
|
1168
|
+
*
|
|
1169
|
+
* multiplier = ⌈tan(maxPolarAngle)⌉
|
|
1170
|
+
*
|
|
1171
|
+
* At steep camera pitch, coplanar surfaces from different draw calls (different
|
|
1172
|
+
* BatchedMesh instances) compute slightly different depth values at the same pixel
|
|
1173
|
+
* due to floating-point interpolation differences in the vertex shader. These errors
|
|
1174
|
+
* are proportional to the depth gradient across the surface, which for a horizontal
|
|
1175
|
+
* plane at pitch angle θ is proportional to tan(θ). The multiplier ensures the
|
|
1176
|
+
* per-layer polygon offset exceeds these errors at the configured maximum pitch.
|
|
1177
|
+
*
|
|
1178
|
+
* For elevated content the effective viewing angle can exceed maxPolarAngle, but this
|
|
1179
|
+
* is self-correcting: the screen-space height of the surface shrinks as cos(angle),
|
|
1180
|
+
* so the total visible z-fighting (pixels × severity ∝ cos × tan = sin) is bounded
|
|
1181
|
+
* and becomes subpixel as the effective angle approaches 90°.
|
|
1182
|
+
*
|
|
1183
|
+
* With maxPolarAngle = 85°: ⌈tan(85°)⌉ = ⌈11.43⌉ = 12.
|
|
1184
|
+
* @param material Material to patch
|
|
1185
|
+
*/
|
|
1186
|
+
addPolygonOffset(material) {
|
|
1187
|
+
const onBeforeRender = material.onBeforeRender.bind(material);
|
|
1188
|
+
material.onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
|
|
1189
|
+
onBeforeRender(renderer, scene, camera, geometry, object, group);
|
|
1190
|
+
material.polygonOffset = true;
|
|
1191
|
+
material.polygonOffsetFactor = 0;
|
|
1192
|
+
material.polygonOffsetUnits = -object.renderOrder * POLYGON_OFFSET_MULTIPLIER;
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1161
1195
|
}
|
|
1162
1196
|
function isShapeDef(def) {
|
|
1163
1197
|
return def.shape !== void 0;
|
|
@@ -1189,36 +1223,14 @@ function isLineLayer(layer) {
|
|
|
1189
1223
|
function isLayerLayer(layer) {
|
|
1190
1224
|
return layer.children[0] && isLayerDef(layer.children[0]);
|
|
1191
1225
|
}
|
|
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
1226
|
const INTERACTIVE_LAYER = 1;
|
|
1218
|
-
function
|
|
1219
|
-
|
|
1227
|
+
function isInteractive(object) {
|
|
1228
|
+
return object.layers.isEnabled(INTERACTIVE_LAYER);
|
|
1229
|
+
}
|
|
1230
|
+
function setInteractive(object, isInteractive2) {
|
|
1231
|
+
if (isInteractive2) object.layers.enable(INTERACTIVE_LAYER);
|
|
1220
1232
|
else object.layers.disable(INTERACTIVE_LAYER);
|
|
1221
|
-
object.children.forEach((child) => setInteractive(child,
|
|
1233
|
+
object.children.forEach((child) => setInteractive(child, isInteractive2));
|
|
1222
1234
|
}
|
|
1223
1235
|
function isVisible(object) {
|
|
1224
1236
|
if (!object.visible) return false;
|
|
@@ -1483,6 +1495,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1483
1495
|
}
|
|
1484
1496
|
buildLayer(layer) {
|
|
1485
1497
|
var _a2;
|
|
1498
|
+
const is3D = layer.mode === "3D";
|
|
1486
1499
|
const group = new Group();
|
|
1487
1500
|
const images = layer.children;
|
|
1488
1501
|
const bins = this.packImages(images);
|
|
@@ -1497,7 +1510,11 @@ class ImageSystem extends RenderableSystem {
|
|
|
1497
1510
|
).sort((a, b) => a.originalIndex - b.originalIndex);
|
|
1498
1511
|
const instanceCount = rectsWithDef.length;
|
|
1499
1512
|
const texture = createAtlas(bin);
|
|
1500
|
-
const instanceMaterial = this.materialSystem.createTextureMaterial(
|
|
1513
|
+
const instanceMaterial = this.materialSystem.createTextureMaterial({
|
|
1514
|
+
map: texture,
|
|
1515
|
+
uvOffset: true,
|
|
1516
|
+
depthEnable: is3D
|
|
1517
|
+
});
|
|
1501
1518
|
const instanceGeometry = new PlaneGeometry();
|
|
1502
1519
|
const vertexCount = instanceGeometry.attributes["position"].count;
|
|
1503
1520
|
const indexCount = ((_a2 = instanceGeometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
@@ -1582,7 +1599,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1582
1599
|
const bounds = imageDef.bounds;
|
|
1583
1600
|
const origin2 = imageDef.origin ?? [0.5, 0.5];
|
|
1584
1601
|
this.originTranslationMatrix.makeTranslation(0.5 - origin2[0], 0.5 - origin2[1], 0);
|
|
1585
|
-
this.globalTranslationMatrix.makeTranslation(bounds.center.x, bounds.center.y,
|
|
1602
|
+
this.globalTranslationMatrix.makeTranslation(bounds.center.x, bounds.center.y, bounds.elevation);
|
|
1586
1603
|
this.scaleMatrix.makeScale(bounds.size.x, bounds.size.y, 1);
|
|
1587
1604
|
this.rotationMatrix.makeRotationZ(bounds.rotation);
|
|
1588
1605
|
const matrix = this.originTranslationMatrix.premultiply(this.scaleMatrix).premultiply(this.rotationMatrix).premultiply(this.globalTranslationMatrix);
|
|
@@ -1732,10 +1749,13 @@ class Rect {
|
|
|
1732
1749
|
* @param min Top left corner of the rectangle.
|
|
1733
1750
|
* @param max Bottom right corner of the rectangle.
|
|
1734
1751
|
* @param rotation Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise.
|
|
1752
|
+
* @param elevation Optional elevation. Positive values raise geometry toward the viewer. Defaults to 0.
|
|
1735
1753
|
*/
|
|
1736
|
-
constructor(min, max, rotation) {
|
|
1754
|
+
constructor(min, max, rotation, elevation) {
|
|
1737
1755
|
/** Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise. */
|
|
1738
1756
|
__publicField(this, "rotation");
|
|
1757
|
+
/** Optional elevation of the rectangle. Positive values raise geometry toward the viewer. Defaults to 0. */
|
|
1758
|
+
__publicField(this, "elevation");
|
|
1739
1759
|
__publicField(this, "_min");
|
|
1740
1760
|
__publicField(this, "_max");
|
|
1741
1761
|
__publicField(this, "_center", new Vector2());
|
|
@@ -1743,6 +1763,7 @@ class Rect {
|
|
|
1743
1763
|
this._min = createVector2(min);
|
|
1744
1764
|
this._max = createVector2(max);
|
|
1745
1765
|
this.rotation = rotation ?? 0;
|
|
1766
|
+
this.elevation = elevation ?? 0;
|
|
1746
1767
|
this.updateCenterAndSize();
|
|
1747
1768
|
}
|
|
1748
1769
|
/** Top left corner of the rectangle. */
|
|
@@ -1785,14 +1806,15 @@ class Rect {
|
|
|
1785
1806
|
* Creates a rectangle from an SVG rectangle element.
|
|
1786
1807
|
* @param rect {@link SVGRectElement} or {@link SVGImageElement}
|
|
1787
1808
|
* @param rotation Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise.
|
|
1809
|
+
* @param elevation Optional elevation. Positive values raise geometry toward the viewer. Defaults to 0.
|
|
1788
1810
|
* @returns new {@link Rect} instance
|
|
1789
1811
|
*/
|
|
1790
|
-
static fromSvg(rect, rotation) {
|
|
1812
|
+
static fromSvg(rect, rotation, elevation) {
|
|
1791
1813
|
const x = rect.x.baseVal.value;
|
|
1792
1814
|
const y = rect.y.baseVal.value;
|
|
1793
1815
|
const width = rect.width.baseVal.value;
|
|
1794
1816
|
const height = rect.height.baseVal.value;
|
|
1795
|
-
return new Rect([x, y], [x + width, y + height], rotation);
|
|
1817
|
+
return new Rect([x, y], [x + width, y + height], rotation, elevation);
|
|
1796
1818
|
}
|
|
1797
1819
|
/**
|
|
1798
1820
|
* Moves the rectangle by the given offset.
|
|
@@ -1848,7 +1870,11 @@ class Polygon {
|
|
|
1848
1870
|
get indices() {
|
|
1849
1871
|
return this._indices;
|
|
1850
1872
|
}
|
|
1851
|
-
/**
|
|
1873
|
+
/**
|
|
1874
|
+
* Bounding rectangle of the polygon.
|
|
1875
|
+
* This is a 2D axis-aligned bounding box computed from the X/Y projection of vertices.
|
|
1876
|
+
* The Z extent of the polygon is not tracked.
|
|
1877
|
+
*/
|
|
1852
1878
|
get bounds() {
|
|
1853
1879
|
return this._bbox;
|
|
1854
1880
|
}
|
|
@@ -1858,11 +1884,12 @@ class Polygon {
|
|
|
1858
1884
|
* @returns new {@link Polygon} instance
|
|
1859
1885
|
*/
|
|
1860
1886
|
static fromRect(rect) {
|
|
1887
|
+
const z = rect.elevation;
|
|
1861
1888
|
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 }
|
|
1889
|
+
{ x: rect.min.x, y: rect.min.y, z },
|
|
1890
|
+
{ x: rect.max.x, y: rect.min.y, z },
|
|
1891
|
+
{ x: rect.max.x, y: rect.max.y, z },
|
|
1892
|
+
{ x: rect.min.x, y: rect.max.y, z }
|
|
1866
1893
|
];
|
|
1867
1894
|
const indices = [
|
|
1868
1895
|
[0, 1, 2],
|
|
@@ -1914,7 +1941,8 @@ class Polygon {
|
|
|
1914
1941
|
}
|
|
1915
1942
|
/**
|
|
1916
1943
|
* 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
|
|
1944
|
+
* @param scaleFactor Can be a single number or a 2D vector. If a single number is provided, both horizontal and
|
|
1945
|
+
* vertical axes will be scaled by the same factor. Z is unchanged in both cases.
|
|
1918
1946
|
* @param origin Origin of the scaling. If omitted, defaults to the bounding rectangle center.
|
|
1919
1947
|
* @returns this {@link Polygon} instance
|
|
1920
1948
|
*/
|
|
@@ -1942,6 +1970,31 @@ class Polygon {
|
|
|
1942
1970
|
}
|
|
1943
1971
|
}
|
|
1944
1972
|
const tempVector2 = new Vector2();
|
|
1973
|
+
function groupBy(list, keyGetter) {
|
|
1974
|
+
const map = /* @__PURE__ */ new Map();
|
|
1975
|
+
list.forEach((item) => {
|
|
1976
|
+
const key = keyGetter(item);
|
|
1977
|
+
const collection = map.get(key);
|
|
1978
|
+
if (!collection) {
|
|
1979
|
+
map.set(key, [item]);
|
|
1980
|
+
} else {
|
|
1981
|
+
collection.push(item);
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
return map;
|
|
1985
|
+
}
|
|
1986
|
+
function partition(list, pred) {
|
|
1987
|
+
const truthy = [];
|
|
1988
|
+
const falsy = [];
|
|
1989
|
+
for (const item of list) {
|
|
1990
|
+
if (pred(item)) {
|
|
1991
|
+
truthy.push(item);
|
|
1992
|
+
} else {
|
|
1993
|
+
falsy.push(item);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return [truthy, falsy];
|
|
1997
|
+
}
|
|
1945
1998
|
function countGeometry(geometry) {
|
|
1946
1999
|
var _a2;
|
|
1947
2000
|
if (geometry.index == null) return { vertices: geometry.getAttribute("position").count, indices: 0 };
|
|
@@ -1950,6 +2003,18 @@ function countGeometry(geometry) {
|
|
|
1950
2003
|
indices: ((_a2 = geometry.index) == null ? void 0 : _a2.count) ?? 0
|
|
1951
2004
|
};
|
|
1952
2005
|
}
|
|
2006
|
+
const sphereCenter = new Vector3();
|
|
2007
|
+
function computeBoundingSphere(bounds, origin2, out = new Sphere()) {
|
|
2008
|
+
const ox = (origin2 == null ? void 0 : origin2[0]) ?? 0.5;
|
|
2009
|
+
const oy = (origin2 == null ? void 0 : origin2[1]) ?? 0.5;
|
|
2010
|
+
const w = bounds.size.x;
|
|
2011
|
+
const h = bounds.size.y;
|
|
2012
|
+
const dx = Math.max(ox, 1 - ox) * w;
|
|
2013
|
+
const dy = Math.max(oy, 1 - oy) * h;
|
|
2014
|
+
sphereCenter.set(bounds.center.x, bounds.center.y, bounds.elevation);
|
|
2015
|
+
out.set(sphereCenter, Math.hypot(dx, dy));
|
|
2016
|
+
return out;
|
|
2017
|
+
}
|
|
1953
2018
|
const logger$a = createLogger("mesh");
|
|
1954
2019
|
extend([namesPlugin]);
|
|
1955
2020
|
class MeshSystem extends RenderableSystem {
|
|
@@ -1964,13 +2029,13 @@ class MeshSystem extends RenderableSystem {
|
|
|
1964
2029
|
__publicField(this, "rotation", new Quaternion());
|
|
1965
2030
|
__publicField(this, "scale", new Vector3());
|
|
1966
2031
|
__publicField(this, "matrix", new Matrix4());
|
|
1967
|
-
__publicField(this, "rectGeometry", new PlaneGeometry(1, 1));
|
|
2032
|
+
__publicField(this, "rectGeometry", new PlaneGeometry(1, 1).deleteAttribute("uv").deleteAttribute("normal"));
|
|
2033
|
+
__publicField(this, "rectGeometry3D", new PlaneGeometry(1, 1).deleteAttribute("uv").toNonIndexed());
|
|
1968
2034
|
__publicField(this, "mapInstanceIdToShapeType", /* @__PURE__ */ new Map());
|
|
1969
2035
|
this.materialSystem = materialSystem;
|
|
1970
|
-
this.rectGeometry.deleteAttribute("normal");
|
|
1971
|
-
this.rectGeometry.deleteAttribute("uv");
|
|
1972
2036
|
}
|
|
1973
2037
|
buildLayer(layer) {
|
|
2038
|
+
const is3D = layer.mode === "3D";
|
|
1974
2039
|
const shapes = layer.children;
|
|
1975
2040
|
const mapShapeToNormColor = /* @__PURE__ */ new Map();
|
|
1976
2041
|
for (const shapeDef of shapes) {
|
|
@@ -1987,21 +2052,15 @@ class MeshSystem extends RenderableSystem {
|
|
|
1987
2052
|
const transparentShapesGrouped = groupBy(transparentShapes, (shapeDef) => mapShapeToNormColor.get(shapeDef).a);
|
|
1988
2053
|
const group = new Group();
|
|
1989
2054
|
for (const [opacity, shapes2] of transparentShapesGrouped) {
|
|
1990
|
-
const transparentMesh = this.buildBatchedMesh(shapes2, opacity);
|
|
2055
|
+
const transparentMesh = this.buildBatchedMesh(shapes2, is3D, opacity);
|
|
1991
2056
|
transparentMesh.name = "transparent";
|
|
1992
2057
|
group.add(transparentMesh);
|
|
1993
2058
|
}
|
|
1994
2059
|
if (opaqueShapes.length) {
|
|
1995
|
-
const opaqueMesh = this.buildBatchedMesh(opaqueShapes);
|
|
2060
|
+
const opaqueMesh = this.buildBatchedMesh(opaqueShapes, is3D);
|
|
1996
2061
|
opaqueMesh.name = "opaque";
|
|
1997
2062
|
group.add(opaqueMesh);
|
|
1998
2063
|
}
|
|
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
2064
|
return group;
|
|
2006
2065
|
}
|
|
2007
2066
|
updateDefImpl(shapeDef, mesh, instanceIds, firstUpdate) {
|
|
@@ -2026,7 +2085,9 @@ class MeshSystem extends RenderableSystem {
|
|
|
2026
2085
|
logger$a.warn("Polygon geometry changing not supported %O", shapeDef);
|
|
2027
2086
|
return;
|
|
2028
2087
|
}
|
|
2029
|
-
|
|
2088
|
+
const geometry = this.buildPolygonGeometry(shape);
|
|
2089
|
+
if (mesh.geometry.hasAttribute("normal")) geometry.computeVertexNormals();
|
|
2090
|
+
mesh.setGeometryAt(geometryId, geometry);
|
|
2030
2091
|
} else if (isRect) {
|
|
2031
2092
|
this.updateRect(shape, mesh, instanceId);
|
|
2032
2093
|
}
|
|
@@ -2040,34 +2101,39 @@ class MeshSystem extends RenderableSystem {
|
|
|
2040
2101
|
mesh.setColorAt(instanceId, this.color.setRGB(color.r / 255, color.g / 255, color.b / 255, SRGBColorSpace));
|
|
2041
2102
|
}
|
|
2042
2103
|
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
|
|
2104
|
+
this.position.set(shape.center.x, shape.center.y, shape.elevation);
|
|
2105
|
+
this.rotation.setFromAxisAngle(new Vector3(0, 0, 1), shape.rotation);
|
|
2045
2106
|
this.scale.set(shape.size.x, shape.size.y, 1);
|
|
2046
2107
|
this.matrix.compose(this.position, this.rotation, this.scale);
|
|
2047
2108
|
mesh.setMatrixAt(instanceId, this.matrix);
|
|
2048
2109
|
}
|
|
2049
|
-
buildBatchedMesh(shapes, opacity = 1) {
|
|
2110
|
+
buildBatchedMesh(shapes, is3D, opacity = 1) {
|
|
2050
2111
|
let vertexCount = 0;
|
|
2051
2112
|
let indexCount = 0;
|
|
2052
2113
|
let rectAdded = false;
|
|
2053
2114
|
const shapeDefToGeometry = /* @__PURE__ */ new Map();
|
|
2115
|
+
const rectGeom = is3D ? this.rectGeometry3D : this.rectGeometry;
|
|
2054
2116
|
for (const shapeDef of shapes) {
|
|
2055
2117
|
let vertices = 0;
|
|
2056
2118
|
let indices = 0;
|
|
2057
2119
|
if (shapeDef.shape instanceof Rect && !rectAdded) {
|
|
2058
2120
|
rectAdded = true;
|
|
2059
|
-
({ vertices, indices } = countGeometry(
|
|
2121
|
+
({ vertices, indices } = countGeometry(rectGeom));
|
|
2060
2122
|
} else if (shapeDef.shape instanceof Polygon) {
|
|
2061
|
-
|
|
2123
|
+
let geometry = this.buildPolygonGeometry(shapeDef.shape);
|
|
2124
|
+
if (is3D) {
|
|
2125
|
+
geometry = geometry.toNonIndexed();
|
|
2126
|
+
geometry.computeVertexNormals();
|
|
2127
|
+
}
|
|
2062
2128
|
shapeDefToGeometry.set(shapeDef, geometry);
|
|
2063
2129
|
({ vertices, indices } = countGeometry(geometry));
|
|
2064
2130
|
}
|
|
2065
2131
|
vertexCount += vertices;
|
|
2066
2132
|
indexCount += indices;
|
|
2067
2133
|
}
|
|
2068
|
-
const material = this.materialSystem.createColorMaterial({ opacity });
|
|
2134
|
+
const material = this.materialSystem.createColorMaterial({ opacity, depthEnable: is3D, lightingEnable: is3D });
|
|
2069
2135
|
const batchedMesh = new BatchedMesh(shapes.length, vertexCount, indexCount, material);
|
|
2070
|
-
const rectGeometryId = rectAdded ? batchedMesh.addGeometry(
|
|
2136
|
+
const rectGeometryId = rectAdded ? batchedMesh.addGeometry(rectGeom) : void 0;
|
|
2071
2137
|
batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, list));
|
|
2072
2138
|
for (const shapeDef of shapes) {
|
|
2073
2139
|
let instanceId;
|
|
@@ -2201,9 +2267,10 @@ class TextSystem extends RenderableSystem {
|
|
|
2201
2267
|
}
|
|
2202
2268
|
}
|
|
2203
2269
|
buildBatchedText(layer) {
|
|
2270
|
+
const is3D = layer.mode === "3D";
|
|
2204
2271
|
const textDefs = layer.children;
|
|
2205
2272
|
const batchedText = new BatchedText();
|
|
2206
|
-
batchedText.material = this.materialSystem.createColorMaterial();
|
|
2273
|
+
batchedText.material = this.materialSystem.createColorMaterial({ depthEnable: is3D });
|
|
2207
2274
|
const mappingData = [];
|
|
2208
2275
|
let instanceId = 0;
|
|
2209
2276
|
for (const textDef of textDefs) {
|
|
@@ -2247,7 +2314,7 @@ class TextSystem extends RenderableSystem {
|
|
|
2247
2314
|
this.worldPosition.copy(this.localPosition).rotateAround({ x: 0, y: 0 }, textDef.bounds.rotation).add(textDef.bounds.center);
|
|
2248
2315
|
this.textScale.copy(this.initialTextScale).multiplyScalar(fontSize * dpr);
|
|
2249
2316
|
text.scale.set(this.textScale.x, this.textScale.y, 1);
|
|
2250
|
-
text.position.set(this.worldPosition.x, this.worldPosition.y,
|
|
2317
|
+
text.position.set(this.worldPosition.x, this.worldPosition.y, textDef.bounds.elevation);
|
|
2251
2318
|
text.rotation.set(0, 0, textDef.bounds.rotation);
|
|
2252
2319
|
this.calculateClipRect(text, textDef, this.localPosition, this.textScale, this.localToMin, this.localToMax);
|
|
2253
2320
|
this.localPosition.y += height * dpr;
|
|
@@ -2482,22 +2549,24 @@ class LayerSystem {
|
|
|
2482
2549
|
* @returns sorted leaf layers for debug logging
|
|
2483
2550
|
*/
|
|
2484
2551
|
initRenderOrder(rootLayer) {
|
|
2485
|
-
const
|
|
2486
|
-
const
|
|
2552
|
+
const twoDLayers = [];
|
|
2553
|
+
const threeDLayers = [];
|
|
2554
|
+
const stack = [
|
|
2555
|
+
{ layer: rootLayer, inheritedMode: rootLayer.mode ?? "2D" }
|
|
2556
|
+
];
|
|
2487
2557
|
while (stack.length) {
|
|
2488
|
-
const layer = stack.pop();
|
|
2558
|
+
const { layer, inheritedMode } = stack.pop();
|
|
2489
2559
|
if (isLayerLayer(layer)) {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
for (const child of children) {
|
|
2560
|
+
for (let i = layer.children.length - 1; i >= 0; i--) {
|
|
2561
|
+
const child = layer.children[i];
|
|
2493
2562
|
this.mapLayerDefToParent.set(child, layer);
|
|
2494
|
-
stack.push(child);
|
|
2563
|
+
stack.push({ layer: child, inheritedMode: child.mode ?? inheritedMode });
|
|
2495
2564
|
}
|
|
2496
2565
|
} else {
|
|
2497
|
-
|
|
2566
|
+
layer.mode ?? (layer.mode = inheritedMode);
|
|
2567
|
+
(layer.mode === "3D" ? threeDLayers : twoDLayers).push(layer);
|
|
2498
2568
|
}
|
|
2499
2569
|
}
|
|
2500
|
-
const [threeDLayers, twoDLayers] = partition(leafLayers, (layer) => layer.mode === "3d");
|
|
2501
2570
|
const sorted = [...twoDLayers, ...threeDLayers];
|
|
2502
2571
|
for (let i = 0; i < sorted.length; i++) {
|
|
2503
2572
|
this.renderOrderMap.set(sorted[i], i + 1);
|
|
@@ -2514,6 +2583,36 @@ class LayerSystem {
|
|
|
2514
2583
|
return fullName;
|
|
2515
2584
|
}
|
|
2516
2585
|
}
|
|
2586
|
+
class LightsSystem {
|
|
2587
|
+
constructor() {
|
|
2588
|
+
__publicField(this, "color", 16777215);
|
|
2589
|
+
__publicField(this, "intensity", 0.5);
|
|
2590
|
+
__publicField(this, "mapSceneToLight", /* @__PURE__ */ new Map());
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Initializes a directional light for the given scene.
|
|
2594
|
+
* @param scene {@link Scene} to add the light to
|
|
2595
|
+
*/
|
|
2596
|
+
initLights(scene) {
|
|
2597
|
+
const ambientLight = new AmbientLight(16777215, this.intensity);
|
|
2598
|
+
scene.add(ambientLight);
|
|
2599
|
+
const directionalLight = new DirectionalLight(this.color, this.intensity * Math.PI * 2);
|
|
2600
|
+
scene.add(directionalLight);
|
|
2601
|
+
scene.add(directionalLight.target);
|
|
2602
|
+
this.mapSceneToLight.set(scene, directionalLight);
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Updates the light direction to match the camera's viewing direction.
|
|
2606
|
+
* @param scene {@link Scene} containing the light
|
|
2607
|
+
* @param camera {@link Camera} to derive the light direction from
|
|
2608
|
+
*/
|
|
2609
|
+
updateLights(scene, camera) {
|
|
2610
|
+
const light = this.mapSceneToLight.get(scene);
|
|
2611
|
+
if (!light) return;
|
|
2612
|
+
const e = scene.matrixWorld.elements;
|
|
2613
|
+
camera.getWorldDirection(light.position).multiply({ x: Math.sign(e[0]), y: Math.sign(e[5]), z: Math.sign(e[10]) }).normalize().negate();
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2517
2616
|
const logger$7 = createLogger("");
|
|
2518
2617
|
function assertNotDisposed(renderer, funcName) {
|
|
2519
2618
|
if (renderer.isDisposed) {
|
|
@@ -2612,10 +2711,12 @@ class ExternalCameraSystem {
|
|
|
2612
2711
|
}
|
|
2613
2712
|
}
|
|
2614
2713
|
const originalProjectionMatrix = new Matrix4();
|
|
2714
|
+
const patchedMeshes = /* @__PURE__ */ new WeakSet();
|
|
2615
2715
|
function patchMatricesInExternalMode(scene) {
|
|
2616
2716
|
scene.traverse((child) => {
|
|
2617
2717
|
const mesh = child;
|
|
2618
|
-
if (!mesh.isMesh) return;
|
|
2718
|
+
if (!mesh.isMesh || patchedMeshes.has(mesh)) return;
|
|
2719
|
+
patchedMeshes.add(mesh);
|
|
2619
2720
|
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
2620
2721
|
for (const material of materials) {
|
|
2621
2722
|
const onBeforeRender = material.onBeforeRender.bind(material);
|
|
@@ -3011,12 +3112,15 @@ class CoordinatesSystem {
|
|
|
3011
3112
|
return out;
|
|
3012
3113
|
}
|
|
3013
3114
|
/**
|
|
3014
|
-
* Converts a point from
|
|
3115
|
+
* Converts a point from NDC to world space by intersecting the ground plane (z=0).
|
|
3116
|
+
* Returns 2D world coordinates (z is always 0). This is appropriate for coordinate
|
|
3117
|
+
* conversions that don't need to account for 3D geometry (e.g. canvasToSvg, camera controls).
|
|
3118
|
+
* For pointer events that should land on 3D geometry, use scene raycasting instead.
|
|
3015
3119
|
* @param ndcCoords Point in NDC (normalized device coordinates)
|
|
3016
3120
|
* @param out Optional output vector
|
|
3017
|
-
* @returns Point in world space
|
|
3121
|
+
* @returns Point on the ground plane in world space, or undefined if the ray is parallel to the plane
|
|
3018
3122
|
*/
|
|
3019
|
-
|
|
3123
|
+
ndcToWorldPlane(ndcCoords, out = new Vector3()) {
|
|
3020
3124
|
return this.pickingSystem.intersectPlane(ndcCoords, this.cameraSystem.camera, out);
|
|
3021
3125
|
}
|
|
3022
3126
|
/**
|
|
@@ -3046,7 +3150,7 @@ class CoordinatesSystem {
|
|
|
3046
3150
|
const vec2 = new Vector2();
|
|
3047
3151
|
const vec3 = new Vector3();
|
|
3048
3152
|
const ndcPoint = this.canvasToNDC(point, vec2);
|
|
3049
|
-
const worldPoint = this.
|
|
3153
|
+
const worldPoint = this.ndcToWorldPlane(ndcPoint, vec3);
|
|
3050
3154
|
if (!worldPoint) return [];
|
|
3051
3155
|
const result = [];
|
|
3052
3156
|
for (const sceneState of this.sceneSystem.sceneStates) {
|
|
@@ -5665,6 +5769,9 @@ const subsetOfTHREE = {
|
|
|
5665
5769
|
};
|
|
5666
5770
|
CameraControls.install({ THREE: subsetOfTHREE });
|
|
5667
5771
|
const logger$4 = createLogger("cameraController");
|
|
5772
|
+
const ELEVATION_PRECISION = 0.1;
|
|
5773
|
+
const ELEVATION_HEADROOM_RATIO = 0.98;
|
|
5774
|
+
const DEPTH_BUFFER_LEVELS = 2 ** 24;
|
|
5668
5775
|
class CameraController extends CameraControls {
|
|
5669
5776
|
/**
|
|
5670
5777
|
* @param camera {@link PerspectiveCamera} instance
|
|
@@ -5686,6 +5793,7 @@ class CameraController extends CameraControls {
|
|
|
5686
5793
|
three: CameraController.ACTION.NONE
|
|
5687
5794
|
};
|
|
5688
5795
|
this.touchCancelListener = () => this.cancel();
|
|
5796
|
+
void this.rotatePolarTo(0, false);
|
|
5689
5797
|
}
|
|
5690
5798
|
/**
|
|
5691
5799
|
* Get bearing angle between current camera orientation and true north (in radians).
|
|
@@ -5696,15 +5804,50 @@ class CameraController extends CameraControls {
|
|
|
5696
5804
|
}
|
|
5697
5805
|
update(delta) {
|
|
5698
5806
|
const needsUpdate = super.update(delta);
|
|
5699
|
-
if (needsUpdate
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5807
|
+
if (needsUpdate) {
|
|
5808
|
+
this.updateNearPlane();
|
|
5809
|
+
if (delta > 0) {
|
|
5810
|
+
const position = this.camera.position.toArray().map((value) => +value.toFixed(2));
|
|
5811
|
+
const target = this._target.toArray().map((value) => +value.toFixed(2));
|
|
5812
|
+
const { theta, phi, radius } = this._spherical;
|
|
5813
|
+
const spherical = [theta * RAD2DEG, phi * RAD2DEG, radius].map((value) => +value.toFixed(2));
|
|
5814
|
+
const clippingPlanes = [this.camera.near, this.camera.far];
|
|
5815
|
+
logger$4.debug("camera update %O", { position, target, spherical, clippingPlanes });
|
|
5816
|
+
}
|
|
5705
5817
|
}
|
|
5706
5818
|
return needsUpdate;
|
|
5707
5819
|
}
|
|
5820
|
+
/**
|
|
5821
|
+
* Updates the near clipping plane to maintain consistent depth buffer precision
|
|
5822
|
+
* at the scene plane regardless of zoom level.
|
|
5823
|
+
*
|
|
5824
|
+
* Two constraints determine the near plane:
|
|
5825
|
+
* - **Headroom**: near = D × (1 − headroom ratio). Dominates at close/default zoom,
|
|
5826
|
+
* keeping headroom at exactly the target ratio. Precision exceeds the target.
|
|
5827
|
+
* - **Precision**: near = D² / (2²⁴ × P). Dominates at far zoom, maintaining target
|
|
5828
|
+
* precision at the cost of reduced headroom.
|
|
5829
|
+
*
|
|
5830
|
+
* The larger of the two is used (precision is never sacrificed for headroom).
|
|
5831
|
+
* The depth precision P is derived from {@link ELEVATION_PRECISION} and the current
|
|
5832
|
+
* {@link CameraControls.maxPolarAngle}, so it adapts when pitch limits change.
|
|
5833
|
+
*
|
|
5834
|
+
* Depth precision at the floor plane (z=0) is the guaranteed minimum. Content above
|
|
5835
|
+
* (closer to camera) has quadratically better precision: P × (d/D)². Content below the
|
|
5836
|
+
* floor degrades quadratically but remains within ~1% for practical depths (< 1% of D).
|
|
5837
|
+
*/
|
|
5838
|
+
updateNearPlane() {
|
|
5839
|
+
const camera = this.camera;
|
|
5840
|
+
const D = this.distance;
|
|
5841
|
+
if (D <= 0) return;
|
|
5842
|
+
const cosPitch = Math.max(Math.cos(this.maxPolarAngle), 0.01);
|
|
5843
|
+
const depthPrecision = ELEVATION_PRECISION * cosPitch;
|
|
5844
|
+
const headroomNear = D * (1 - ELEVATION_HEADROOM_RATIO);
|
|
5845
|
+
const precisionNear = D * D / (DEPTH_BUFFER_LEVELS * depthPrecision);
|
|
5846
|
+
const near = Math.max(headroomNear, precisionNear);
|
|
5847
|
+
if (camera.near === near) return;
|
|
5848
|
+
camera.near = near;
|
|
5849
|
+
camera.updateProjectionMatrix();
|
|
5850
|
+
}
|
|
5708
5851
|
connect(domElement) {
|
|
5709
5852
|
super.connect(domElement);
|
|
5710
5853
|
domElement.addEventListener("touchcancel", this.touchCancelListener);
|
|
@@ -6074,7 +6217,7 @@ class RollHandler extends Handler {
|
|
|
6074
6217
|
}
|
|
6075
6218
|
setPivot(e) {
|
|
6076
6219
|
this.coordinatesSystem.canvasToNDC(e.offsetCenter, this.pivotNDC);
|
|
6077
|
-
if (!this.coordinatesSystem.
|
|
6220
|
+
if (!this.coordinatesSystem.ndcToWorldPlane(this.pivotNDC, this.pivotWorld)) return false;
|
|
6078
6221
|
this.controller.getPosition(this.cameraPosition);
|
|
6079
6222
|
this.controller.getTarget(this.targetWorld);
|
|
6080
6223
|
this.cameraForward.copy(this.targetWorld).sub(this.cameraPosition);
|
|
@@ -6160,7 +6303,7 @@ class InteractionsSystem {
|
|
|
6160
6303
|
if (internalCameraSystem) {
|
|
6161
6304
|
this.eventManager = new EventManager(this.canvas, {
|
|
6162
6305
|
recognizers: [Rotate, [Pan, { event: "pitch", pointers: 2 }, "rotate"]]
|
|
6163
|
-
//
|
|
6306
|
+
// TODO: Double click to zoom
|
|
6164
6307
|
});
|
|
6165
6308
|
this.handlers = {
|
|
6166
6309
|
pan: new PanHandler(this.canvas, this.eventManager, internalCameraSystem, coordinatesSystem),
|
|
@@ -6188,8 +6331,6 @@ class InteractionsSystem {
|
|
|
6188
6331
|
});
|
|
6189
6332
|
this.handlers.pan.enable();
|
|
6190
6333
|
this.handlers.zoom.enable();
|
|
6191
|
-
this.handlers.roll.enable();
|
|
6192
|
-
this.handlers.pitch.enable();
|
|
6193
6334
|
}
|
|
6194
6335
|
}
|
|
6195
6336
|
/**
|
|
@@ -6251,15 +6392,18 @@ class InteractionsSystem {
|
|
|
6251
6392
|
if (isDragging || !hasListeners) return;
|
|
6252
6393
|
const mousePointer = eventToCanvas(event);
|
|
6253
6394
|
coordinatesSystem.canvasToNDC(mousePointer, this.mousePointerNDC);
|
|
6254
|
-
|
|
6255
|
-
const intersectionsByScene = this.viewportAccess.getIntersectedObjectsByScene(this.mousePointerNDC);
|
|
6395
|
+
const raycastResults = this.viewportAccess.raycastByScene(this.mousePointerNDC);
|
|
6256
6396
|
const data = [];
|
|
6257
|
-
for (const { sceneId, intersections } of
|
|
6258
|
-
const
|
|
6259
|
-
const
|
|
6397
|
+
for (const { sceneId, intersections } of raycastResults) {
|
|
6398
|
+
const interactive = intersections.filter((i) => isInteractive(i.object));
|
|
6399
|
+
const closestHit = interactive[0] ?? intersections[0];
|
|
6400
|
+
const worldPoint = closestHit ? this.mousePointerWorld.copy(closestHit.point) : coordinatesSystem.ndcToWorldPlane(this.mousePointerNDC, this.mousePointerWorld);
|
|
6401
|
+
if (!worldPoint) continue;
|
|
6402
|
+
const point = coordinatesSystem.worldToModel(worldPoint, this.mousePointerModel, sceneId);
|
|
6403
|
+
const defs = this.layerSystem.getIntersectedDefs(interactive);
|
|
6260
6404
|
data.push({ point: { x: point.x, y: point.y }, defs, sceneId });
|
|
6261
6405
|
}
|
|
6262
|
-
this.events.emit(eventType, { event, data });
|
|
6406
|
+
if (data.length > 0) this.events.emit(eventType, { event, data });
|
|
6263
6407
|
};
|
|
6264
6408
|
const mousemove = (event) => sharedMouseHandler("mousemove", event);
|
|
6265
6409
|
const mouseout = (event) => sharedMouseHandler("mouseout", event);
|
|
@@ -6295,7 +6439,7 @@ class UpdatesSystem {
|
|
|
6295
6439
|
constructor(ctx, layerSystem) {
|
|
6296
6440
|
__publicField(this, "pendingDefs", /* @__PURE__ */ new Set());
|
|
6297
6441
|
__publicField(this, "culledDefs", /* @__PURE__ */ new Set());
|
|
6298
|
-
__publicField(this, "
|
|
6442
|
+
__publicField(this, "defSphere", new Sphere());
|
|
6299
6443
|
__publicField(this, "useUpdateBuffering", true);
|
|
6300
6444
|
this.ctx = ctx;
|
|
6301
6445
|
this.layerSystem = layerSystem;
|
|
@@ -6336,10 +6480,9 @@ class UpdatesSystem {
|
|
|
6336
6480
|
let culled = false;
|
|
6337
6481
|
this.pendingDefs.delete(def);
|
|
6338
6482
|
if (isTextDef(def) || isImageDef(def)) {
|
|
6339
|
-
const
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
culled = !frustum2.intersectsBox(this.defBounds);
|
|
6483
|
+
const origin2 = isImageDef(def) ? def.origin : void 0;
|
|
6484
|
+
computeBoundingSphere(def.bounds, origin2, this.defSphere);
|
|
6485
|
+
culled = !frustum2.intersectsSphere(this.defSphere);
|
|
6343
6486
|
}
|
|
6344
6487
|
if (culled) this.culledDefs.add(def);
|
|
6345
6488
|
else {
|
|
@@ -6384,8 +6527,8 @@ class InternalCameraSystem {
|
|
|
6384
6527
|
const h = ctx.getDrawingBufferSizePx()[1];
|
|
6385
6528
|
this.prevViewportHeightPx = h;
|
|
6386
6529
|
this.camera = new PerspectiveCamera(this.defaultFov);
|
|
6530
|
+
this.camera.up.set(0, 0, -1);
|
|
6387
6531
|
this.controller = new CameraController(this.camera);
|
|
6388
|
-
void this.controller.rotatePolarTo(0, false);
|
|
6389
6532
|
}
|
|
6390
6533
|
/** Current camera zoom factor. */
|
|
6391
6534
|
get zoomFactor() {
|
|
@@ -6418,12 +6561,8 @@ class InternalCameraSystem {
|
|
|
6418
6561
|
initCamera(viewbox) {
|
|
6419
6562
|
this.zoomBounds = [0.1, viewbox.size.x > 1e4 ? 100 : 35];
|
|
6420
6563
|
this.updateCamera();
|
|
6421
|
-
this.
|
|
6422
|
-
this.camera.position.set(0, 0, -this.zoomIdentityDistance);
|
|
6423
|
-
this.camera.lookAt(0, 0, 0);
|
|
6564
|
+
this.controller.update(0);
|
|
6424
6565
|
this.camera.updateMatrixWorld();
|
|
6425
|
-
this.camera.up.set(0, 0, -1);
|
|
6426
|
-
this.controller.updateCameraUp();
|
|
6427
6566
|
}
|
|
6428
6567
|
/** Updates the camera when the renderer size changes. */
|
|
6429
6568
|
updateCamera() {
|
|
@@ -6435,8 +6574,7 @@ class InternalCameraSystem {
|
|
|
6435
6574
|
const maxDistance = Math.abs(newZoomIdentity / this.zoomBounds[0]);
|
|
6436
6575
|
const minDistance = Math.abs(newZoomIdentity / this.zoomBounds[1]);
|
|
6437
6576
|
this.camera.aspect = w / (h || 1);
|
|
6438
|
-
this.camera.
|
|
6439
|
-
this.camera.far = Math.max(maxDistance, this.camera.near) * 2;
|
|
6577
|
+
this.camera.far = Math.max(maxDistance, 0.1) * 2;
|
|
6440
6578
|
this.camera.updateProjectionMatrix();
|
|
6441
6579
|
this.controller.minDistance = minDistance;
|
|
6442
6580
|
this.controller.maxDistance = maxDistance;
|
|
@@ -6471,21 +6609,21 @@ class InternalCameraSystem {
|
|
|
6471
6609
|
}
|
|
6472
6610
|
}
|
|
6473
6611
|
class PickingSystem {
|
|
6474
|
-
/** */
|
|
6475
6612
|
constructor() {
|
|
6476
6613
|
__publicField(this, "raycaster", new Raycaster());
|
|
6477
6614
|
__publicField(this, "ndcPoint", new Vector2());
|
|
6478
6615
|
__publicField(this, "viewboxPlane", new Plane(new Vector3(0, 0, 1), 0));
|
|
6479
|
-
this.raycaster.layers.set(INTERACTIVE_LAYER);
|
|
6480
6616
|
}
|
|
6481
6617
|
/**
|
|
6482
|
-
*
|
|
6618
|
+
* Raycasts the scene and returns all visible intersections sorted by distance.
|
|
6619
|
+
* Does not filter by interaction layer — consumers are responsible for partitioning
|
|
6620
|
+
* interactive vs non-interactive results using {@link isInteractive}.
|
|
6483
6621
|
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
6484
6622
|
* @param scene {@link Scene} instance
|
|
6485
6623
|
* @param camera {@link Camera} instance
|
|
6486
|
-
* @returns Array of {@link Intersection} instances
|
|
6624
|
+
* @returns Array of {@link Intersection} instances, nearest first
|
|
6487
6625
|
*/
|
|
6488
|
-
|
|
6626
|
+
raycast(ndcCoords, scene, camera) {
|
|
6489
6627
|
this.setRaycasterFromCamera(ndcCoords, camera);
|
|
6490
6628
|
const intersections = this.raycaster.intersectObject(scene, true);
|
|
6491
6629
|
return intersections.filter((i) => isVisible(i.object));
|
|
@@ -6608,7 +6746,7 @@ class SceneSystem {
|
|
|
6608
6746
|
const scaleFactor = Math.min(visibleRectSize.width, visibleRectSize.height);
|
|
6609
6747
|
const { x: centerX, y: centerY } = viewbox.center;
|
|
6610
6748
|
this.translationMatrix.makeTranslation(-centerX, -centerY, 0);
|
|
6611
|
-
this.scaleMatrix.makeScale(scaleFactor, scaleFactor,
|
|
6749
|
+
this.scaleMatrix.makeScale(scaleFactor, scaleFactor, -scaleFactor);
|
|
6612
6750
|
targetMatrix.copy(this.translationMatrix).premultiply(this.scaleMatrix);
|
|
6613
6751
|
const viewportRectCenter = this.tempVector2.copy(viewportRectPx.center);
|
|
6614
6752
|
const offset = viewportRectCenter.sub({ x: bufferW / 2, y: bufferH / 2 });
|
|
@@ -6680,15 +6818,16 @@ class ViewportSystem {
|
|
|
6680
6818
|
this.visibleRectCss = rect;
|
|
6681
6819
|
}
|
|
6682
6820
|
/**
|
|
6683
|
-
*
|
|
6821
|
+
* Raycasts all loaded scenes and returns all visible intersections per scene.
|
|
6822
|
+
* Results are not filtered by interaction layer — consumers partition as needed.
|
|
6684
6823
|
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
6685
|
-
* @returns Array of {@link
|
|
6824
|
+
* @returns Array of {@link SceneRaycastResult} instances
|
|
6686
6825
|
*/
|
|
6687
|
-
|
|
6826
|
+
raycastByScene(ndcCoords) {
|
|
6688
6827
|
const result = [];
|
|
6689
6828
|
for (const sceneState of this.sceneSystem.sceneStates) {
|
|
6690
6829
|
if (!sceneState.loaded) continue;
|
|
6691
|
-
const intersections = this.pickingSystem.
|
|
6830
|
+
const intersections = this.pickingSystem.raycast(ndcCoords, sceneState.scene, this.camera);
|
|
6692
6831
|
result.push({ intersections, sceneId: sceneState.id });
|
|
6693
6832
|
}
|
|
6694
6833
|
return result;
|
|
@@ -6850,6 +6989,7 @@ class Renderer {
|
|
|
6850
6989
|
__publicField(this, "memoryInfoExtension", null);
|
|
6851
6990
|
__publicField(this, "memoryInfo");
|
|
6852
6991
|
__publicField(this, "eventSystem");
|
|
6992
|
+
__publicField(this, "lightsSystem");
|
|
6853
6993
|
__publicField(this, "layerSystem");
|
|
6854
6994
|
__publicField(this, "viewportSystem");
|
|
6855
6995
|
__publicField(this, "updatesSystem");
|
|
@@ -6868,7 +7008,8 @@ class Renderer {
|
|
|
6868
7008
|
const rendererOptions = {
|
|
6869
7009
|
antialias: true,
|
|
6870
7010
|
context: gl,
|
|
6871
|
-
canvas: this.canvas
|
|
7011
|
+
canvas: this.canvas,
|
|
7012
|
+
precision: "highp"
|
|
6872
7013
|
};
|
|
6873
7014
|
this.clock = new Clock();
|
|
6874
7015
|
this.renderer = new WebGLRenderer(rendererOptions);
|
|
@@ -6877,6 +7018,7 @@ class Renderer {
|
|
|
6877
7018
|
this.renderer.autoClear = !this.isExternalMode;
|
|
6878
7019
|
this.ctx = this.createRendererContext();
|
|
6879
7020
|
this.eventSystem = new EventSystem();
|
|
7021
|
+
this.lightsSystem = new LightsSystem();
|
|
6880
7022
|
this.layerSystem = new LayerSystem(this.ctx);
|
|
6881
7023
|
this.viewportSystem = new ViewportSystem(this.ctx, this.eventSystem);
|
|
6882
7024
|
this.updatesSystem = new UpdatesSystem(this.ctx, this.layerSystem);
|
|
@@ -7046,7 +7188,6 @@ class Renderer {
|
|
|
7046
7188
|
} else if (sceneState.scene.children.length === 0) {
|
|
7047
7189
|
const root = this.layerSystem.buildScene(sceneState.sceneDef);
|
|
7048
7190
|
scene.add(root);
|
|
7049
|
-
if (this.isExternalMode) patchMatricesInExternalMode(scene);
|
|
7050
7191
|
}
|
|
7051
7192
|
}
|
|
7052
7193
|
const justLoaded = loadedChanged && sceneState.loaded;
|
|
@@ -7054,6 +7195,8 @@ class Renderer {
|
|
|
7054
7195
|
this.viewportSystem.updatePtScale(id);
|
|
7055
7196
|
const hasDefsUpdated = this.updatesSystem.processPendingUpdates(frustum2, 3);
|
|
7056
7197
|
const forceRedraw = hasControlsUpdated || hasDefsUpdated || justLoaded || this.isExternalMode || this.ui;
|
|
7198
|
+
if (this.isExternalMode) patchMatricesInExternalMode(scene);
|
|
7199
|
+
this.lightsSystem.updateLights(scene, camera);
|
|
7057
7200
|
if (this.needsRedraw || forceRedraw) this.renderer.render(scene, camera);
|
|
7058
7201
|
if (hasDefsUpdated || this.needsRedraw) this.viewportSystem.invalidateSceneBounds(sceneState);
|
|
7059
7202
|
}
|
|
@@ -7090,8 +7233,9 @@ class Renderer {
|
|
|
7090
7233
|
}
|
|
7091
7234
|
initScene(sceneDef, sceneId) {
|
|
7092
7235
|
const root = this.layerSystem.buildScene(sceneDef);
|
|
7093
|
-
const scene = this.viewportSystem.initScene(sceneDef, sceneId)
|
|
7094
|
-
|
|
7236
|
+
const scene = this.viewportSystem.initScene(sceneDef, sceneId);
|
|
7237
|
+
scene.add(root);
|
|
7238
|
+
this.lightsSystem.initLights(scene);
|
|
7095
7239
|
}
|
|
7096
7240
|
// https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
|
|
7097
7241
|
resizeCanvasToDisplaySize() {
|