@expofp/renderer 1.3.2 → 1.4.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 +38 -407
- package/dist/index.js +2618 -68
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
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
|
+
var _a;
|
|
4
5
|
import { DataTexture, FloatType, UnsignedIntType, IntType, RGBAFormat, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedFormat, RedIntegerFormat, BatchedMesh as BatchedMesh$1, BufferAttribute, StreamDrawUsage, Color, Matrix4, Vector3, Vector4, DoubleSide, MeshBasicMaterial, Texture, Quaternion, Group, PlaneGeometry, SRGBColorSpace, Vector2, BufferGeometry, LinearSRGBColorSpace, Mesh, Plane, Raycaster, Sphere, Box3, Spherical, PerspectiveCamera, Scene, Camera, MathUtils, Clock, WebGLRenderer } from "three";
|
|
5
6
|
import { traverseAncestorsGenerator } from "three/examples/jsm/utils/SceneUtils.js";
|
|
6
7
|
import { BatchedText as BatchedText$1, Text as Text$1 } from "troika-three-text";
|
|
7
8
|
import { LineMaterial, LineSegmentsGeometry } from "three/examples/jsm/Addons.js";
|
|
8
9
|
import { MaxRectsPacker, Rectangle } from "maxrects-packer";
|
|
9
10
|
import { converter, parse } from "culori";
|
|
10
|
-
import { RAD2DEG, DEG2RAD
|
|
11
|
-
import CameraControls from "camera-controls";
|
|
11
|
+
import { RAD2DEG, DEG2RAD as DEG2RAD$1 } from "three/src/math/MathUtils.js";
|
|
12
12
|
import { EventManager, Rotate, Pan } from "mjolnir.js";
|
|
13
13
|
function isObject(item) {
|
|
14
14
|
return !!item && typeof item === "object" && !Array.isArray(item);
|
|
@@ -330,9 +330,9 @@ class BatchedMesh extends BatchedMesh$1 {
|
|
|
330
330
|
return instanceId;
|
|
331
331
|
}
|
|
332
332
|
onBeforeRender(renderer, scene, camera, geometry, material, group) {
|
|
333
|
-
var
|
|
333
|
+
var _a2;
|
|
334
334
|
if (!this.isMaterialPatched) this.patchMaterial(material);
|
|
335
|
-
(
|
|
335
|
+
(_a2 = this.uniformsTexture) == null ? void 0 : _a2.update();
|
|
336
336
|
if (this.useMultiDraw) return super.onBeforeRender(renderer, scene, camera, geometry, material, group);
|
|
337
337
|
if (!this.indexBuffer) {
|
|
338
338
|
const vertexCount = geometry.getAttribute("position").count;
|
|
@@ -343,11 +343,11 @@ class BatchedMesh extends BatchedMesh$1 {
|
|
|
343
343
|
this._multiDrawCount = 0;
|
|
344
344
|
}
|
|
345
345
|
onAfterRender(renderer, scene, camera, geometry) {
|
|
346
|
-
var
|
|
346
|
+
var _a2;
|
|
347
347
|
if (this.useMultiDraw) return;
|
|
348
348
|
const batchCount = this.batchCount;
|
|
349
349
|
const gl = renderer.getContext();
|
|
350
|
-
if (geometry.index == null) return console.warn("No index buffer", (
|
|
350
|
+
if (geometry.index == null) return console.warn("No index buffer", (_a2 = this.parent) == null ? void 0 : _a2.name);
|
|
351
351
|
const type = this.getIndexType(gl, geometry.index);
|
|
352
352
|
gl.drawElements(gl.TRIANGLES, batchCount, type, 0);
|
|
353
353
|
renderer.info.update(batchCount, gl.TRIANGLES, 1);
|
|
@@ -394,11 +394,11 @@ class BatchedMesh extends BatchedMesh$1 {
|
|
|
394
394
|
return this;
|
|
395
395
|
}
|
|
396
396
|
dispose() {
|
|
397
|
-
var
|
|
397
|
+
var _a2;
|
|
398
398
|
this.geometry.setIndex(this.indexBuffer ?? null);
|
|
399
399
|
super.dispose();
|
|
400
400
|
this.uniformSchema = {};
|
|
401
|
-
(
|
|
401
|
+
(_a2 = this.uniformsTexture) == null ? void 0 : _a2.dispose();
|
|
402
402
|
this.uniformsTexture = void 0;
|
|
403
403
|
this.indexBuffer = void 0;
|
|
404
404
|
this.geometryById.forEach((geometry) => geometry.dispose());
|
|
@@ -407,9 +407,9 @@ class BatchedMesh extends BatchedMesh$1 {
|
|
|
407
407
|
return this;
|
|
408
408
|
}
|
|
409
409
|
resizeToFitGeometry(geometry) {
|
|
410
|
-
var
|
|
410
|
+
var _a2;
|
|
411
411
|
const vertexCount = geometry.attributes["position"].count;
|
|
412
|
-
const indexCount = ((
|
|
412
|
+
const indexCount = ((_a2 = geometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
413
413
|
this._maxVertexCount += vertexCount;
|
|
414
414
|
this._maxIndexCount += indexCount;
|
|
415
415
|
this.setGeometrySize(this._maxVertexCount, this._maxIndexCount);
|
|
@@ -451,12 +451,12 @@ class BatchedMesh extends BatchedMesh$1 {
|
|
|
451
451
|
const onBeforeCompile = material.onBeforeCompile.bind(material);
|
|
452
452
|
const customProgramCacheKey = material.customProgramCacheKey.bind(material);
|
|
453
453
|
material.onBeforeCompile = (shader, renderer) => {
|
|
454
|
-
var
|
|
454
|
+
var _a2;
|
|
455
455
|
onBeforeCompile(shader, renderer);
|
|
456
456
|
shader.defines ?? (shader.defines = {});
|
|
457
457
|
shader.defines["USE_BATCH_UNIFORMS"] = "";
|
|
458
458
|
shader.uniforms["uniformsTexture"] = { value: this.uniformsTexture };
|
|
459
|
-
const { vertex, fragment } = ((
|
|
459
|
+
const { vertex, fragment } = ((_a2 = this.uniformsTexture) == null ? void 0 : _a2.getUniformsGLSL("uniformsTexture", "instanceId", "int")) ?? {
|
|
460
460
|
vertex: "",
|
|
461
461
|
fragment: ""
|
|
462
462
|
};
|
|
@@ -607,7 +607,7 @@ class BatchedText extends BatchedText$1 {
|
|
|
607
607
|
this.dispatchEvent({ type: "dispose" });
|
|
608
608
|
}
|
|
609
609
|
_prepareForRender(material) {
|
|
610
|
-
var
|
|
610
|
+
var _a2;
|
|
611
611
|
const isOutline = material.isTextOutlineMaterial;
|
|
612
612
|
material.uniforms.uTroikaIsOutline.value = isOutline;
|
|
613
613
|
let texture = this._dataTextures[isOutline ? "outline" : "main"];
|
|
@@ -626,7 +626,7 @@ class BatchedText extends BatchedText$1 {
|
|
|
626
626
|
const texData = texture.image.data;
|
|
627
627
|
this.textureNeedsUpdate = false;
|
|
628
628
|
for (const text of this.textArray) {
|
|
629
|
-
const index = ((
|
|
629
|
+
const index = ((_a2 = this._members.get(text)) == null ? void 0 : _a2.index) ?? -1;
|
|
630
630
|
const textRenderInfo = text.textRenderInfo;
|
|
631
631
|
if (index < 0 || !textRenderInfo) continue;
|
|
632
632
|
const startIndex = index * floatsPerMember;
|
|
@@ -1313,7 +1313,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1313
1313
|
if (this.memoryLimitMb) this.resizeTextures();
|
|
1314
1314
|
}
|
|
1315
1315
|
buildLayer(layer) {
|
|
1316
|
-
var
|
|
1316
|
+
var _a2;
|
|
1317
1317
|
const group = new Group();
|
|
1318
1318
|
const images = layer.children;
|
|
1319
1319
|
const bins = this.packImages(images);
|
|
@@ -1331,7 +1331,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1331
1331
|
const instanceMaterial = this.materialSystem.createTextureMaterial(texture, true);
|
|
1332
1332
|
const instanceGeometry = new PlaneGeometry();
|
|
1333
1333
|
const vertexCount = instanceGeometry.attributes["position"].count;
|
|
1334
|
-
const indexCount = ((
|
|
1334
|
+
const indexCount = ((_a2 = instanceGeometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
1335
1335
|
const batchedMesh = new BatchedMesh(instanceCount, vertexCount, indexCount, instanceMaterial);
|
|
1336
1336
|
batchedMesh.sortObjects = false;
|
|
1337
1337
|
batchedMesh.addPerInstanceUniforms({ vertex: { uvOffset: "vec4" } });
|
|
@@ -1352,7 +1352,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1352
1352
|
* Resize textures to fit the memory limit.
|
|
1353
1353
|
*/
|
|
1354
1354
|
resizeTextures() {
|
|
1355
|
-
var
|
|
1355
|
+
var _a2;
|
|
1356
1356
|
if (!this.memoryLimitMb) {
|
|
1357
1357
|
console.warn("Memory limit is not set, unable to resize textures.");
|
|
1358
1358
|
return;
|
|
@@ -1390,7 +1390,7 @@ class ImageSystem extends RenderableSystem {
|
|
|
1390
1390
|
const resizedTexture = resizeTexture(texture, resizeFactor);
|
|
1391
1391
|
const textureDim = `${texture.image.width}x${texture.image.height}`;
|
|
1392
1392
|
const resizedDim = `${resizedTexture.image.width}x${resizedTexture.image.height}`;
|
|
1393
|
-
console.log(`Resized atlas for ${mesh.name || ((
|
|
1393
|
+
console.log(`Resized atlas for ${mesh.name || ((_a2 = mesh.parent) == null ? void 0 : _a2.name)}, from ${textureDim} to ${resizedDim}`);
|
|
1394
1394
|
newTotal += getTextureSizeBytes(resizedTexture);
|
|
1395
1395
|
material.map = resizedTexture;
|
|
1396
1396
|
material.needsUpdate = true;
|
|
@@ -1490,7 +1490,7 @@ class LineSystem extends RenderableSystem {
|
|
|
1490
1490
|
this.materialSystem = materialSystem;
|
|
1491
1491
|
}
|
|
1492
1492
|
buildLayer(layer) {
|
|
1493
|
-
var
|
|
1493
|
+
var _a2;
|
|
1494
1494
|
const group = new Group();
|
|
1495
1495
|
let vertexCount = 0;
|
|
1496
1496
|
let indexCount = 0;
|
|
@@ -1501,7 +1501,7 @@ class LineSystem extends RenderableSystem {
|
|
|
1501
1501
|
lineGeometry.setPositions(line.points.flatMap((pt) => [pt.x, pt.y, 0]));
|
|
1502
1502
|
geometries.set(line, this.deinterleaveGeometry(lineGeometry));
|
|
1503
1503
|
vertexCount += lineGeometry.attributes["position"].count;
|
|
1504
|
-
indexCount += ((
|
|
1504
|
+
indexCount += ((_a2 = lineGeometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
1505
1505
|
}
|
|
1506
1506
|
const material = this.materialSystem.createLineMaterial({ color: "white" });
|
|
1507
1507
|
const batchedMesh = new BatchedMesh(lines.length, vertexCount, indexCount, material);
|
|
@@ -1709,8 +1709,8 @@ class MeshSystem extends RenderableSystem {
|
|
|
1709
1709
|
const [opaqueShapes, transparentShapes] = partition(
|
|
1710
1710
|
shapes,
|
|
1711
1711
|
(shapeDef) => {
|
|
1712
|
-
var
|
|
1713
|
-
return (((
|
|
1712
|
+
var _a2;
|
|
1713
|
+
return (((_a2 = mapShapeToNormColor.get(shapeDef)) == null ? void 0 : _a2.alpha) ?? 1) === 1;
|
|
1714
1714
|
}
|
|
1715
1715
|
);
|
|
1716
1716
|
const transparentShapesGrouped = groupBy(transparentShapes, (shapeDef) => mapShapeToNormColor.get(shapeDef).alpha);
|
|
@@ -1735,7 +1735,7 @@ class MeshSystem extends RenderableSystem {
|
|
|
1735
1735
|
}
|
|
1736
1736
|
}
|
|
1737
1737
|
buildBatchedMesh(shapes, opacity = 1) {
|
|
1738
|
-
var
|
|
1738
|
+
var _a2, _b;
|
|
1739
1739
|
let vertexCount = 0;
|
|
1740
1740
|
let indexCount = 0;
|
|
1741
1741
|
let rectGeometry = void 0;
|
|
@@ -1748,7 +1748,7 @@ class MeshSystem extends RenderableSystem {
|
|
|
1748
1748
|
rectGeometry.deleteAttribute("uv");
|
|
1749
1749
|
}
|
|
1750
1750
|
vertexCount += rectGeometry.getAttribute("position").count;
|
|
1751
|
-
indexCount += ((
|
|
1751
|
+
indexCount += ((_a2 = rectGeometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
1752
1752
|
} else if (shapeDef.shape instanceof Polygon) {
|
|
1753
1753
|
const geometry = this.buildPolygonGeometry(shapeDef.shape);
|
|
1754
1754
|
shapeDefToGeometry.set(shapeDef, geometry);
|
|
@@ -2181,6 +2181,2521 @@ class LayerSystem {
|
|
|
2181
2181
|
return fullName;
|
|
2182
2182
|
}
|
|
2183
2183
|
}
|
|
2184
|
+
/*!
|
|
2185
|
+
* camera-controls
|
|
2186
|
+
* https://github.com/yomotsu/camera-controls
|
|
2187
|
+
* (c) 2017 @yomotsu
|
|
2188
|
+
* Released under the MIT License.
|
|
2189
|
+
*/
|
|
2190
|
+
const MOUSE_BUTTON = {
|
|
2191
|
+
LEFT: 1,
|
|
2192
|
+
RIGHT: 2,
|
|
2193
|
+
MIDDLE: 4
|
|
2194
|
+
};
|
|
2195
|
+
const ACTION = Object.freeze({
|
|
2196
|
+
NONE: 0,
|
|
2197
|
+
ROTATE: 1,
|
|
2198
|
+
ROTATE_POLAR: 8388608,
|
|
2199
|
+
ROTATE_AZIMUTH: 16777216,
|
|
2200
|
+
TRUCK: 2,
|
|
2201
|
+
SCREEN_PAN: 4,
|
|
2202
|
+
OFFSET: 8,
|
|
2203
|
+
DOLLY: 16,
|
|
2204
|
+
ZOOM: 32,
|
|
2205
|
+
TOUCH_ROTATE: 64,
|
|
2206
|
+
TOUCH_TRUCK: 128,
|
|
2207
|
+
TOUCH_SCREEN_PAN: 256,
|
|
2208
|
+
TOUCH_OFFSET: 512,
|
|
2209
|
+
TOUCH_DOLLY: 1024,
|
|
2210
|
+
TOUCH_ZOOM: 2048,
|
|
2211
|
+
TOUCH_DOLLY_TRUCK: 4096,
|
|
2212
|
+
TOUCH_DOLLY_SCREEN_PAN: 8192,
|
|
2213
|
+
TOUCH_DOLLY_OFFSET: 16384,
|
|
2214
|
+
TOUCH_DOLLY_ROTATE: 32768,
|
|
2215
|
+
TOUCH_ZOOM_TRUCK: 65536,
|
|
2216
|
+
TOUCH_ZOOM_OFFSET: 131072,
|
|
2217
|
+
TOUCH_ZOOM_SCREEN_PAN: 262144,
|
|
2218
|
+
TOUCH_ZOOM_ROTATE: 524288
|
|
2219
|
+
});
|
|
2220
|
+
const DOLLY_DIRECTION = {
|
|
2221
|
+
NONE: 0,
|
|
2222
|
+
IN: 1,
|
|
2223
|
+
OUT: -1
|
|
2224
|
+
};
|
|
2225
|
+
function isPerspectiveCamera(camera) {
|
|
2226
|
+
return camera.isPerspectiveCamera;
|
|
2227
|
+
}
|
|
2228
|
+
function isOrthographicCamera(camera) {
|
|
2229
|
+
return camera.isOrthographicCamera;
|
|
2230
|
+
}
|
|
2231
|
+
const PI_2 = Math.PI * 2;
|
|
2232
|
+
const PI_HALF = Math.PI / 2;
|
|
2233
|
+
const EPSILON = 1e-5;
|
|
2234
|
+
const DEG2RAD = Math.PI / 180;
|
|
2235
|
+
function clamp(value, min, max) {
|
|
2236
|
+
return Math.max(min, Math.min(max, value));
|
|
2237
|
+
}
|
|
2238
|
+
function approxZero(number, error = EPSILON) {
|
|
2239
|
+
return Math.abs(number) < error;
|
|
2240
|
+
}
|
|
2241
|
+
function approxEquals(a, b, error = EPSILON) {
|
|
2242
|
+
return approxZero(a - b, error);
|
|
2243
|
+
}
|
|
2244
|
+
function roundToStep(value, step) {
|
|
2245
|
+
return Math.round(value / step) * step;
|
|
2246
|
+
}
|
|
2247
|
+
function infinityToMaxNumber(value) {
|
|
2248
|
+
if (isFinite(value))
|
|
2249
|
+
return value;
|
|
2250
|
+
if (value < 0)
|
|
2251
|
+
return -Number.MAX_VALUE;
|
|
2252
|
+
return Number.MAX_VALUE;
|
|
2253
|
+
}
|
|
2254
|
+
function maxNumberToInfinity(value) {
|
|
2255
|
+
if (Math.abs(value) < Number.MAX_VALUE)
|
|
2256
|
+
return value;
|
|
2257
|
+
return value * Infinity;
|
|
2258
|
+
}
|
|
2259
|
+
function smoothDamp(current, target, currentVelocityRef, smoothTime, maxSpeed = Infinity, deltaTime) {
|
|
2260
|
+
smoothTime = Math.max(1e-4, smoothTime);
|
|
2261
|
+
const omega = 2 / smoothTime;
|
|
2262
|
+
const x = omega * deltaTime;
|
|
2263
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
2264
|
+
let change = current - target;
|
|
2265
|
+
const originalTo = target;
|
|
2266
|
+
const maxChange = maxSpeed * smoothTime;
|
|
2267
|
+
change = clamp(change, -maxChange, maxChange);
|
|
2268
|
+
target = current - change;
|
|
2269
|
+
const temp = (currentVelocityRef.value + omega * change) * deltaTime;
|
|
2270
|
+
currentVelocityRef.value = (currentVelocityRef.value - omega * temp) * exp;
|
|
2271
|
+
let output = target + (change + temp) * exp;
|
|
2272
|
+
if (originalTo - current > 0 === output > originalTo) {
|
|
2273
|
+
output = originalTo;
|
|
2274
|
+
currentVelocityRef.value = (output - originalTo) / deltaTime;
|
|
2275
|
+
}
|
|
2276
|
+
return output;
|
|
2277
|
+
}
|
|
2278
|
+
function smoothDampVec3(current, target, currentVelocityRef, smoothTime, maxSpeed = Infinity, deltaTime, out) {
|
|
2279
|
+
smoothTime = Math.max(1e-4, smoothTime);
|
|
2280
|
+
const omega = 2 / smoothTime;
|
|
2281
|
+
const x = omega * deltaTime;
|
|
2282
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
2283
|
+
let targetX = target.x;
|
|
2284
|
+
let targetY = target.y;
|
|
2285
|
+
let targetZ = target.z;
|
|
2286
|
+
let changeX = current.x - targetX;
|
|
2287
|
+
let changeY = current.y - targetY;
|
|
2288
|
+
let changeZ = current.z - targetZ;
|
|
2289
|
+
const originalToX = targetX;
|
|
2290
|
+
const originalToY = targetY;
|
|
2291
|
+
const originalToZ = targetZ;
|
|
2292
|
+
const maxChange = maxSpeed * smoothTime;
|
|
2293
|
+
const maxChangeSq = maxChange * maxChange;
|
|
2294
|
+
const magnitudeSq = changeX * changeX + changeY * changeY + changeZ * changeZ;
|
|
2295
|
+
if (magnitudeSq > maxChangeSq) {
|
|
2296
|
+
const magnitude = Math.sqrt(magnitudeSq);
|
|
2297
|
+
changeX = changeX / magnitude * maxChange;
|
|
2298
|
+
changeY = changeY / magnitude * maxChange;
|
|
2299
|
+
changeZ = changeZ / magnitude * maxChange;
|
|
2300
|
+
}
|
|
2301
|
+
targetX = current.x - changeX;
|
|
2302
|
+
targetY = current.y - changeY;
|
|
2303
|
+
targetZ = current.z - changeZ;
|
|
2304
|
+
const tempX = (currentVelocityRef.x + omega * changeX) * deltaTime;
|
|
2305
|
+
const tempY = (currentVelocityRef.y + omega * changeY) * deltaTime;
|
|
2306
|
+
const tempZ = (currentVelocityRef.z + omega * changeZ) * deltaTime;
|
|
2307
|
+
currentVelocityRef.x = (currentVelocityRef.x - omega * tempX) * exp;
|
|
2308
|
+
currentVelocityRef.y = (currentVelocityRef.y - omega * tempY) * exp;
|
|
2309
|
+
currentVelocityRef.z = (currentVelocityRef.z - omega * tempZ) * exp;
|
|
2310
|
+
out.x = targetX + (changeX + tempX) * exp;
|
|
2311
|
+
out.y = targetY + (changeY + tempY) * exp;
|
|
2312
|
+
out.z = targetZ + (changeZ + tempZ) * exp;
|
|
2313
|
+
const origMinusCurrentX = originalToX - current.x;
|
|
2314
|
+
const origMinusCurrentY = originalToY - current.y;
|
|
2315
|
+
const origMinusCurrentZ = originalToZ - current.z;
|
|
2316
|
+
const outMinusOrigX = out.x - originalToX;
|
|
2317
|
+
const outMinusOrigY = out.y - originalToY;
|
|
2318
|
+
const outMinusOrigZ = out.z - originalToZ;
|
|
2319
|
+
if (origMinusCurrentX * outMinusOrigX + origMinusCurrentY * outMinusOrigY + origMinusCurrentZ * outMinusOrigZ > 0) {
|
|
2320
|
+
out.x = originalToX;
|
|
2321
|
+
out.y = originalToY;
|
|
2322
|
+
out.z = originalToZ;
|
|
2323
|
+
currentVelocityRef.x = (out.x - originalToX) / deltaTime;
|
|
2324
|
+
currentVelocityRef.y = (out.y - originalToY) / deltaTime;
|
|
2325
|
+
currentVelocityRef.z = (out.z - originalToZ) / deltaTime;
|
|
2326
|
+
}
|
|
2327
|
+
return out;
|
|
2328
|
+
}
|
|
2329
|
+
function extractClientCoordFromEvent(pointers, out) {
|
|
2330
|
+
out.set(0, 0);
|
|
2331
|
+
pointers.forEach((pointer) => {
|
|
2332
|
+
out.x += pointer.clientX;
|
|
2333
|
+
out.y += pointer.clientY;
|
|
2334
|
+
});
|
|
2335
|
+
out.x /= pointers.length;
|
|
2336
|
+
out.y /= pointers.length;
|
|
2337
|
+
}
|
|
2338
|
+
function notSupportedInOrthographicCamera(camera, message) {
|
|
2339
|
+
if (isOrthographicCamera(camera)) {
|
|
2340
|
+
console.warn(`${message} is not supported in OrthographicCamera`);
|
|
2341
|
+
return true;
|
|
2342
|
+
}
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
class EventDispatcher {
|
|
2346
|
+
constructor() {
|
|
2347
|
+
__publicField(this, "_listeners", {});
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Adds the specified event listener.
|
|
2351
|
+
* @param type event name
|
|
2352
|
+
* @param listener handler function
|
|
2353
|
+
* @category Methods
|
|
2354
|
+
*/
|
|
2355
|
+
addEventListener(type, listener) {
|
|
2356
|
+
const listeners = this._listeners;
|
|
2357
|
+
if (listeners[type] === void 0)
|
|
2358
|
+
listeners[type] = [];
|
|
2359
|
+
if (listeners[type].indexOf(listener) === -1)
|
|
2360
|
+
listeners[type].push(listener);
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Presence of the specified event listener.
|
|
2364
|
+
* @param type event name
|
|
2365
|
+
* @param listener handler function
|
|
2366
|
+
* @category Methods
|
|
2367
|
+
*/
|
|
2368
|
+
hasEventListener(type, listener) {
|
|
2369
|
+
const listeners = this._listeners;
|
|
2370
|
+
return listeners[type] !== void 0 && listeners[type].indexOf(listener) !== -1;
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Removes the specified event listener
|
|
2374
|
+
* @param type event name
|
|
2375
|
+
* @param listener handler function
|
|
2376
|
+
* @category Methods
|
|
2377
|
+
*/
|
|
2378
|
+
removeEventListener(type, listener) {
|
|
2379
|
+
const listeners = this._listeners;
|
|
2380
|
+
const listenerArray = listeners[type];
|
|
2381
|
+
if (listenerArray !== void 0) {
|
|
2382
|
+
const index = listenerArray.indexOf(listener);
|
|
2383
|
+
if (index !== -1)
|
|
2384
|
+
listenerArray.splice(index, 1);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Removes all event listeners
|
|
2389
|
+
* @param type event name
|
|
2390
|
+
* @category Methods
|
|
2391
|
+
*/
|
|
2392
|
+
removeAllEventListeners(type) {
|
|
2393
|
+
if (!type) {
|
|
2394
|
+
this._listeners = {};
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
if (Array.isArray(this._listeners[type]))
|
|
2398
|
+
this._listeners[type].length = 0;
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Fire an event type.
|
|
2402
|
+
* @param event DispatcherEvent
|
|
2403
|
+
* @category Methods
|
|
2404
|
+
*/
|
|
2405
|
+
dispatchEvent(event) {
|
|
2406
|
+
const listeners = this._listeners;
|
|
2407
|
+
const listenerArray = listeners[event.type];
|
|
2408
|
+
if (listenerArray !== void 0) {
|
|
2409
|
+
event.target = this;
|
|
2410
|
+
const array = listenerArray.slice(0);
|
|
2411
|
+
for (let i = 0, l = array.length; i < l; i++) {
|
|
2412
|
+
array[i].call(this, event);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
const VERSION = "3.1.1";
|
|
2418
|
+
const TOUCH_DOLLY_FACTOR = 1 / 8;
|
|
2419
|
+
const isMac = /Mac/.test((_a = globalThis == null ? void 0 : globalThis.navigator) == null ? void 0 : _a.platform);
|
|
2420
|
+
let THREE;
|
|
2421
|
+
let _ORIGIN;
|
|
2422
|
+
let _AXIS_Y;
|
|
2423
|
+
let _AXIS_Z;
|
|
2424
|
+
let _PLANE_XY;
|
|
2425
|
+
let _v2;
|
|
2426
|
+
let _v3A;
|
|
2427
|
+
let _v3B;
|
|
2428
|
+
let _v3C;
|
|
2429
|
+
let _cameraDirection;
|
|
2430
|
+
let _xColumn;
|
|
2431
|
+
let _yColumn;
|
|
2432
|
+
let _zColumn;
|
|
2433
|
+
let _deltaTarget;
|
|
2434
|
+
let _deltaOffset;
|
|
2435
|
+
let _sphericalA;
|
|
2436
|
+
let _sphericalB;
|
|
2437
|
+
let _box3A;
|
|
2438
|
+
let _box3B;
|
|
2439
|
+
let _sphere;
|
|
2440
|
+
let _quaternionA;
|
|
2441
|
+
let _quaternionB;
|
|
2442
|
+
let _rotationMatrix;
|
|
2443
|
+
let _raycaster;
|
|
2444
|
+
class CameraControls extends EventDispatcher {
|
|
2445
|
+
/**
|
|
2446
|
+
* Creates a `CameraControls` instance.
|
|
2447
|
+
*
|
|
2448
|
+
* Note:
|
|
2449
|
+
* You **must install** three.js before using camera-controls. see [#install](#install)
|
|
2450
|
+
* Not doing so will lead to runtime errors (`undefined` references to THREE).
|
|
2451
|
+
*
|
|
2452
|
+
* e.g.
|
|
2453
|
+
* ```
|
|
2454
|
+
* CameraControls.install( { THREE } );
|
|
2455
|
+
* const cameraControls = new CameraControls( camera, domElement );
|
|
2456
|
+
* ```
|
|
2457
|
+
*
|
|
2458
|
+
* @param camera A `THREE.PerspectiveCamera` or `THREE.OrthographicCamera` to be controlled.
|
|
2459
|
+
* @param domElement A `HTMLElement` for the draggable area, usually `renderer.domElement`.
|
|
2460
|
+
* @category Constructor
|
|
2461
|
+
*/
|
|
2462
|
+
constructor(camera, domElement) {
|
|
2463
|
+
super();
|
|
2464
|
+
/**
|
|
2465
|
+
* Minimum vertical angle in radians.
|
|
2466
|
+
* The angle has to be between `0` and `.maxPolarAngle` inclusive.
|
|
2467
|
+
* The default value is `0`.
|
|
2468
|
+
*
|
|
2469
|
+
* e.g.
|
|
2470
|
+
* ```
|
|
2471
|
+
* cameraControls.maxPolarAngle = 0;
|
|
2472
|
+
* ```
|
|
2473
|
+
* @category Properties
|
|
2474
|
+
*/
|
|
2475
|
+
__publicField(this, "minPolarAngle", 0);
|
|
2476
|
+
// radians
|
|
2477
|
+
/**
|
|
2478
|
+
* Maximum vertical angle in radians.
|
|
2479
|
+
* The angle has to be between `.maxPolarAngle` and `Math.PI` inclusive.
|
|
2480
|
+
* The default value is `Math.PI`.
|
|
2481
|
+
*
|
|
2482
|
+
* e.g.
|
|
2483
|
+
* ```
|
|
2484
|
+
* cameraControls.maxPolarAngle = Math.PI;
|
|
2485
|
+
* ```
|
|
2486
|
+
* @category Properties
|
|
2487
|
+
*/
|
|
2488
|
+
__publicField(this, "maxPolarAngle", Math.PI);
|
|
2489
|
+
// radians
|
|
2490
|
+
/**
|
|
2491
|
+
* Minimum horizontal angle in radians.
|
|
2492
|
+
* The angle has to be less than `.maxAzimuthAngle`.
|
|
2493
|
+
* The default value is `- Infinity`.
|
|
2494
|
+
*
|
|
2495
|
+
* e.g.
|
|
2496
|
+
* ```
|
|
2497
|
+
* cameraControls.minAzimuthAngle = - Infinity;
|
|
2498
|
+
* ```
|
|
2499
|
+
* @category Properties
|
|
2500
|
+
*/
|
|
2501
|
+
__publicField(this, "minAzimuthAngle", -Infinity);
|
|
2502
|
+
// radians
|
|
2503
|
+
/**
|
|
2504
|
+
* Maximum horizontal angle in radians.
|
|
2505
|
+
* The angle has to be greater than `.minAzimuthAngle`.
|
|
2506
|
+
* The default value is `Infinity`.
|
|
2507
|
+
*
|
|
2508
|
+
* e.g.
|
|
2509
|
+
* ```
|
|
2510
|
+
* cameraControls.maxAzimuthAngle = Infinity;
|
|
2511
|
+
* ```
|
|
2512
|
+
* @category Properties
|
|
2513
|
+
*/
|
|
2514
|
+
__publicField(this, "maxAzimuthAngle", Infinity);
|
|
2515
|
+
// radians
|
|
2516
|
+
// How far you can dolly in and out ( PerspectiveCamera only )
|
|
2517
|
+
/**
|
|
2518
|
+
* Minimum distance for dolly. The value must be higher than `0`. Default is `Number.EPSILON`.
|
|
2519
|
+
* PerspectiveCamera only.
|
|
2520
|
+
* @category Properties
|
|
2521
|
+
*/
|
|
2522
|
+
__publicField(this, "minDistance", Number.EPSILON);
|
|
2523
|
+
/**
|
|
2524
|
+
* Maximum distance for dolly. The value must be higher than `minDistance`. Default is `Infinity`.
|
|
2525
|
+
* PerspectiveCamera only.
|
|
2526
|
+
* @category Properties
|
|
2527
|
+
*/
|
|
2528
|
+
__publicField(this, "maxDistance", Infinity);
|
|
2529
|
+
/**
|
|
2530
|
+
* `true` to enable Infinity Dolly for wheel and pinch. Use this with `minDistance` and `maxDistance`
|
|
2531
|
+
* If the Dolly distance is less (or over) than the `minDistance` (or `maxDistance`), `infinityDolly` will keep the distance and pushes the target position instead.
|
|
2532
|
+
* @category Properties
|
|
2533
|
+
*/
|
|
2534
|
+
__publicField(this, "infinityDolly", false);
|
|
2535
|
+
/**
|
|
2536
|
+
* Minimum camera zoom.
|
|
2537
|
+
* @category Properties
|
|
2538
|
+
*/
|
|
2539
|
+
__publicField(this, "minZoom", 0.01);
|
|
2540
|
+
/**
|
|
2541
|
+
* Maximum camera zoom.
|
|
2542
|
+
* @category Properties
|
|
2543
|
+
*/
|
|
2544
|
+
__publicField(this, "maxZoom", Infinity);
|
|
2545
|
+
/**
|
|
2546
|
+
* Approximate time in seconds to reach the target. A smaller value will reach the target faster.
|
|
2547
|
+
* @category Properties
|
|
2548
|
+
*/
|
|
2549
|
+
__publicField(this, "smoothTime", 0.25);
|
|
2550
|
+
/**
|
|
2551
|
+
* the smoothTime while dragging
|
|
2552
|
+
* @category Properties
|
|
2553
|
+
*/
|
|
2554
|
+
__publicField(this, "draggingSmoothTime", 0.125);
|
|
2555
|
+
/**
|
|
2556
|
+
* the smoothTime while using mouse wheel for dolly or zoom.
|
|
2557
|
+
* Defaults to `draggingSmoothTime` (0.125).
|
|
2558
|
+
* @category Properties
|
|
2559
|
+
*/
|
|
2560
|
+
__publicField(this, "wheelSmoothTime", 0.125);
|
|
2561
|
+
/**
|
|
2562
|
+
* Approximate time in seconds to reach the target for azimuth (horizontal) rotation when not user-controlled.
|
|
2563
|
+
* Defaults to `smoothTime` (0.25).
|
|
2564
|
+
* @category Properties
|
|
2565
|
+
*/
|
|
2566
|
+
__publicField(this, "azimuthTime", 0.25);
|
|
2567
|
+
/**
|
|
2568
|
+
* Approximate time in seconds to reach the target for polar (vertical) rotation when not user-controlled.
|
|
2569
|
+
* Defaults to `smoothTime` (0.25).
|
|
2570
|
+
* @category Properties
|
|
2571
|
+
*/
|
|
2572
|
+
__publicField(this, "polarTime", 0.25);
|
|
2573
|
+
/**
|
|
2574
|
+
* Approximate time in seconds to reach the target for dolly when not user-controlled.
|
|
2575
|
+
* Defaults to `smoothTime` (0.25).
|
|
2576
|
+
* @category Properties
|
|
2577
|
+
*/
|
|
2578
|
+
__publicField(this, "dollyTime", 0.25);
|
|
2579
|
+
/**
|
|
2580
|
+
* Approximate time in seconds to reach the target for truck when not user-controlled.
|
|
2581
|
+
* Defaults to `smoothTime` (0.25).
|
|
2582
|
+
* @category Properties
|
|
2583
|
+
*/
|
|
2584
|
+
__publicField(this, "truckTime", 0.25);
|
|
2585
|
+
/**
|
|
2586
|
+
* Max transition speed in unit-per-seconds
|
|
2587
|
+
* @category Properties
|
|
2588
|
+
*/
|
|
2589
|
+
__publicField(this, "maxSpeed", Infinity);
|
|
2590
|
+
/**
|
|
2591
|
+
* Speed of azimuth (horizontal) rotation.
|
|
2592
|
+
* @category Properties
|
|
2593
|
+
*/
|
|
2594
|
+
__publicField(this, "azimuthRotateSpeed", 1);
|
|
2595
|
+
/**
|
|
2596
|
+
* Speed of polar (vertical) rotation.
|
|
2597
|
+
* @category Properties
|
|
2598
|
+
*/
|
|
2599
|
+
__publicField(this, "polarRotateSpeed", 1);
|
|
2600
|
+
/**
|
|
2601
|
+
* Speed of mouse-wheel dollying.
|
|
2602
|
+
* @category Properties
|
|
2603
|
+
*/
|
|
2604
|
+
__publicField(this, "dollySpeed", 1);
|
|
2605
|
+
/**
|
|
2606
|
+
* `true` to invert direction when dollying or zooming via drag
|
|
2607
|
+
* @category Properties
|
|
2608
|
+
*/
|
|
2609
|
+
__publicField(this, "dollyDragInverted", false);
|
|
2610
|
+
/**
|
|
2611
|
+
* Speed of drag for truck and pedestal.
|
|
2612
|
+
* @category Properties
|
|
2613
|
+
*/
|
|
2614
|
+
__publicField(this, "truckSpeed", 2);
|
|
2615
|
+
/**
|
|
2616
|
+
* `true` to enable Dolly-in to the mouse cursor coords.
|
|
2617
|
+
* @category Properties
|
|
2618
|
+
*/
|
|
2619
|
+
__publicField(this, "dollyToCursor", false);
|
|
2620
|
+
/**
|
|
2621
|
+
* @category Properties
|
|
2622
|
+
*/
|
|
2623
|
+
__publicField(this, "dragToOffset", false);
|
|
2624
|
+
/**
|
|
2625
|
+
* Friction ratio of the boundary.
|
|
2626
|
+
* @category Properties
|
|
2627
|
+
*/
|
|
2628
|
+
__publicField(this, "boundaryFriction", 0);
|
|
2629
|
+
/**
|
|
2630
|
+
* Controls how soon the `rest` event fires as the camera slows.
|
|
2631
|
+
* @category Properties
|
|
2632
|
+
*/
|
|
2633
|
+
__publicField(this, "restThreshold", 0.01);
|
|
2634
|
+
/**
|
|
2635
|
+
* An array of Meshes to collide with camera.
|
|
2636
|
+
* Be aware colliderMeshes may decrease performance. The collision test uses 4 raycasters from the camera since the near plane has 4 corners.
|
|
2637
|
+
* @category Properties
|
|
2638
|
+
*/
|
|
2639
|
+
__publicField(this, "colliderMeshes", []);
|
|
2640
|
+
// button configs
|
|
2641
|
+
/**
|
|
2642
|
+
* User's mouse input config.
|
|
2643
|
+
*
|
|
2644
|
+
* | button to assign | behavior |
|
|
2645
|
+
* | --------------------- | -------- |
|
|
2646
|
+
* | `mouseButtons.left` | `CameraControls.ACTION.ROTATE`* \| `CameraControls.ACTION.ROTATE_POLAR` \| `CameraControls.ACTION.ROTATE_AZIMUTH` \| `CameraControls.ACTION.TRUCK` \| `CameraControls.ACTION.OFFSET` \| `CameraControls.ACTION.DOLLY` \| `CameraControls.ACTION.ZOOM` \| `CameraControls.ACTION.NONE` |
|
|
2647
|
+
* | `mouseButtons.right` | `CameraControls.ACTION.ROTATE` \| `CameraControls.ACTION.ROTATE_POLAR` \| `CameraControls.ACTION.ROTATE_AZIMUTH` \| `CameraControls.ACTION.TRUCK`* \| `CameraControls.ACTION.OFFSET` \| `CameraControls.ACTION.DOLLY` \| `CameraControls.ACTION.ZOOM` \| `CameraControls.ACTION.NONE` |
|
|
2648
|
+
* | `mouseButtons.wheel` ¹ | `CameraControls.ACTION.ROTATE` \| `CameraControls.ACTION.ROTATE_POLAR` \| `CameraControls.ACTION.ROTATE_AZIMUTH` \| `CameraControls.ACTION.TRUCK` \| `CameraControls.ACTION.OFFSET` \| `CameraControls.ACTION.DOLLY` \| `CameraControls.ACTION.ZOOM` \| `CameraControls.ACTION.NONE` |
|
|
2649
|
+
* | `mouseButtons.middle` ² | `CameraControls.ACTION.ROTATE` \| `CameraControls.ACTION.ROTATE_POLAR` \| `CameraControls.ACTION.ROTATE_AZIMUTH` \| `CameraControls.ACTION.TRUCK` \| `CameraControls.ACTION.OFFSET` \| `CameraControls.ACTION.DOLLY`* \| `CameraControls.ACTION.ZOOM` \| `CameraControls.ACTION.NONE` |
|
|
2650
|
+
*
|
|
2651
|
+
* 1. Mouse wheel event for scroll "up/down" on mac "up/down/left/right"
|
|
2652
|
+
* 2. Mouse click on wheel event "button"
|
|
2653
|
+
* - \* is the default.
|
|
2654
|
+
* - The default of `mouseButtons.wheel` is:
|
|
2655
|
+
* - `DOLLY` for Perspective camera.
|
|
2656
|
+
* - `ZOOM` for Orthographic camera, and can't set `DOLLY`.
|
|
2657
|
+
* @category Properties
|
|
2658
|
+
*/
|
|
2659
|
+
__publicField(this, "mouseButtons");
|
|
2660
|
+
/**
|
|
2661
|
+
* User's touch input config.
|
|
2662
|
+
*
|
|
2663
|
+
* | fingers to assign | behavior |
|
|
2664
|
+
* | --------------------- | -------- |
|
|
2665
|
+
* | `touches.one` | `CameraControls.ACTION.TOUCH_ROTATE`* \| `CameraControls.ACTION.TOUCH_TRUCK` \| `CameraControls.ACTION.TOUCH_OFFSET` \| `CameraControls.ACTION.DOLLY` | `CameraControls.ACTION.ZOOM` | `CameraControls.ACTION.NONE` |
|
|
2666
|
+
* | `touches.two` | `ACTION.TOUCH_DOLLY_TRUCK` \| `ACTION.TOUCH_DOLLY_OFFSET` \| `ACTION.TOUCH_DOLLY_ROTATE` \| `ACTION.TOUCH_ZOOM_TRUCK` \| `ACTION.TOUCH_ZOOM_OFFSET` \| `ACTION.TOUCH_ZOOM_ROTATE` \| `ACTION.TOUCH_DOLLY` \| `ACTION.TOUCH_ZOOM` \| `CameraControls.ACTION.TOUCH_ROTATE` \| `CameraControls.ACTION.TOUCH_TRUCK` \| `CameraControls.ACTION.TOUCH_OFFSET` \| `CameraControls.ACTION.NONE` |
|
|
2667
|
+
* | `touches.three` | `ACTION.TOUCH_DOLLY_TRUCK` \| `ACTION.TOUCH_DOLLY_OFFSET` \| `ACTION.TOUCH_DOLLY_ROTATE` \| `ACTION.TOUCH_ZOOM_TRUCK` \| `ACTION.TOUCH_ZOOM_OFFSET` \| `ACTION.TOUCH_ZOOM_ROTATE` \| `CameraControls.ACTION.TOUCH_ROTATE` \| `CameraControls.ACTION.TOUCH_TRUCK` \| `CameraControls.ACTION.TOUCH_OFFSET` \| `CameraControls.ACTION.NONE` |
|
|
2668
|
+
*
|
|
2669
|
+
* - \* is the default.
|
|
2670
|
+
* - The default of `touches.two` and `touches.three` is:
|
|
2671
|
+
* - `TOUCH_DOLLY_TRUCK` for Perspective camera.
|
|
2672
|
+
* - `TOUCH_ZOOM_TRUCK` for Orthographic camera, and can't set `TOUCH_DOLLY_TRUCK` and `TOUCH_DOLLY`.
|
|
2673
|
+
* @category Properties
|
|
2674
|
+
*/
|
|
2675
|
+
__publicField(this, "touches");
|
|
2676
|
+
/**
|
|
2677
|
+
* Force cancel user dragging.
|
|
2678
|
+
* @category Methods
|
|
2679
|
+
*/
|
|
2680
|
+
// cancel will be overwritten in the constructor.
|
|
2681
|
+
__publicField(this, "cancel", () => {
|
|
2682
|
+
});
|
|
2683
|
+
/**
|
|
2684
|
+
* Still an experimental feature.
|
|
2685
|
+
* This could change at any time.
|
|
2686
|
+
* @category Methods
|
|
2687
|
+
*/
|
|
2688
|
+
__publicField(this, "lockPointer");
|
|
2689
|
+
/**
|
|
2690
|
+
* Still an experimental feature.
|
|
2691
|
+
* This could change at any time.
|
|
2692
|
+
* @category Methods
|
|
2693
|
+
*/
|
|
2694
|
+
__publicField(this, "unlockPointer");
|
|
2695
|
+
__publicField(this, "_enabled", true);
|
|
2696
|
+
__publicField(this, "_camera");
|
|
2697
|
+
__publicField(this, "_yAxisUpSpace");
|
|
2698
|
+
__publicField(this, "_yAxisUpSpaceInverse");
|
|
2699
|
+
__publicField(this, "_state", ACTION.NONE);
|
|
2700
|
+
__publicField(this, "_domElement");
|
|
2701
|
+
__publicField(this, "_viewport", null);
|
|
2702
|
+
// the location of focus, where the object orbits around
|
|
2703
|
+
__publicField(this, "_target");
|
|
2704
|
+
__publicField(this, "_targetEnd");
|
|
2705
|
+
__publicField(this, "_focalOffset");
|
|
2706
|
+
__publicField(this, "_focalOffsetEnd");
|
|
2707
|
+
// rotation and dolly distance
|
|
2708
|
+
__publicField(this, "_spherical");
|
|
2709
|
+
__publicField(this, "_sphericalEnd");
|
|
2710
|
+
__publicField(this, "_lastDistance");
|
|
2711
|
+
__publicField(this, "_zoom");
|
|
2712
|
+
__publicField(this, "_zoomEnd");
|
|
2713
|
+
__publicField(this, "_lastZoom");
|
|
2714
|
+
// reset
|
|
2715
|
+
__publicField(this, "_cameraUp0");
|
|
2716
|
+
__publicField(this, "_target0");
|
|
2717
|
+
__publicField(this, "_position0");
|
|
2718
|
+
__publicField(this, "_zoom0");
|
|
2719
|
+
__publicField(this, "_focalOffset0");
|
|
2720
|
+
__publicField(this, "_dollyControlCoord");
|
|
2721
|
+
__publicField(this, "_dollyToCursorTarget");
|
|
2722
|
+
__publicField(this, "_changedDolly", 0);
|
|
2723
|
+
__publicField(this, "_changedZoom", 0);
|
|
2724
|
+
// collisionTest uses nearPlane. ( PerspectiveCamera only )
|
|
2725
|
+
__publicField(this, "_nearPlaneCorners");
|
|
2726
|
+
__publicField(this, "_hasRested", true);
|
|
2727
|
+
__publicField(this, "_boundary");
|
|
2728
|
+
__publicField(this, "_boundaryEnclosesCamera", false);
|
|
2729
|
+
__publicField(this, "_needsUpdate", true);
|
|
2730
|
+
__publicField(this, "_updatedLastTime", false);
|
|
2731
|
+
__publicField(this, "_elementRect", new DOMRect());
|
|
2732
|
+
__publicField(this, "_isDragging", false);
|
|
2733
|
+
__publicField(this, "_dragNeedsUpdate", true);
|
|
2734
|
+
__publicField(this, "_activePointers", []);
|
|
2735
|
+
__publicField(this, "_lockedPointer", null);
|
|
2736
|
+
__publicField(this, "_interactiveArea", new DOMRect(0, 0, 1, 1));
|
|
2737
|
+
// Use draggingSmoothTime over smoothTime while true.
|
|
2738
|
+
// set automatically true on user-dragging start.
|
|
2739
|
+
// set automatically false on programmable methods call.
|
|
2740
|
+
__publicField(this, "_isUserControllingRotate", false);
|
|
2741
|
+
__publicField(this, "_isUserControllingDolly", false);
|
|
2742
|
+
__publicField(this, "_isUserControllingTruck", false);
|
|
2743
|
+
__publicField(this, "_isUserControllingOffset", false);
|
|
2744
|
+
__publicField(this, "_isUserControllingZoom", false);
|
|
2745
|
+
__publicField(this, "_isWheelControllingDolly", false);
|
|
2746
|
+
__publicField(this, "_isWheelControllingZoom", false);
|
|
2747
|
+
__publicField(this, "_lastDollyDirection", DOLLY_DIRECTION.NONE);
|
|
2748
|
+
// velocities for smoothDamp
|
|
2749
|
+
__publicField(this, "_thetaVelocity", { value: 0 });
|
|
2750
|
+
__publicField(this, "_phiVelocity", { value: 0 });
|
|
2751
|
+
__publicField(this, "_radiusVelocity", { value: 0 });
|
|
2752
|
+
__publicField(this, "_targetVelocity", new THREE.Vector3());
|
|
2753
|
+
__publicField(this, "_focalOffsetVelocity", new THREE.Vector3());
|
|
2754
|
+
__publicField(this, "_zoomVelocity", { value: 0 });
|
|
2755
|
+
__publicField(this, "_truckInternal", (deltaX, deltaY, dragToOffset, screenSpacePanning) => {
|
|
2756
|
+
let truckX;
|
|
2757
|
+
let pedestalY;
|
|
2758
|
+
if (isPerspectiveCamera(this._camera)) {
|
|
2759
|
+
const offset = _v3A.copy(this._camera.position).sub(this._target);
|
|
2760
|
+
const fov = this._camera.getEffectiveFOV() * DEG2RAD;
|
|
2761
|
+
const targetDistance = offset.length() * Math.tan(fov * 0.5);
|
|
2762
|
+
truckX = this.truckSpeed * deltaX * targetDistance / this._elementRect.height;
|
|
2763
|
+
pedestalY = this.truckSpeed * deltaY * targetDistance / this._elementRect.height;
|
|
2764
|
+
} else if (isOrthographicCamera(this._camera)) {
|
|
2765
|
+
const camera = this._camera;
|
|
2766
|
+
truckX = this.truckSpeed * deltaX * (camera.right - camera.left) / camera.zoom / this._elementRect.width;
|
|
2767
|
+
pedestalY = this.truckSpeed * deltaY * (camera.top - camera.bottom) / camera.zoom / this._elementRect.height;
|
|
2768
|
+
} else {
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
if (screenSpacePanning) {
|
|
2772
|
+
dragToOffset ? this.setFocalOffset(this._focalOffsetEnd.x + truckX, this._focalOffsetEnd.y, this._focalOffsetEnd.z, true) : this.truck(truckX, 0, true);
|
|
2773
|
+
this.forward(-pedestalY, true);
|
|
2774
|
+
} else {
|
|
2775
|
+
dragToOffset ? this.setFocalOffset(this._focalOffsetEnd.x + truckX, this._focalOffsetEnd.y + pedestalY, this._focalOffsetEnd.z, true) : this.truck(truckX, pedestalY, true);
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
__publicField(this, "_rotateInternal", (deltaX, deltaY) => {
|
|
2779
|
+
const hasRotate = (this._state & ACTION.ROTATE) === ACTION.ROTATE;
|
|
2780
|
+
const hasRotatePolar = (this._state & ACTION.ROTATE_POLAR) === ACTION.ROTATE_POLAR;
|
|
2781
|
+
const hasRotateAzimuth = (this._state & ACTION.ROTATE_AZIMUTH) === ACTION.ROTATE_AZIMUTH;
|
|
2782
|
+
let theta = 0;
|
|
2783
|
+
let phi = 0;
|
|
2784
|
+
if (hasRotate || hasRotatePolar && hasRotateAzimuth) {
|
|
2785
|
+
theta = PI_2 * this.azimuthRotateSpeed * deltaX / this._elementRect.height;
|
|
2786
|
+
phi = PI_2 * this.polarRotateSpeed * deltaY / this._elementRect.height;
|
|
2787
|
+
} else {
|
|
2788
|
+
if (hasRotateAzimuth) {
|
|
2789
|
+
theta = PI_2 * this.azimuthRotateSpeed * deltaX / this._elementRect.height;
|
|
2790
|
+
}
|
|
2791
|
+
if (hasRotatePolar) {
|
|
2792
|
+
phi = PI_2 * this.polarRotateSpeed * deltaY / this._elementRect.height;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
if (theta !== 0 || phi !== 0) {
|
|
2796
|
+
this.rotate(theta, phi, true);
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2799
|
+
__publicField(this, "_cursorToWorld", (x, y) => {
|
|
2800
|
+
const camera = this._camera;
|
|
2801
|
+
const cameraDirection = this._getCameraDirection(_cameraDirection);
|
|
2802
|
+
const planeX = _v3A.copy(cameraDirection).cross(camera.up).normalize();
|
|
2803
|
+
if (planeX.lengthSq() === 0) planeX.x = 1;
|
|
2804
|
+
const planeY = _v3B.crossVectors(planeX, cameraDirection);
|
|
2805
|
+
const worldToScreen = this._sphericalEnd.radius * Math.tan(camera.getEffectiveFOV() * DEG2RAD * 0.5);
|
|
2806
|
+
const cursor = _v3C.copy(this._targetEnd).add(planeX.multiplyScalar(x * worldToScreen * camera.aspect)).add(planeY.multiplyScalar(y * worldToScreen));
|
|
2807
|
+
return cursor;
|
|
2808
|
+
});
|
|
2809
|
+
__publicField(this, "_cursorToPlane", (x, y) => {
|
|
2810
|
+
const cursor = this._cursorToWorld(x, y);
|
|
2811
|
+
const origin2 = _v3A.setFromSpherical(this._sphericalEnd).applyQuaternion(this._yAxisUpSpaceInverse).add(this._targetEnd);
|
|
2812
|
+
const rayDirection = _v3B.copy(cursor).sub(origin2).normalize();
|
|
2813
|
+
_raycaster.set(origin2, rayDirection);
|
|
2814
|
+
const intersection = _raycaster.ray.intersectPlane(_PLANE_XY, _v3C);
|
|
2815
|
+
return intersection;
|
|
2816
|
+
});
|
|
2817
|
+
__publicField(this, "_dollyInternal", (delta, x, y) => {
|
|
2818
|
+
const dollyScale = Math.pow(0.95, -delta * this.dollySpeed);
|
|
2819
|
+
const lastDistance = this._sphericalEnd.radius;
|
|
2820
|
+
const distance = this._sphericalEnd.radius * dollyScale;
|
|
2821
|
+
const clampedDistance = clamp(distance, this.minDistance, this.maxDistance);
|
|
2822
|
+
const overflowedDistance = clampedDistance - distance;
|
|
2823
|
+
let truckOverride = false;
|
|
2824
|
+
if (this.infinityDolly) {
|
|
2825
|
+
if (this.dollyToCursor) {
|
|
2826
|
+
this._dollyToNoClamp(distance, true);
|
|
2827
|
+
this._changedDolly += distance - lastDistance;
|
|
2828
|
+
} else {
|
|
2829
|
+
this.dollyInFixed(overflowedDistance, true);
|
|
2830
|
+
this._dollyToNoClamp(clampedDistance, true);
|
|
2831
|
+
}
|
|
2832
|
+
} else {
|
|
2833
|
+
if (this.dollyToCursor) {
|
|
2834
|
+
const deltaDistance = clampedDistance - lastDistance;
|
|
2835
|
+
this._dollyControlCoord.set(x, y);
|
|
2836
|
+
this._dollyToNoClamp(clampedDistance, true);
|
|
2837
|
+
const intersection = this._cursorToPlane(x, y);
|
|
2838
|
+
if (intersection) {
|
|
2839
|
+
this._changedDolly += deltaDistance;
|
|
2840
|
+
this._dollyToCursorTarget.copy(intersection);
|
|
2841
|
+
} else {
|
|
2842
|
+
this._dollyToNoClamp(lastDistance, true);
|
|
2843
|
+
this._changedDolly = 0;
|
|
2844
|
+
truckOverride = true;
|
|
2845
|
+
const bottomNDC = new THREE.Vector2(0, -1);
|
|
2846
|
+
const ndcToClient = new THREE.Vector2(-1, 1);
|
|
2847
|
+
this._dollyControlCoord.sub(bottomNDC).multiplyScalar(deltaDistance / (this.truckSpeed * this.truckSpeed)).multiply(ndcToClient);
|
|
2848
|
+
this._truckInternal(this._dollyControlCoord.x, this._dollyControlCoord.y, false, true);
|
|
2849
|
+
}
|
|
2850
|
+
} else {
|
|
2851
|
+
this._dollyToNoClamp(clampedDistance, true);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
if (!truckOverride) this._lastDollyDirection = Math.sign(-delta);
|
|
2855
|
+
return truckOverride;
|
|
2856
|
+
});
|
|
2857
|
+
__publicField(this, "_zoomInternal", (delta, x, y) => {
|
|
2858
|
+
const zoomScale = Math.pow(0.95, delta * this.dollySpeed);
|
|
2859
|
+
const lastZoom = this._zoom;
|
|
2860
|
+
const zoom = this._zoom * zoomScale;
|
|
2861
|
+
this.zoomTo(zoom, true);
|
|
2862
|
+
if (this.dollyToCursor) {
|
|
2863
|
+
this._changedZoom += zoom - lastZoom;
|
|
2864
|
+
this._dollyControlCoord.set(x, y);
|
|
2865
|
+
}
|
|
2866
|
+
});
|
|
2867
|
+
if (typeof THREE === "undefined") {
|
|
2868
|
+
console.error("camera-controls: `THREE` is undefined. You must first run `CameraControls.install( { THREE: THREE } )`. Check the docs for further information.");
|
|
2869
|
+
}
|
|
2870
|
+
this._camera = camera;
|
|
2871
|
+
this._yAxisUpSpace = new THREE.Quaternion().setFromUnitVectors(this._camera.up, _AXIS_Y);
|
|
2872
|
+
this._yAxisUpSpaceInverse = this._yAxisUpSpace.clone().invert();
|
|
2873
|
+
this._state = ACTION.NONE;
|
|
2874
|
+
this._target = new THREE.Vector3();
|
|
2875
|
+
this._targetEnd = this._target.clone();
|
|
2876
|
+
this._focalOffset = new THREE.Vector3();
|
|
2877
|
+
this._focalOffsetEnd = this._focalOffset.clone();
|
|
2878
|
+
this._spherical = new THREE.Spherical().setFromVector3(_v3A.copy(this._camera.position).applyQuaternion(this._yAxisUpSpace));
|
|
2879
|
+
this._sphericalEnd = this._spherical.clone();
|
|
2880
|
+
this._lastDistance = this._spherical.radius;
|
|
2881
|
+
this._zoom = this._camera.zoom;
|
|
2882
|
+
this._zoomEnd = this._zoom;
|
|
2883
|
+
this._lastZoom = this._zoom;
|
|
2884
|
+
this._nearPlaneCorners = [
|
|
2885
|
+
new THREE.Vector3(),
|
|
2886
|
+
new THREE.Vector3(),
|
|
2887
|
+
new THREE.Vector3(),
|
|
2888
|
+
new THREE.Vector3()
|
|
2889
|
+
];
|
|
2890
|
+
this._updateNearPlaneCorners();
|
|
2891
|
+
this._boundary = new THREE.Box3(new THREE.Vector3(-Infinity, -Infinity, -Infinity), new THREE.Vector3(Infinity, Infinity, Infinity));
|
|
2892
|
+
this._cameraUp0 = this._camera.up.clone();
|
|
2893
|
+
this._target0 = this._target.clone();
|
|
2894
|
+
this._position0 = this._camera.position.clone();
|
|
2895
|
+
this._zoom0 = this._zoom;
|
|
2896
|
+
this._focalOffset0 = this._focalOffset.clone();
|
|
2897
|
+
this._dollyControlCoord = new THREE.Vector2();
|
|
2898
|
+
this._dollyToCursorTarget = new THREE.Vector3();
|
|
2899
|
+
this.mouseButtons = {
|
|
2900
|
+
left: ACTION.ROTATE,
|
|
2901
|
+
middle: ACTION.DOLLY,
|
|
2902
|
+
right: ACTION.TRUCK,
|
|
2903
|
+
wheel: isPerspectiveCamera(this._camera) ? ACTION.DOLLY : isOrthographicCamera(this._camera) ? ACTION.ZOOM : ACTION.NONE
|
|
2904
|
+
};
|
|
2905
|
+
this.touches = {
|
|
2906
|
+
one: ACTION.TOUCH_ROTATE,
|
|
2907
|
+
two: isPerspectiveCamera(this._camera) ? ACTION.TOUCH_DOLLY_TRUCK : isOrthographicCamera(this._camera) ? ACTION.TOUCH_ZOOM_TRUCK : ACTION.NONE,
|
|
2908
|
+
three: ACTION.TOUCH_TRUCK
|
|
2909
|
+
};
|
|
2910
|
+
const dragStartPosition = new THREE.Vector2();
|
|
2911
|
+
const lastDragPosition = new THREE.Vector2();
|
|
2912
|
+
const dollyStart = new THREE.Vector2();
|
|
2913
|
+
const onPointerDown = (event) => {
|
|
2914
|
+
if (!this._enabled || !this._domElement)
|
|
2915
|
+
return;
|
|
2916
|
+
if (this._interactiveArea.left !== 0 || this._interactiveArea.top !== 0 || this._interactiveArea.width !== 1 || this._interactiveArea.height !== 1) {
|
|
2917
|
+
const elRect = this._domElement.getBoundingClientRect();
|
|
2918
|
+
const left = event.clientX / elRect.width;
|
|
2919
|
+
const top = event.clientY / elRect.height;
|
|
2920
|
+
if (left < this._interactiveArea.left || left > this._interactiveArea.right || top < this._interactiveArea.top || top > this._interactiveArea.bottom)
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
const mouseButton = event.pointerType !== "mouse" ? null : (event.buttons & MOUSE_BUTTON.LEFT) === MOUSE_BUTTON.LEFT ? MOUSE_BUTTON.LEFT : (event.buttons & MOUSE_BUTTON.MIDDLE) === MOUSE_BUTTON.MIDDLE ? MOUSE_BUTTON.MIDDLE : (event.buttons & MOUSE_BUTTON.RIGHT) === MOUSE_BUTTON.RIGHT ? MOUSE_BUTTON.RIGHT : null;
|
|
2924
|
+
if (mouseButton !== null) {
|
|
2925
|
+
const zombiePointer = this._findPointerByMouseButton(mouseButton);
|
|
2926
|
+
zombiePointer && this._disposePointer(zombiePointer);
|
|
2927
|
+
}
|
|
2928
|
+
if ((event.buttons & MOUSE_BUTTON.LEFT) === MOUSE_BUTTON.LEFT && this._lockedPointer)
|
|
2929
|
+
return;
|
|
2930
|
+
const pointer = {
|
|
2931
|
+
pointerId: event.pointerId,
|
|
2932
|
+
clientX: event.clientX,
|
|
2933
|
+
clientY: event.clientY,
|
|
2934
|
+
deltaX: 0,
|
|
2935
|
+
deltaY: 0,
|
|
2936
|
+
mouseButton
|
|
2937
|
+
};
|
|
2938
|
+
this._activePointers.push(pointer);
|
|
2939
|
+
this._domElement.ownerDocument.removeEventListener("pointermove", onPointerMove, { passive: false });
|
|
2940
|
+
this._domElement.ownerDocument.removeEventListener("pointerup", onPointerUp);
|
|
2941
|
+
this._domElement.ownerDocument.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
2942
|
+
this._domElement.ownerDocument.addEventListener("pointerup", onPointerUp);
|
|
2943
|
+
this._isDragging = true;
|
|
2944
|
+
startDragging(event);
|
|
2945
|
+
};
|
|
2946
|
+
const onPointerMove = (event) => {
|
|
2947
|
+
if (event.cancelable)
|
|
2948
|
+
event.preventDefault();
|
|
2949
|
+
const pointerId = event.pointerId;
|
|
2950
|
+
const pointer = this._lockedPointer || this._findPointerById(pointerId);
|
|
2951
|
+
if (!pointer)
|
|
2952
|
+
return;
|
|
2953
|
+
pointer.clientX = event.clientX;
|
|
2954
|
+
pointer.clientY = event.clientY;
|
|
2955
|
+
pointer.deltaX = event.movementX;
|
|
2956
|
+
pointer.deltaY = event.movementY;
|
|
2957
|
+
this._state = 0;
|
|
2958
|
+
if (event.pointerType === "touch") {
|
|
2959
|
+
switch (this._activePointers.length) {
|
|
2960
|
+
case 1:
|
|
2961
|
+
this._state = this.touches.one;
|
|
2962
|
+
break;
|
|
2963
|
+
case 2:
|
|
2964
|
+
this._state = this.touches.two;
|
|
2965
|
+
break;
|
|
2966
|
+
case 3:
|
|
2967
|
+
this._state = this.touches.three;
|
|
2968
|
+
break;
|
|
2969
|
+
}
|
|
2970
|
+
} else {
|
|
2971
|
+
if (!this._isDragging && this._lockedPointer || this._isDragging && (event.buttons & MOUSE_BUTTON.LEFT) === MOUSE_BUTTON.LEFT) {
|
|
2972
|
+
this._state = this._state | this.mouseButtons.left;
|
|
2973
|
+
}
|
|
2974
|
+
if (this._isDragging && (event.buttons & MOUSE_BUTTON.MIDDLE) === MOUSE_BUTTON.MIDDLE) {
|
|
2975
|
+
this._state = this._state | this.mouseButtons.middle;
|
|
2976
|
+
}
|
|
2977
|
+
if (this._isDragging && (event.buttons & MOUSE_BUTTON.RIGHT) === MOUSE_BUTTON.RIGHT) {
|
|
2978
|
+
this._state = this._state | this.mouseButtons.right;
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
dragging();
|
|
2982
|
+
};
|
|
2983
|
+
const onPointerUp = (event) => {
|
|
2984
|
+
const pointer = this._findPointerById(event.pointerId);
|
|
2985
|
+
if (pointer && pointer === this._lockedPointer)
|
|
2986
|
+
return;
|
|
2987
|
+
pointer && this._disposePointer(pointer);
|
|
2988
|
+
if (event.pointerType === "touch") {
|
|
2989
|
+
switch (this._activePointers.length) {
|
|
2990
|
+
case 0:
|
|
2991
|
+
this._state = ACTION.NONE;
|
|
2992
|
+
break;
|
|
2993
|
+
case 1:
|
|
2994
|
+
this._state = this.touches.one;
|
|
2995
|
+
break;
|
|
2996
|
+
case 2:
|
|
2997
|
+
this._state = this.touches.two;
|
|
2998
|
+
break;
|
|
2999
|
+
case 3:
|
|
3000
|
+
this._state = this.touches.three;
|
|
3001
|
+
break;
|
|
3002
|
+
}
|
|
3003
|
+
} else {
|
|
3004
|
+
this._state = ACTION.NONE;
|
|
3005
|
+
}
|
|
3006
|
+
endDragging();
|
|
3007
|
+
};
|
|
3008
|
+
let lastScrollTimeStamp = -1;
|
|
3009
|
+
const onMouseWheel = (event) => {
|
|
3010
|
+
if (!this._domElement)
|
|
3011
|
+
return;
|
|
3012
|
+
if (!this._enabled || this.mouseButtons.wheel === ACTION.NONE)
|
|
3013
|
+
return;
|
|
3014
|
+
if (this._interactiveArea.left !== 0 || this._interactiveArea.top !== 0 || this._interactiveArea.width !== 1 || this._interactiveArea.height !== 1) {
|
|
3015
|
+
const elRect = this._domElement.getBoundingClientRect();
|
|
3016
|
+
const left = event.clientX / elRect.width;
|
|
3017
|
+
const top = event.clientY / elRect.height;
|
|
3018
|
+
if (left < this._interactiveArea.left || left > this._interactiveArea.right || top < this._interactiveArea.top || top > this._interactiveArea.bottom)
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
event.preventDefault();
|
|
3022
|
+
if (this.dollyToCursor || this.mouseButtons.wheel === ACTION.ROTATE || this.mouseButtons.wheel === ACTION.TRUCK) {
|
|
3023
|
+
const now = performance.now();
|
|
3024
|
+
if (lastScrollTimeStamp - now < 1e3)
|
|
3025
|
+
this._getClientRect(this._elementRect);
|
|
3026
|
+
lastScrollTimeStamp = now;
|
|
3027
|
+
}
|
|
3028
|
+
const deltaYFactor = isMac ? -1 : -3;
|
|
3029
|
+
const delta = event.deltaMode === 1 || event.ctrlKey ? event.deltaY / deltaYFactor : event.deltaY / (deltaYFactor * 10);
|
|
3030
|
+
const x = this.dollyToCursor ? (event.clientX - this._elementRect.x) / this._elementRect.width * 2 - 1 : 0;
|
|
3031
|
+
const y = this.dollyToCursor ? (event.clientY - this._elementRect.y) / this._elementRect.height * -2 + 1 : 0;
|
|
3032
|
+
const wheelAction = this.mouseButtons.wheel;
|
|
3033
|
+
if (wheelAction === ACTION.ROTATE || wheelAction === ACTION.ROTATE_POLAR || wheelAction === ACTION.ROTATE_AZIMUTH || wheelAction === (ACTION.ROTATE_POLAR | ACTION.ROTATE_AZIMUTH)) {
|
|
3034
|
+
this._rotateInternal(event.deltaX, event.deltaY);
|
|
3035
|
+
this._isUserControllingRotate = true;
|
|
3036
|
+
} else if (wheelAction === ACTION.TRUCK) {
|
|
3037
|
+
this._truckInternal(event.deltaX, event.deltaY, false, false);
|
|
3038
|
+
this._isUserControllingTruck = true;
|
|
3039
|
+
} else if (wheelAction === ACTION.SCREEN_PAN) {
|
|
3040
|
+
this._truckInternal(event.deltaX, event.deltaY, false, true);
|
|
3041
|
+
this._isUserControllingTruck = true;
|
|
3042
|
+
} else if (wheelAction === ACTION.OFFSET) {
|
|
3043
|
+
this._truckInternal(event.deltaX, event.deltaY, true, false);
|
|
3044
|
+
this._isUserControllingOffset = true;
|
|
3045
|
+
} else if (wheelAction === ACTION.DOLLY) {
|
|
3046
|
+
const truckOverride = this._dollyInternal(-delta, x, y);
|
|
3047
|
+
if (!truckOverride) {
|
|
3048
|
+
this._isUserControllingDolly = true;
|
|
3049
|
+
this._isWheelControllingDolly = true;
|
|
3050
|
+
} else {
|
|
3051
|
+
this._isUserControllingTruck = true;
|
|
3052
|
+
}
|
|
3053
|
+
} else if (wheelAction === ACTION.ZOOM) {
|
|
3054
|
+
this._zoomInternal(-delta, x, y);
|
|
3055
|
+
this._isUserControllingZoom = true;
|
|
3056
|
+
this._isWheelControllingZoom = true;
|
|
3057
|
+
}
|
|
3058
|
+
this.dispatchEvent({ type: "control" });
|
|
3059
|
+
};
|
|
3060
|
+
const onContextMenu = (event) => {
|
|
3061
|
+
if (!this._domElement || !this._enabled)
|
|
3062
|
+
return;
|
|
3063
|
+
if (this.mouseButtons.right === CameraControls.ACTION.NONE) {
|
|
3064
|
+
const pointerId = event instanceof PointerEvent ? event.pointerId : 0;
|
|
3065
|
+
const pointer = this._findPointerById(pointerId);
|
|
3066
|
+
pointer && this._disposePointer(pointer);
|
|
3067
|
+
this._domElement.ownerDocument.removeEventListener("pointermove", onPointerMove, { passive: false });
|
|
3068
|
+
this._domElement.ownerDocument.removeEventListener("pointerup", onPointerUp);
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
event.preventDefault();
|
|
3072
|
+
};
|
|
3073
|
+
const startDragging = (event) => {
|
|
3074
|
+
if (!this._enabled)
|
|
3075
|
+
return;
|
|
3076
|
+
extractClientCoordFromEvent(this._activePointers, _v2);
|
|
3077
|
+
this._getClientRect(this._elementRect);
|
|
3078
|
+
dragStartPosition.copy(_v2);
|
|
3079
|
+
lastDragPosition.copy(_v2);
|
|
3080
|
+
const isMultiTouch = this._activePointers.length >= 2;
|
|
3081
|
+
if (isMultiTouch) {
|
|
3082
|
+
const dx = _v2.x - this._activePointers[1].clientX;
|
|
3083
|
+
const dy = _v2.y - this._activePointers[1].clientY;
|
|
3084
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3085
|
+
dollyStart.set(0, distance);
|
|
3086
|
+
const x = (this._activePointers[0].clientX + this._activePointers[1].clientX) * 0.5;
|
|
3087
|
+
const y = (this._activePointers[0].clientY + this._activePointers[1].clientY) * 0.5;
|
|
3088
|
+
lastDragPosition.set(x, y);
|
|
3089
|
+
}
|
|
3090
|
+
this._state = 0;
|
|
3091
|
+
if (!event) {
|
|
3092
|
+
if (this._lockedPointer)
|
|
3093
|
+
this._state = this._state | this.mouseButtons.left;
|
|
3094
|
+
} else if ("pointerType" in event && event.pointerType === "touch") {
|
|
3095
|
+
switch (this._activePointers.length) {
|
|
3096
|
+
case 1:
|
|
3097
|
+
this._state = this.touches.one;
|
|
3098
|
+
break;
|
|
3099
|
+
case 2:
|
|
3100
|
+
this._state = this.touches.two;
|
|
3101
|
+
break;
|
|
3102
|
+
case 3:
|
|
3103
|
+
this._state = this.touches.three;
|
|
3104
|
+
break;
|
|
3105
|
+
}
|
|
3106
|
+
} else {
|
|
3107
|
+
if (!this._lockedPointer && (event.buttons & MOUSE_BUTTON.LEFT) === MOUSE_BUTTON.LEFT) {
|
|
3108
|
+
this._state = this._state | this.mouseButtons.left;
|
|
3109
|
+
}
|
|
3110
|
+
if ((event.buttons & MOUSE_BUTTON.MIDDLE) === MOUSE_BUTTON.MIDDLE) {
|
|
3111
|
+
this._state = this._state | this.mouseButtons.middle;
|
|
3112
|
+
}
|
|
3113
|
+
if ((event.buttons & MOUSE_BUTTON.RIGHT) === MOUSE_BUTTON.RIGHT) {
|
|
3114
|
+
this._state = this._state | this.mouseButtons.right;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
if ((this._state & (ACTION.ROTATE | ACTION.ROTATE_POLAR | ACTION.ROTATE_AZIMUTH)) !== ACTION.NONE || (this._state & ACTION.TOUCH_ROTATE) === ACTION.TOUCH_ROTATE || (this._state & ACTION.TOUCH_DOLLY_ROTATE) === ACTION.TOUCH_DOLLY_ROTATE || (this._state & ACTION.TOUCH_ZOOM_ROTATE) === ACTION.TOUCH_ZOOM_ROTATE) {
|
|
3118
|
+
const hasRotate = (this._state & ACTION.ROTATE) === ACTION.ROTATE;
|
|
3119
|
+
const hasRotatePolar = (this._state & ACTION.ROTATE_POLAR) === ACTION.ROTATE_POLAR;
|
|
3120
|
+
const hasRotateAzimuth = (this._state & ACTION.ROTATE_AZIMUTH) === ACTION.ROTATE_AZIMUTH;
|
|
3121
|
+
const hasTouchRotate = (this._state & ACTION.TOUCH_ROTATE) === ACTION.TOUCH_ROTATE || (this._state & ACTION.TOUCH_DOLLY_ROTATE) === ACTION.TOUCH_DOLLY_ROTATE || (this._state & ACTION.TOUCH_ZOOM_ROTATE) === ACTION.TOUCH_ZOOM_ROTATE;
|
|
3122
|
+
if (hasRotate || hasRotateAzimuth || hasTouchRotate) {
|
|
3123
|
+
this._sphericalEnd.theta = this._spherical.theta;
|
|
3124
|
+
this._thetaVelocity.value = 0;
|
|
3125
|
+
}
|
|
3126
|
+
if (hasRotate || hasRotatePolar || hasTouchRotate) {
|
|
3127
|
+
this._sphericalEnd.phi = this._spherical.phi;
|
|
3128
|
+
this._phiVelocity.value = 0;
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
if ((this._state & ACTION.TRUCK) === ACTION.TRUCK || (this._state & ACTION.SCREEN_PAN) === ACTION.SCREEN_PAN || (this._state & ACTION.TOUCH_TRUCK) === ACTION.TOUCH_TRUCK || (this._state & ACTION.TOUCH_SCREEN_PAN) === ACTION.TOUCH_SCREEN_PAN || (this._state & ACTION.TOUCH_DOLLY_TRUCK) === ACTION.TOUCH_DOLLY_TRUCK || (this._state & ACTION.TOUCH_DOLLY_SCREEN_PAN) === ACTION.TOUCH_DOLLY_SCREEN_PAN || (this._state & ACTION.TOUCH_ZOOM_TRUCK) === ACTION.TOUCH_ZOOM_TRUCK || (this._state & ACTION.TOUCH_ZOOM_SCREEN_PAN) === ACTION.TOUCH_DOLLY_SCREEN_PAN) {
|
|
3132
|
+
this._targetEnd.copy(this._target);
|
|
3133
|
+
this._targetVelocity.set(0, 0, 0);
|
|
3134
|
+
}
|
|
3135
|
+
if ((this._state & ACTION.DOLLY) === ACTION.DOLLY || (this._state & ACTION.TOUCH_DOLLY) === ACTION.TOUCH_DOLLY || (this._state & ACTION.TOUCH_DOLLY_TRUCK) === ACTION.TOUCH_DOLLY_TRUCK || (this._state & ACTION.TOUCH_DOLLY_SCREEN_PAN) === ACTION.TOUCH_DOLLY_SCREEN_PAN || (this._state & ACTION.TOUCH_DOLLY_OFFSET) === ACTION.TOUCH_DOLLY_OFFSET || (this._state & ACTION.TOUCH_DOLLY_ROTATE) === ACTION.TOUCH_DOLLY_ROTATE) {
|
|
3136
|
+
this._sphericalEnd.radius = this._spherical.radius;
|
|
3137
|
+
this._radiusVelocity.value = 0;
|
|
3138
|
+
}
|
|
3139
|
+
if ((this._state & ACTION.ZOOM) === ACTION.ZOOM || (this._state & ACTION.TOUCH_ZOOM) === ACTION.TOUCH_ZOOM || (this._state & ACTION.TOUCH_ZOOM_TRUCK) === ACTION.TOUCH_ZOOM_TRUCK || (this._state & ACTION.TOUCH_ZOOM_SCREEN_PAN) === ACTION.TOUCH_ZOOM_SCREEN_PAN || (this._state & ACTION.TOUCH_ZOOM_OFFSET) === ACTION.TOUCH_ZOOM_OFFSET || (this._state & ACTION.TOUCH_ZOOM_ROTATE) === ACTION.TOUCH_ZOOM_ROTATE) {
|
|
3140
|
+
this._zoomEnd = this._zoom;
|
|
3141
|
+
this._zoomVelocity.value = 0;
|
|
3142
|
+
}
|
|
3143
|
+
if ((this._state & ACTION.OFFSET) === ACTION.OFFSET || (this._state & ACTION.TOUCH_OFFSET) === ACTION.TOUCH_OFFSET || (this._state & ACTION.TOUCH_DOLLY_OFFSET) === ACTION.TOUCH_DOLLY_OFFSET || (this._state & ACTION.TOUCH_ZOOM_OFFSET) === ACTION.TOUCH_ZOOM_OFFSET) {
|
|
3144
|
+
this._focalOffsetEnd.copy(this._focalOffset);
|
|
3145
|
+
this._focalOffsetVelocity.set(0, 0, 0);
|
|
3146
|
+
}
|
|
3147
|
+
this.dispatchEvent({ type: "controlstart" });
|
|
3148
|
+
};
|
|
3149
|
+
const dragging = () => {
|
|
3150
|
+
if (!this._enabled || !this._dragNeedsUpdate)
|
|
3151
|
+
return;
|
|
3152
|
+
this._dragNeedsUpdate = false;
|
|
3153
|
+
extractClientCoordFromEvent(this._activePointers, _v2);
|
|
3154
|
+
const isPointerLockActive = this._domElement && this._domElement.ownerDocument.pointerLockElement === this._domElement;
|
|
3155
|
+
const lockedPointer = isPointerLockActive ? this._lockedPointer || this._activePointers[0] : null;
|
|
3156
|
+
const deltaX = lockedPointer ? -lockedPointer.deltaX : lastDragPosition.x - _v2.x;
|
|
3157
|
+
const deltaY = lockedPointer ? -lockedPointer.deltaY : lastDragPosition.y - _v2.y;
|
|
3158
|
+
lastDragPosition.copy(_v2);
|
|
3159
|
+
if ((this._state & (ACTION.ROTATE | ACTION.ROTATE_POLAR | ACTION.ROTATE_AZIMUTH)) !== ACTION.NONE || (this._state & ACTION.TOUCH_ROTATE) === ACTION.TOUCH_ROTATE || (this._state & ACTION.TOUCH_DOLLY_ROTATE) === ACTION.TOUCH_DOLLY_ROTATE || (this._state & ACTION.TOUCH_ZOOM_ROTATE) === ACTION.TOUCH_ZOOM_ROTATE) {
|
|
3160
|
+
this._rotateInternal(deltaX, deltaY);
|
|
3161
|
+
this._isUserControllingRotate = true;
|
|
3162
|
+
}
|
|
3163
|
+
if ((this._state & ACTION.DOLLY) === ACTION.DOLLY || (this._state & ACTION.ZOOM) === ACTION.ZOOM) {
|
|
3164
|
+
const dollyX = this.dollyToCursor ? (dragStartPosition.x - this._elementRect.x) / this._elementRect.width * 2 - 1 : 0;
|
|
3165
|
+
const dollyY = this.dollyToCursor ? (dragStartPosition.y - this._elementRect.y) / this._elementRect.height * -2 + 1 : 0;
|
|
3166
|
+
const dollyDirection = this.dollyDragInverted ? -1 : 1;
|
|
3167
|
+
if ((this._state & ACTION.DOLLY) === ACTION.DOLLY) {
|
|
3168
|
+
const truckOverride = this._dollyInternal(dollyDirection * deltaY * TOUCH_DOLLY_FACTOR, dollyX, dollyY);
|
|
3169
|
+
if (!truckOverride) {
|
|
3170
|
+
this._isUserControllingDolly = true;
|
|
3171
|
+
} else {
|
|
3172
|
+
this._isUserControllingTruck = true;
|
|
3173
|
+
}
|
|
3174
|
+
} else {
|
|
3175
|
+
this._zoomInternal(dollyDirection * deltaY * TOUCH_DOLLY_FACTOR, dollyX, dollyY);
|
|
3176
|
+
this._isUserControllingZoom = true;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
if ((this._state & ACTION.TOUCH_DOLLY) === ACTION.TOUCH_DOLLY || (this._state & ACTION.TOUCH_ZOOM) === ACTION.TOUCH_ZOOM || (this._state & ACTION.TOUCH_DOLLY_TRUCK) === ACTION.TOUCH_DOLLY_TRUCK || (this._state & ACTION.TOUCH_ZOOM_TRUCK) === ACTION.TOUCH_ZOOM_TRUCK || (this._state & ACTION.TOUCH_DOLLY_SCREEN_PAN) === ACTION.TOUCH_DOLLY_SCREEN_PAN || (this._state & ACTION.TOUCH_ZOOM_SCREEN_PAN) === ACTION.TOUCH_ZOOM_SCREEN_PAN || (this._state & ACTION.TOUCH_DOLLY_OFFSET) === ACTION.TOUCH_DOLLY_OFFSET || (this._state & ACTION.TOUCH_ZOOM_OFFSET) === ACTION.TOUCH_ZOOM_OFFSET || (this._state & ACTION.TOUCH_DOLLY_ROTATE) === ACTION.TOUCH_DOLLY_ROTATE || (this._state & ACTION.TOUCH_ZOOM_ROTATE) === ACTION.TOUCH_ZOOM_ROTATE) {
|
|
3180
|
+
const dx = _v2.x - this._activePointers[1].clientX;
|
|
3181
|
+
const dy = _v2.y - this._activePointers[1].clientY;
|
|
3182
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3183
|
+
const dollyDelta = dollyStart.y - distance;
|
|
3184
|
+
dollyStart.set(0, distance);
|
|
3185
|
+
const dollyX = this.dollyToCursor ? (lastDragPosition.x - this._elementRect.x) / this._elementRect.width * 2 - 1 : 0;
|
|
3186
|
+
const dollyY = this.dollyToCursor ? (lastDragPosition.y - this._elementRect.y) / this._elementRect.height * -2 + 1 : 0;
|
|
3187
|
+
if ((this._state & ACTION.TOUCH_DOLLY) === ACTION.TOUCH_DOLLY || (this._state & ACTION.TOUCH_DOLLY_ROTATE) === ACTION.TOUCH_DOLLY_ROTATE || (this._state & ACTION.TOUCH_DOLLY_TRUCK) === ACTION.TOUCH_DOLLY_TRUCK || (this._state & ACTION.TOUCH_DOLLY_SCREEN_PAN) === ACTION.TOUCH_DOLLY_SCREEN_PAN || (this._state & ACTION.TOUCH_DOLLY_OFFSET) === ACTION.TOUCH_DOLLY_OFFSET) {
|
|
3188
|
+
const truckOverride = this._dollyInternal(dollyDelta * TOUCH_DOLLY_FACTOR, dollyX, dollyY);
|
|
3189
|
+
if (!truckOverride) {
|
|
3190
|
+
this._isUserControllingDolly = true;
|
|
3191
|
+
} else {
|
|
3192
|
+
this._isUserControllingTruck = true;
|
|
3193
|
+
}
|
|
3194
|
+
} else {
|
|
3195
|
+
this._zoomInternal(dollyDelta * TOUCH_DOLLY_FACTOR, dollyX, dollyY);
|
|
3196
|
+
this._isUserControllingZoom = true;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
if ((this._state & ACTION.TRUCK) === ACTION.TRUCK || (this._state & ACTION.TOUCH_TRUCK) === ACTION.TOUCH_TRUCK || (this._state & ACTION.TOUCH_DOLLY_TRUCK) === ACTION.TOUCH_DOLLY_TRUCK || (this._state & ACTION.TOUCH_ZOOM_TRUCK) === ACTION.TOUCH_ZOOM_TRUCK) {
|
|
3200
|
+
this._truckInternal(deltaX, deltaY, false, false);
|
|
3201
|
+
this._isUserControllingTruck = true;
|
|
3202
|
+
}
|
|
3203
|
+
if ((this._state & ACTION.SCREEN_PAN) === ACTION.SCREEN_PAN || (this._state & ACTION.TOUCH_SCREEN_PAN) === ACTION.TOUCH_SCREEN_PAN || (this._state & ACTION.TOUCH_DOLLY_SCREEN_PAN) === ACTION.TOUCH_DOLLY_SCREEN_PAN || (this._state & ACTION.TOUCH_ZOOM_SCREEN_PAN) === ACTION.TOUCH_ZOOM_SCREEN_PAN) {
|
|
3204
|
+
this._truckInternal(deltaX, deltaY, false, true);
|
|
3205
|
+
this._isUserControllingTruck = true;
|
|
3206
|
+
}
|
|
3207
|
+
if ((this._state & ACTION.OFFSET) === ACTION.OFFSET || (this._state & ACTION.TOUCH_OFFSET) === ACTION.TOUCH_OFFSET || (this._state & ACTION.TOUCH_DOLLY_OFFSET) === ACTION.TOUCH_DOLLY_OFFSET || (this._state & ACTION.TOUCH_ZOOM_OFFSET) === ACTION.TOUCH_ZOOM_OFFSET) {
|
|
3208
|
+
this._truckInternal(deltaX, deltaY, true, false);
|
|
3209
|
+
this._isUserControllingOffset = true;
|
|
3210
|
+
}
|
|
3211
|
+
this.dispatchEvent({ type: "control" });
|
|
3212
|
+
};
|
|
3213
|
+
const endDragging = () => {
|
|
3214
|
+
extractClientCoordFromEvent(this._activePointers, _v2);
|
|
3215
|
+
lastDragPosition.copy(_v2);
|
|
3216
|
+
this._dragNeedsUpdate = false;
|
|
3217
|
+
if (this._activePointers.length === 0 || this._activePointers.length === 1 && this._activePointers[0] === this._lockedPointer) {
|
|
3218
|
+
this._isDragging = false;
|
|
3219
|
+
}
|
|
3220
|
+
if (this._activePointers.length === 0 && this._domElement) {
|
|
3221
|
+
this._domElement.ownerDocument.removeEventListener("pointermove", onPointerMove, { passive: false });
|
|
3222
|
+
this._domElement.ownerDocument.removeEventListener("pointerup", onPointerUp);
|
|
3223
|
+
this.dispatchEvent({ type: "controlend" });
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
this.lockPointer = () => {
|
|
3227
|
+
if (!this._enabled || !this._domElement)
|
|
3228
|
+
return;
|
|
3229
|
+
this.cancel();
|
|
3230
|
+
this._lockedPointer = {
|
|
3231
|
+
pointerId: -1,
|
|
3232
|
+
clientX: 0,
|
|
3233
|
+
clientY: 0,
|
|
3234
|
+
deltaX: 0,
|
|
3235
|
+
deltaY: 0,
|
|
3236
|
+
mouseButton: null
|
|
3237
|
+
};
|
|
3238
|
+
this._activePointers.push(this._lockedPointer);
|
|
3239
|
+
this._domElement.ownerDocument.removeEventListener("pointermove", onPointerMove, { passive: false });
|
|
3240
|
+
this._domElement.ownerDocument.removeEventListener("pointerup", onPointerUp);
|
|
3241
|
+
this._domElement.requestPointerLock();
|
|
3242
|
+
this._domElement.ownerDocument.addEventListener("pointerlockchange", onPointerLockChange);
|
|
3243
|
+
this._domElement.ownerDocument.addEventListener("pointerlockerror", onPointerLockError);
|
|
3244
|
+
this._domElement.ownerDocument.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
3245
|
+
this._domElement.ownerDocument.addEventListener("pointerup", onPointerUp);
|
|
3246
|
+
startDragging();
|
|
3247
|
+
};
|
|
3248
|
+
this.unlockPointer = () => {
|
|
3249
|
+
var _a2, _b, _c;
|
|
3250
|
+
if (this._lockedPointer !== null) {
|
|
3251
|
+
this._disposePointer(this._lockedPointer);
|
|
3252
|
+
this._lockedPointer = null;
|
|
3253
|
+
}
|
|
3254
|
+
(_a2 = this._domElement) == null ? void 0 : _a2.ownerDocument.exitPointerLock();
|
|
3255
|
+
(_b = this._domElement) == null ? void 0 : _b.ownerDocument.removeEventListener("pointerlockchange", onPointerLockChange);
|
|
3256
|
+
(_c = this._domElement) == null ? void 0 : _c.ownerDocument.removeEventListener("pointerlockerror", onPointerLockError);
|
|
3257
|
+
this.cancel();
|
|
3258
|
+
};
|
|
3259
|
+
const onPointerLockChange = () => {
|
|
3260
|
+
const isPointerLockActive = this._domElement && this._domElement.ownerDocument.pointerLockElement === this._domElement;
|
|
3261
|
+
if (!isPointerLockActive)
|
|
3262
|
+
this.unlockPointer();
|
|
3263
|
+
};
|
|
3264
|
+
const onPointerLockError = () => {
|
|
3265
|
+
this.unlockPointer();
|
|
3266
|
+
};
|
|
3267
|
+
this._addAllEventListeners = (domElement2) => {
|
|
3268
|
+
this._domElement = domElement2;
|
|
3269
|
+
this._domElement.style.touchAction = "none";
|
|
3270
|
+
this._domElement.style.userSelect = "none";
|
|
3271
|
+
this._domElement.style.webkitUserSelect = "none";
|
|
3272
|
+
this._domElement.addEventListener("pointerdown", onPointerDown);
|
|
3273
|
+
this._domElement.addEventListener("pointercancel", onPointerUp);
|
|
3274
|
+
this._domElement.addEventListener("wheel", onMouseWheel, { passive: false });
|
|
3275
|
+
this._domElement.addEventListener("contextmenu", onContextMenu);
|
|
3276
|
+
};
|
|
3277
|
+
this._removeAllEventListeners = () => {
|
|
3278
|
+
if (!this._domElement)
|
|
3279
|
+
return;
|
|
3280
|
+
this._domElement.style.touchAction = "";
|
|
3281
|
+
this._domElement.style.userSelect = "";
|
|
3282
|
+
this._domElement.style.webkitUserSelect = "";
|
|
3283
|
+
this._domElement.removeEventListener("pointerdown", onPointerDown);
|
|
3284
|
+
this._domElement.removeEventListener("pointercancel", onPointerUp);
|
|
3285
|
+
this._domElement.removeEventListener("wheel", onMouseWheel, { passive: false });
|
|
3286
|
+
this._domElement.removeEventListener("contextmenu", onContextMenu);
|
|
3287
|
+
this._domElement.ownerDocument.removeEventListener("pointermove", onPointerMove, { passive: false });
|
|
3288
|
+
this._domElement.ownerDocument.removeEventListener("pointerup", onPointerUp);
|
|
3289
|
+
this._domElement.ownerDocument.removeEventListener("pointerlockchange", onPointerLockChange);
|
|
3290
|
+
this._domElement.ownerDocument.removeEventListener("pointerlockerror", onPointerLockError);
|
|
3291
|
+
};
|
|
3292
|
+
this.cancel = () => {
|
|
3293
|
+
if (this._state === ACTION.NONE)
|
|
3294
|
+
return;
|
|
3295
|
+
this._state = ACTION.NONE;
|
|
3296
|
+
this._activePointers.length = 0;
|
|
3297
|
+
endDragging();
|
|
3298
|
+
};
|
|
3299
|
+
if (domElement)
|
|
3300
|
+
this.connect(domElement);
|
|
3301
|
+
this.update(0);
|
|
3302
|
+
}
|
|
3303
|
+
/**
|
|
3304
|
+
* Injects THREE as the dependency. You can then proceed to use CameraControls.
|
|
3305
|
+
*
|
|
3306
|
+
* e.g
|
|
3307
|
+
* ```javascript
|
|
3308
|
+
* CameraControls.install( { THREE: THREE } );
|
|
3309
|
+
* ```
|
|
3310
|
+
*
|
|
3311
|
+
* Note: If you do not wish to use enter three.js to reduce file size(tree-shaking for example), make a subset to install.
|
|
3312
|
+
*
|
|
3313
|
+
* ```js
|
|
3314
|
+
* import {
|
|
3315
|
+
* Vector2,
|
|
3316
|
+
* Vector3,
|
|
3317
|
+
* Vector4,
|
|
3318
|
+
* Quaternion,
|
|
3319
|
+
* Matrix4,
|
|
3320
|
+
* Spherical,
|
|
3321
|
+
* Box3,
|
|
3322
|
+
* Sphere,
|
|
3323
|
+
* Raycaster,
|
|
3324
|
+
* MathUtils,
|
|
3325
|
+
* } from 'three';
|
|
3326
|
+
*
|
|
3327
|
+
* const subsetOfTHREE = {
|
|
3328
|
+
* Vector2 : Vector2,
|
|
3329
|
+
* Vector3 : Vector3,
|
|
3330
|
+
* Vector4 : Vector4,
|
|
3331
|
+
* Quaternion: Quaternion,
|
|
3332
|
+
* Matrix4 : Matrix4,
|
|
3333
|
+
* Spherical : Spherical,
|
|
3334
|
+
* Box3 : Box3,
|
|
3335
|
+
* Sphere : Sphere,
|
|
3336
|
+
* Raycaster : Raycaster,
|
|
3337
|
+
* };
|
|
3338
|
+
|
|
3339
|
+
* CameraControls.install( { THREE: subsetOfTHREE } );
|
|
3340
|
+
* ```
|
|
3341
|
+
* @category Statics
|
|
3342
|
+
*/
|
|
3343
|
+
static install(libs) {
|
|
3344
|
+
THREE = libs.THREE;
|
|
3345
|
+
_ORIGIN = Object.freeze(new THREE.Vector3(0, 0, 0));
|
|
3346
|
+
_AXIS_Y = Object.freeze(new THREE.Vector3(0, 1, 0));
|
|
3347
|
+
_AXIS_Z = Object.freeze(new THREE.Vector3(0, 0, 1));
|
|
3348
|
+
_PLANE_XY = Object.freeze(new THREE.Plane(_AXIS_Z, 0));
|
|
3349
|
+
_v2 = new THREE.Vector2();
|
|
3350
|
+
_v3A = new THREE.Vector3();
|
|
3351
|
+
_v3B = new THREE.Vector3();
|
|
3352
|
+
_v3C = new THREE.Vector3();
|
|
3353
|
+
_cameraDirection = new THREE.Vector3();
|
|
3354
|
+
_xColumn = new THREE.Vector3();
|
|
3355
|
+
_yColumn = new THREE.Vector3();
|
|
3356
|
+
_zColumn = new THREE.Vector3();
|
|
3357
|
+
_deltaTarget = new THREE.Vector3();
|
|
3358
|
+
_deltaOffset = new THREE.Vector3();
|
|
3359
|
+
_sphericalA = new THREE.Spherical();
|
|
3360
|
+
_sphericalB = new THREE.Spherical();
|
|
3361
|
+
_box3A = new THREE.Box3();
|
|
3362
|
+
_box3B = new THREE.Box3();
|
|
3363
|
+
_sphere = new THREE.Sphere();
|
|
3364
|
+
_quaternionA = new THREE.Quaternion();
|
|
3365
|
+
_quaternionB = new THREE.Quaternion();
|
|
3366
|
+
_rotationMatrix = new THREE.Matrix4();
|
|
3367
|
+
_raycaster = new THREE.Raycaster();
|
|
3368
|
+
}
|
|
3369
|
+
/**
|
|
3370
|
+
* list all ACTIONs
|
|
3371
|
+
* @category Statics
|
|
3372
|
+
*/
|
|
3373
|
+
static get ACTION() {
|
|
3374
|
+
return ACTION;
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* @deprecated Use `cameraControls.mouseButtons.left = CameraControls.ACTION.SCREEN_PAN` instead.
|
|
3378
|
+
*/
|
|
3379
|
+
set verticalDragToForward(_) {
|
|
3380
|
+
console.warn("camera-controls: `verticalDragToForward` was removed. Use `mouseButtons.left = CameraControls.ACTION.SCREEN_PAN` instead.");
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* The camera to be controlled
|
|
3384
|
+
* @category Properties
|
|
3385
|
+
*/
|
|
3386
|
+
get camera() {
|
|
3387
|
+
return this._camera;
|
|
3388
|
+
}
|
|
3389
|
+
set camera(camera) {
|
|
3390
|
+
this._camera = camera;
|
|
3391
|
+
this.updateCameraUp();
|
|
3392
|
+
this._camera.updateProjectionMatrix();
|
|
3393
|
+
this._updateNearPlaneCorners();
|
|
3394
|
+
this._needsUpdate = true;
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Whether or not the controls are enabled.
|
|
3398
|
+
* `false` to disable user dragging/touch-move, but all methods works.
|
|
3399
|
+
* @category Properties
|
|
3400
|
+
*/
|
|
3401
|
+
get enabled() {
|
|
3402
|
+
return this._enabled;
|
|
3403
|
+
}
|
|
3404
|
+
set enabled(enabled) {
|
|
3405
|
+
this._enabled = enabled;
|
|
3406
|
+
if (!this._domElement)
|
|
3407
|
+
return;
|
|
3408
|
+
if (enabled) {
|
|
3409
|
+
this._domElement.style.touchAction = "none";
|
|
3410
|
+
this._domElement.style.userSelect = "none";
|
|
3411
|
+
this._domElement.style.webkitUserSelect = "none";
|
|
3412
|
+
} else {
|
|
3413
|
+
this.cancel();
|
|
3414
|
+
this._domElement.style.touchAction = "";
|
|
3415
|
+
this._domElement.style.userSelect = "";
|
|
3416
|
+
this._domElement.style.webkitUserSelect = "";
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Returns `true` if the controls are active updating.
|
|
3421
|
+
* readonly value.
|
|
3422
|
+
* @category Properties
|
|
3423
|
+
*/
|
|
3424
|
+
get active() {
|
|
3425
|
+
return !this._hasRested;
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Getter for the current `ACTION`.
|
|
3429
|
+
* readonly value.
|
|
3430
|
+
* @category Properties
|
|
3431
|
+
*/
|
|
3432
|
+
get currentAction() {
|
|
3433
|
+
return this._state;
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3436
|
+
* get/set Current distance.
|
|
3437
|
+
* @category Properties
|
|
3438
|
+
*/
|
|
3439
|
+
get distance() {
|
|
3440
|
+
return this._spherical.radius;
|
|
3441
|
+
}
|
|
3442
|
+
set distance(distance) {
|
|
3443
|
+
if (this._spherical.radius === distance && this._sphericalEnd.radius === distance)
|
|
3444
|
+
return;
|
|
3445
|
+
this._spherical.radius = distance;
|
|
3446
|
+
this._sphericalEnd.radius = distance;
|
|
3447
|
+
this._needsUpdate = true;
|
|
3448
|
+
}
|
|
3449
|
+
// horizontal angle
|
|
3450
|
+
/**
|
|
3451
|
+
* get/set the azimuth angle (horizontal) in radians.
|
|
3452
|
+
* Every 360 degrees turn is added to `.azimuthAngle` value, which is accumulative.
|
|
3453
|
+
* @category Properties
|
|
3454
|
+
*/
|
|
3455
|
+
get azimuthAngle() {
|
|
3456
|
+
return this._spherical.theta;
|
|
3457
|
+
}
|
|
3458
|
+
set azimuthAngle(azimuthAngle) {
|
|
3459
|
+
if (this._spherical.theta === azimuthAngle && this._sphericalEnd.theta === azimuthAngle)
|
|
3460
|
+
return;
|
|
3461
|
+
this._spherical.theta = azimuthAngle;
|
|
3462
|
+
this._sphericalEnd.theta = azimuthAngle;
|
|
3463
|
+
this._needsUpdate = true;
|
|
3464
|
+
}
|
|
3465
|
+
// vertical angle
|
|
3466
|
+
/**
|
|
3467
|
+
* get/set the polar angle (vertical) in radians.
|
|
3468
|
+
* @category Properties
|
|
3469
|
+
*/
|
|
3470
|
+
get polarAngle() {
|
|
3471
|
+
return this._spherical.phi;
|
|
3472
|
+
}
|
|
3473
|
+
set polarAngle(polarAngle) {
|
|
3474
|
+
if (this._spherical.phi === polarAngle && this._sphericalEnd.phi === polarAngle)
|
|
3475
|
+
return;
|
|
3476
|
+
this._spherical.phi = polarAngle;
|
|
3477
|
+
this._sphericalEnd.phi = polarAngle;
|
|
3478
|
+
this._needsUpdate = true;
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Whether camera position should be enclosed in the boundary or not.
|
|
3482
|
+
* @category Properties
|
|
3483
|
+
*/
|
|
3484
|
+
get boundaryEnclosesCamera() {
|
|
3485
|
+
return this._boundaryEnclosesCamera;
|
|
3486
|
+
}
|
|
3487
|
+
set boundaryEnclosesCamera(boundaryEnclosesCamera) {
|
|
3488
|
+
this._boundaryEnclosesCamera = boundaryEnclosesCamera;
|
|
3489
|
+
this._needsUpdate = true;
|
|
3490
|
+
}
|
|
3491
|
+
/**
|
|
3492
|
+
* Set drag-start, touches and wheel enable area in the domElement.
|
|
3493
|
+
* each values are between `0` and `1` inclusive, where `0` is left/top and `1` is right/bottom of the screen.
|
|
3494
|
+
* e.g. `{ x: 0, y: 0, width: 1, height: 1 }` for entire area.
|
|
3495
|
+
* @category Properties
|
|
3496
|
+
*/
|
|
3497
|
+
set interactiveArea(interactiveArea) {
|
|
3498
|
+
this._interactiveArea.width = clamp(interactiveArea.width, 0, 1);
|
|
3499
|
+
this._interactiveArea.height = clamp(interactiveArea.height, 0, 1);
|
|
3500
|
+
this._interactiveArea.x = clamp(interactiveArea.x, 0, 1 - this._interactiveArea.width);
|
|
3501
|
+
this._interactiveArea.y = clamp(interactiveArea.y, 0, 1 - this._interactiveArea.height);
|
|
3502
|
+
}
|
|
3503
|
+
/**
|
|
3504
|
+
* Adds the specified event listener.
|
|
3505
|
+
* Applicable event types (which is `K`) are:
|
|
3506
|
+
* | Event name | Timing |
|
|
3507
|
+
* | ------------------- | ------ |
|
|
3508
|
+
* | `'controlstart'` | When the user starts to control the camera via mouse / touches. ¹ |
|
|
3509
|
+
* | `'control'` | When the user controls the camera (dragging). |
|
|
3510
|
+
* | `'controlend'` | When the user ends to control the camera. ¹ |
|
|
3511
|
+
* | `'transitionstart'` | When any kind of transition starts, either user control or using a method with `enableTransition = true` |
|
|
3512
|
+
* | `'update'` | When the camera position is updated. |
|
|
3513
|
+
* | `'wake'` | When the camera starts moving. |
|
|
3514
|
+
* | `'rest'` | When the camera movement is below `.restThreshold` ². |
|
|
3515
|
+
* | `'sleep'` | When the camera end moving. |
|
|
3516
|
+
*
|
|
3517
|
+
* 1. `mouseButtons.wheel` (Mouse wheel control) does not emit `'controlstart'` and `'controlend'`. `mouseButtons.wheel` uses scroll-event internally, and scroll-event happens intermittently. That means "start" and "end" cannot be detected.
|
|
3518
|
+
* 2. Due to damping, `sleep` will usually fire a few seconds after the camera _appears_ to have stopped moving. If you want to do something (e.g. enable UI, perform another transition) at the point when the camera has stopped, you probably want the `rest` event. This can be fine tuned using the `.restThreshold` parameter. See the [Rest and Sleep Example](https://yomotsu.github.io/camera-controls/examples/rest-and-sleep.html).
|
|
3519
|
+
*
|
|
3520
|
+
* e.g.
|
|
3521
|
+
* ```
|
|
3522
|
+
* cameraControl.addEventListener( 'controlstart', myCallbackFunction );
|
|
3523
|
+
* ```
|
|
3524
|
+
* @param type event name
|
|
3525
|
+
* @param listener handler function
|
|
3526
|
+
* @category Methods
|
|
3527
|
+
*/
|
|
3528
|
+
addEventListener(type, listener) {
|
|
3529
|
+
super.addEventListener(type, listener);
|
|
3530
|
+
}
|
|
3531
|
+
/**
|
|
3532
|
+
* Removes the specified event listener
|
|
3533
|
+
* e.g.
|
|
3534
|
+
* ```
|
|
3535
|
+
* cameraControl.addEventListener( 'controlstart', myCallbackFunction );
|
|
3536
|
+
* ```
|
|
3537
|
+
* @param type event name
|
|
3538
|
+
* @param listener handler function
|
|
3539
|
+
* @category Methods
|
|
3540
|
+
*/
|
|
3541
|
+
removeEventListener(type, listener) {
|
|
3542
|
+
super.removeEventListener(type, listener);
|
|
3543
|
+
}
|
|
3544
|
+
/**
|
|
3545
|
+
* Rotate azimuthal angle(horizontal) and polar angle(vertical).
|
|
3546
|
+
* Every value is added to the current value.
|
|
3547
|
+
* @param azimuthAngle Azimuth rotate angle. In radian.
|
|
3548
|
+
* @param polarAngle Polar rotate angle. In radian.
|
|
3549
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3550
|
+
* @category Methods
|
|
3551
|
+
*/
|
|
3552
|
+
rotate(azimuthAngle, polarAngle, enableTransition = false) {
|
|
3553
|
+
return this.rotateTo(this._sphericalEnd.theta + azimuthAngle, this._sphericalEnd.phi + polarAngle, enableTransition);
|
|
3554
|
+
}
|
|
3555
|
+
/**
|
|
3556
|
+
* Rotate azimuthal angle(horizontal) to the given angle and keep the same polar angle(vertical) target.
|
|
3557
|
+
*
|
|
3558
|
+
* e.g.
|
|
3559
|
+
* ```
|
|
3560
|
+
* cameraControls.rotateAzimuthTo( 30 * THREE.MathUtils.DEG2RAD, true );
|
|
3561
|
+
* ```
|
|
3562
|
+
* @param azimuthAngle Azimuth rotate angle. In radian.
|
|
3563
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3564
|
+
* @category Methods
|
|
3565
|
+
*/
|
|
3566
|
+
rotateAzimuthTo(azimuthAngle, enableTransition = false) {
|
|
3567
|
+
return this.rotateTo(azimuthAngle, this._sphericalEnd.phi, enableTransition);
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Rotate polar angle(vertical) to the given angle and keep the same azimuthal angle(horizontal) target.
|
|
3571
|
+
*
|
|
3572
|
+
* e.g.
|
|
3573
|
+
* ```
|
|
3574
|
+
* cameraControls.rotatePolarTo( 30 * THREE.MathUtils.DEG2RAD, true );
|
|
3575
|
+
* ```
|
|
3576
|
+
* @param polarAngle Polar rotate angle. In radian.
|
|
3577
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3578
|
+
* @category Methods
|
|
3579
|
+
*/
|
|
3580
|
+
rotatePolarTo(polarAngle, enableTransition = false) {
|
|
3581
|
+
return this.rotateTo(this._sphericalEnd.theta, polarAngle, enableTransition);
|
|
3582
|
+
}
|
|
3583
|
+
/**
|
|
3584
|
+
* Rotate azimuthal angle(horizontal) and polar angle(vertical) to the given angle.
|
|
3585
|
+
* Camera view will rotate over the orbit pivot absolutely:
|
|
3586
|
+
*
|
|
3587
|
+
* azimuthAngle
|
|
3588
|
+
* ```
|
|
3589
|
+
* 0º
|
|
3590
|
+
* \
|
|
3591
|
+
* 90º -----+----- -90º
|
|
3592
|
+
* \
|
|
3593
|
+
* 180º
|
|
3594
|
+
* ```
|
|
3595
|
+
* | direction | angle |
|
|
3596
|
+
* | --------- | ---------------------- |
|
|
3597
|
+
* | front | 0º |
|
|
3598
|
+
* | left | 90º (`Math.PI / 2`) |
|
|
3599
|
+
* | right | -90º (`- Math.PI / 2`) |
|
|
3600
|
+
* | back | 180º (`Math.PI`) |
|
|
3601
|
+
*
|
|
3602
|
+
* polarAngle
|
|
3603
|
+
* ```
|
|
3604
|
+
* 180º
|
|
3605
|
+
* |
|
|
3606
|
+
* 90º
|
|
3607
|
+
* |
|
|
3608
|
+
* 0º
|
|
3609
|
+
* ```
|
|
3610
|
+
* | direction | angle |
|
|
3611
|
+
* | -------------------- | ---------------------- |
|
|
3612
|
+
* | top/sky | 180º (`Math.PI`) |
|
|
3613
|
+
* | horizontal from view | 90º (`Math.PI / 2`) |
|
|
3614
|
+
* | bottom/floor | 0º |
|
|
3615
|
+
*
|
|
3616
|
+
* @param azimuthAngle Azimuth rotate angle to. In radian.
|
|
3617
|
+
* @param polarAngle Polar rotate angle to. In radian.
|
|
3618
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3619
|
+
* @category Methods
|
|
3620
|
+
*/
|
|
3621
|
+
rotateTo(azimuthAngle, polarAngle, enableTransition = false) {
|
|
3622
|
+
this._isUserControllingRotate = false;
|
|
3623
|
+
const theta = clamp(azimuthAngle, this.minAzimuthAngle, this.maxAzimuthAngle);
|
|
3624
|
+
const phi = clamp(polarAngle, this.minPolarAngle, this.maxPolarAngle);
|
|
3625
|
+
this._sphericalEnd.theta = theta;
|
|
3626
|
+
this._sphericalEnd.phi = phi;
|
|
3627
|
+
this._sphericalEnd.makeSafe();
|
|
3628
|
+
this._needsUpdate = true;
|
|
3629
|
+
if (!enableTransition) {
|
|
3630
|
+
this._spherical.theta = this._sphericalEnd.theta;
|
|
3631
|
+
this._spherical.phi = this._sphericalEnd.phi;
|
|
3632
|
+
}
|
|
3633
|
+
const resolveImmediately = !enableTransition || approxEquals(this._spherical.theta, this._sphericalEnd.theta, this.restThreshold) && approxEquals(this._spherical.phi, this._sphericalEnd.phi, this.restThreshold);
|
|
3634
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Dolly in/out camera position.
|
|
3638
|
+
* @param distance Distance of dollyIn. Negative number for dollyOut.
|
|
3639
|
+
* @param enableTransition Whether to move smoothly or immediately.
|
|
3640
|
+
* @category Methods
|
|
3641
|
+
*/
|
|
3642
|
+
dolly(distance, enableTransition = false) {
|
|
3643
|
+
return this.dollyTo(this._sphericalEnd.radius - distance, enableTransition);
|
|
3644
|
+
}
|
|
3645
|
+
/**
|
|
3646
|
+
* Dolly in/out camera position to given distance.
|
|
3647
|
+
* @param distance Distance of dolly.
|
|
3648
|
+
* @param enableTransition Whether to move smoothly or immediately.
|
|
3649
|
+
* @category Methods
|
|
3650
|
+
*/
|
|
3651
|
+
dollyTo(distance, enableTransition = false) {
|
|
3652
|
+
this._isUserControllingDolly = false;
|
|
3653
|
+
this._isWheelControllingDolly = false;
|
|
3654
|
+
this._lastDollyDirection = DOLLY_DIRECTION.NONE;
|
|
3655
|
+
this._changedDolly = 0;
|
|
3656
|
+
return this._dollyToNoClamp(clamp(distance, this.minDistance, this.maxDistance), enableTransition);
|
|
3657
|
+
}
|
|
3658
|
+
_dollyToNoClamp(distance, enableTransition = false) {
|
|
3659
|
+
const lastRadius = this._sphericalEnd.radius;
|
|
3660
|
+
const hasCollider = this.colliderMeshes.length >= 1;
|
|
3661
|
+
if (hasCollider) {
|
|
3662
|
+
const maxDistanceByCollisionTest = this._collisionTest();
|
|
3663
|
+
const isCollided = approxEquals(maxDistanceByCollisionTest, this._spherical.radius);
|
|
3664
|
+
const isDollyIn = lastRadius > distance;
|
|
3665
|
+
if (!isDollyIn && isCollided)
|
|
3666
|
+
return Promise.resolve();
|
|
3667
|
+
this._sphericalEnd.radius = Math.min(distance, maxDistanceByCollisionTest);
|
|
3668
|
+
} else {
|
|
3669
|
+
this._sphericalEnd.radius = distance;
|
|
3670
|
+
}
|
|
3671
|
+
this._needsUpdate = true;
|
|
3672
|
+
if (!enableTransition) {
|
|
3673
|
+
this._spherical.radius = this._sphericalEnd.radius;
|
|
3674
|
+
}
|
|
3675
|
+
const resolveImmediately = !enableTransition || approxEquals(this._spherical.radius, this._sphericalEnd.radius, this.restThreshold);
|
|
3676
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
3677
|
+
}
|
|
3678
|
+
/**
|
|
3679
|
+
* Dolly in, but does not change the distance between the target and the camera, and moves the target position instead.
|
|
3680
|
+
* Specify a negative value for dolly out.
|
|
3681
|
+
* @param distance Distance of dolly.
|
|
3682
|
+
* @param enableTransition Whether to move smoothly or immediately.
|
|
3683
|
+
* @category Methods
|
|
3684
|
+
*/
|
|
3685
|
+
dollyInFixed(distance, enableTransition = false) {
|
|
3686
|
+
this._targetEnd.add(this._getCameraDirection(_cameraDirection).multiplyScalar(distance));
|
|
3687
|
+
if (!enableTransition) {
|
|
3688
|
+
this._target.copy(this._targetEnd);
|
|
3689
|
+
}
|
|
3690
|
+
const resolveImmediately = !enableTransition || approxEquals(this._target.x, this._targetEnd.x, this.restThreshold) && approxEquals(this._target.y, this._targetEnd.y, this.restThreshold) && approxEquals(this._target.z, this._targetEnd.z, this.restThreshold);
|
|
3691
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
3692
|
+
}
|
|
3693
|
+
/**
|
|
3694
|
+
* Zoom in/out camera. The value is added to camera zoom.
|
|
3695
|
+
* Limits set with `.minZoom` and `.maxZoom`
|
|
3696
|
+
* @param zoomStep zoom scale
|
|
3697
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3698
|
+
* @category Methods
|
|
3699
|
+
*/
|
|
3700
|
+
zoom(zoomStep, enableTransition = false) {
|
|
3701
|
+
return this.zoomTo(this._zoomEnd + zoomStep, enableTransition);
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* Zoom in/out camera to given scale. The value overwrites camera zoom.
|
|
3705
|
+
* Limits set with .minZoom and .maxZoom
|
|
3706
|
+
* @param zoom
|
|
3707
|
+
* @param enableTransition
|
|
3708
|
+
* @category Methods
|
|
3709
|
+
*/
|
|
3710
|
+
zoomTo(zoom, enableTransition = false) {
|
|
3711
|
+
this._isUserControllingZoom = false;
|
|
3712
|
+
this._isWheelControllingZoom = false;
|
|
3713
|
+
this._zoomEnd = clamp(zoom, this.minZoom, this.maxZoom);
|
|
3714
|
+
this._needsUpdate = true;
|
|
3715
|
+
if (!enableTransition) {
|
|
3716
|
+
this._zoom = this._zoomEnd;
|
|
3717
|
+
}
|
|
3718
|
+
const resolveImmediately = !enableTransition || approxEquals(this._zoom, this._zoomEnd, this.restThreshold);
|
|
3719
|
+
this._changedZoom = 0;
|
|
3720
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
3721
|
+
}
|
|
3722
|
+
/**
|
|
3723
|
+
* @deprecated `pan()` has been renamed to `truck()`
|
|
3724
|
+
* @category Methods
|
|
3725
|
+
*/
|
|
3726
|
+
pan(x, y, enableTransition = false) {
|
|
3727
|
+
console.warn("`pan` has been renamed to `truck`");
|
|
3728
|
+
return this.truck(x, y, enableTransition);
|
|
3729
|
+
}
|
|
3730
|
+
/**
|
|
3731
|
+
* Truck and pedestal camera using current azimuthal angle
|
|
3732
|
+
* @param x Horizontal translate amount
|
|
3733
|
+
* @param y Vertical translate amount
|
|
3734
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3735
|
+
* @category Methods
|
|
3736
|
+
*/
|
|
3737
|
+
truck(x, y, enableTransition = false) {
|
|
3738
|
+
this._camera.updateMatrix();
|
|
3739
|
+
_xColumn.setFromMatrixColumn(this._camera.matrix, 0);
|
|
3740
|
+
_yColumn.setFromMatrixColumn(this._camera.matrix, 1);
|
|
3741
|
+
_xColumn.multiplyScalar(x);
|
|
3742
|
+
_yColumn.multiplyScalar(-y);
|
|
3743
|
+
const offset = _v3A.copy(_xColumn).add(_yColumn);
|
|
3744
|
+
const to = _v3B.copy(this._targetEnd).add(offset);
|
|
3745
|
+
return this.moveTo(to.x, to.y, to.z, enableTransition);
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Move forward / backward.
|
|
3749
|
+
* @param distance Amount to move forward / backward. Negative value to move backward
|
|
3750
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3751
|
+
* @category Methods
|
|
3752
|
+
*/
|
|
3753
|
+
forward(distance, enableTransition = false) {
|
|
3754
|
+
_v3A.setFromMatrixColumn(this._camera.matrix, 0);
|
|
3755
|
+
_v3A.crossVectors(this._camera.up, _v3A);
|
|
3756
|
+
_v3A.multiplyScalar(distance);
|
|
3757
|
+
const to = _v3B.copy(this._targetEnd).add(_v3A);
|
|
3758
|
+
return this.moveTo(to.x, to.y, to.z, enableTransition);
|
|
3759
|
+
}
|
|
3760
|
+
/**
|
|
3761
|
+
* Move up / down.
|
|
3762
|
+
* @param height Amount to move up / down. Negative value to move down
|
|
3763
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3764
|
+
* @category Methods
|
|
3765
|
+
*/
|
|
3766
|
+
elevate(height, enableTransition = false) {
|
|
3767
|
+
_v3A.copy(this._camera.up).multiplyScalar(height);
|
|
3768
|
+
return this.moveTo(this._targetEnd.x + _v3A.x, this._targetEnd.y + _v3A.y, this._targetEnd.z + _v3A.z, enableTransition);
|
|
3769
|
+
}
|
|
3770
|
+
/**
|
|
3771
|
+
* Move target position to given point.
|
|
3772
|
+
* @param x x coord to move center position
|
|
3773
|
+
* @param y y coord to move center position
|
|
3774
|
+
* @param z z coord to move center position
|
|
3775
|
+
* @param enableTransition Whether to move smoothly or immediately
|
|
3776
|
+
* @category Methods
|
|
3777
|
+
*/
|
|
3778
|
+
moveTo(x, y, z, enableTransition = false) {
|
|
3779
|
+
this._isUserControllingTruck = false;
|
|
3780
|
+
const offset = _v3A.set(x, y, z).sub(this._targetEnd);
|
|
3781
|
+
this._encloseToBoundary(this._targetEnd, offset, this.boundaryFriction);
|
|
3782
|
+
this._needsUpdate = true;
|
|
3783
|
+
if (!enableTransition) {
|
|
3784
|
+
this._target.copy(this._targetEnd);
|
|
3785
|
+
}
|
|
3786
|
+
const resolveImmediately = !enableTransition || approxEquals(this._target.x, this._targetEnd.x, this.restThreshold) && approxEquals(this._target.y, this._targetEnd.y, this.restThreshold) && approxEquals(this._target.z, this._targetEnd.z, this.restThreshold);
|
|
3787
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
3788
|
+
}
|
|
3789
|
+
/**
|
|
3790
|
+
* Look in the given point direction.
|
|
3791
|
+
* @param x point x.
|
|
3792
|
+
* @param y point y.
|
|
3793
|
+
* @param z point z.
|
|
3794
|
+
* @param enableTransition Whether to move smoothly or immediately.
|
|
3795
|
+
* @returns Transition end promise
|
|
3796
|
+
* @category Methods
|
|
3797
|
+
*/
|
|
3798
|
+
lookInDirectionOf(x, y, z, enableTransition = false) {
|
|
3799
|
+
const point = _v3A.set(x, y, z);
|
|
3800
|
+
const direction = point.sub(this._targetEnd).normalize();
|
|
3801
|
+
const position = direction.multiplyScalar(-this._sphericalEnd.radius).add(this._targetEnd);
|
|
3802
|
+
return this.setPosition(position.x, position.y, position.z, enableTransition);
|
|
3803
|
+
}
|
|
3804
|
+
/**
|
|
3805
|
+
* Fit the viewport to the box or the bounding box of the object, using the nearest axis. paddings are in unit.
|
|
3806
|
+
* set `cover: true` to fill enter screen.
|
|
3807
|
+
* e.g.
|
|
3808
|
+
* ```
|
|
3809
|
+
* cameraControls.fitToBox( myMesh );
|
|
3810
|
+
* ```
|
|
3811
|
+
* @param box3OrObject Axis aligned bounding box to fit the view.
|
|
3812
|
+
* @param enableTransition Whether to move smoothly or immediately.
|
|
3813
|
+
* @param options | `<object>` { cover: boolean, paddingTop: number, paddingLeft: number, paddingBottom: number, paddingRight: number }
|
|
3814
|
+
* @returns Transition end promise
|
|
3815
|
+
* @category Methods
|
|
3816
|
+
*/
|
|
3817
|
+
fitToBox(box3OrObject, enableTransition, { cover = false, paddingLeft = 0, paddingRight = 0, paddingBottom = 0, paddingTop = 0 } = {}) {
|
|
3818
|
+
const promises = [];
|
|
3819
|
+
const aabb = box3OrObject.isBox3 ? _box3A.copy(box3OrObject) : _box3A.setFromObject(box3OrObject);
|
|
3820
|
+
if (aabb.isEmpty()) {
|
|
3821
|
+
console.warn("camera-controls: fitTo() cannot be used with an empty box. Aborting");
|
|
3822
|
+
Promise.resolve();
|
|
3823
|
+
}
|
|
3824
|
+
const theta = roundToStep(this._sphericalEnd.theta, PI_HALF);
|
|
3825
|
+
const phi = roundToStep(this._sphericalEnd.phi, PI_HALF);
|
|
3826
|
+
promises.push(this.rotateTo(theta, phi, enableTransition));
|
|
3827
|
+
const normal = _v3A.setFromSpherical(this._sphericalEnd).normalize();
|
|
3828
|
+
const rotation = _quaternionA.setFromUnitVectors(normal, _AXIS_Z);
|
|
3829
|
+
const viewFromPolar = approxEquals(Math.abs(normal.y), 1);
|
|
3830
|
+
if (viewFromPolar) {
|
|
3831
|
+
rotation.multiply(_quaternionB.setFromAxisAngle(_AXIS_Y, theta));
|
|
3832
|
+
}
|
|
3833
|
+
rotation.multiply(this._yAxisUpSpaceInverse);
|
|
3834
|
+
const bb = _box3B.makeEmpty();
|
|
3835
|
+
_v3B.copy(aabb.min).applyQuaternion(rotation);
|
|
3836
|
+
bb.expandByPoint(_v3B);
|
|
3837
|
+
_v3B.copy(aabb.min).setX(aabb.max.x).applyQuaternion(rotation);
|
|
3838
|
+
bb.expandByPoint(_v3B);
|
|
3839
|
+
_v3B.copy(aabb.min).setY(aabb.max.y).applyQuaternion(rotation);
|
|
3840
|
+
bb.expandByPoint(_v3B);
|
|
3841
|
+
_v3B.copy(aabb.max).setZ(aabb.min.z).applyQuaternion(rotation);
|
|
3842
|
+
bb.expandByPoint(_v3B);
|
|
3843
|
+
_v3B.copy(aabb.min).setZ(aabb.max.z).applyQuaternion(rotation);
|
|
3844
|
+
bb.expandByPoint(_v3B);
|
|
3845
|
+
_v3B.copy(aabb.max).setY(aabb.min.y).applyQuaternion(rotation);
|
|
3846
|
+
bb.expandByPoint(_v3B);
|
|
3847
|
+
_v3B.copy(aabb.max).setX(aabb.min.x).applyQuaternion(rotation);
|
|
3848
|
+
bb.expandByPoint(_v3B);
|
|
3849
|
+
_v3B.copy(aabb.max).applyQuaternion(rotation);
|
|
3850
|
+
bb.expandByPoint(_v3B);
|
|
3851
|
+
bb.min.x -= paddingLeft;
|
|
3852
|
+
bb.min.y -= paddingBottom;
|
|
3853
|
+
bb.max.x += paddingRight;
|
|
3854
|
+
bb.max.y += paddingTop;
|
|
3855
|
+
rotation.setFromUnitVectors(_AXIS_Z, normal);
|
|
3856
|
+
if (viewFromPolar) {
|
|
3857
|
+
rotation.premultiply(_quaternionB.invert());
|
|
3858
|
+
}
|
|
3859
|
+
rotation.premultiply(this._yAxisUpSpace);
|
|
3860
|
+
const bbSize = bb.getSize(_v3A);
|
|
3861
|
+
const center = bb.getCenter(_v3B).applyQuaternion(rotation);
|
|
3862
|
+
if (isPerspectiveCamera(this._camera)) {
|
|
3863
|
+
const distance = this.getDistanceToFitBox(bbSize.x, bbSize.y, bbSize.z, cover);
|
|
3864
|
+
promises.push(this.moveTo(center.x, center.y, center.z, enableTransition));
|
|
3865
|
+
promises.push(this.dollyTo(distance, enableTransition));
|
|
3866
|
+
promises.push(this.setFocalOffset(0, 0, 0, enableTransition));
|
|
3867
|
+
} else if (isOrthographicCamera(this._camera)) {
|
|
3868
|
+
const camera = this._camera;
|
|
3869
|
+
const width = camera.right - camera.left;
|
|
3870
|
+
const height = camera.top - camera.bottom;
|
|
3871
|
+
const zoom = cover ? Math.max(width / bbSize.x, height / bbSize.y) : Math.min(width / bbSize.x, height / bbSize.y);
|
|
3872
|
+
promises.push(this.moveTo(center.x, center.y, center.z, enableTransition));
|
|
3873
|
+
promises.push(this.zoomTo(zoom, enableTransition));
|
|
3874
|
+
promises.push(this.setFocalOffset(0, 0, 0, enableTransition));
|
|
3875
|
+
}
|
|
3876
|
+
return Promise.all(promises);
|
|
3877
|
+
}
|
|
3878
|
+
/**
|
|
3879
|
+
* Fit the viewport to the sphere or the bounding sphere of the object.
|
|
3880
|
+
* @param sphereOrMesh
|
|
3881
|
+
* @param enableTransition
|
|
3882
|
+
* @category Methods
|
|
3883
|
+
*/
|
|
3884
|
+
fitToSphere(sphereOrMesh, enableTransition) {
|
|
3885
|
+
const promises = [];
|
|
3886
|
+
const isObject3D = "isObject3D" in sphereOrMesh;
|
|
3887
|
+
const boundingSphere = isObject3D ? CameraControls.createBoundingSphere(sphereOrMesh, _sphere) : _sphere.copy(sphereOrMesh);
|
|
3888
|
+
promises.push(this.moveTo(boundingSphere.center.x, boundingSphere.center.y, boundingSphere.center.z, enableTransition));
|
|
3889
|
+
if (isPerspectiveCamera(this._camera)) {
|
|
3890
|
+
const distanceToFit = this.getDistanceToFitSphere(boundingSphere.radius);
|
|
3891
|
+
promises.push(this.dollyTo(distanceToFit, enableTransition));
|
|
3892
|
+
} else if (isOrthographicCamera(this._camera)) {
|
|
3893
|
+
const width = this._camera.right - this._camera.left;
|
|
3894
|
+
const height = this._camera.top - this._camera.bottom;
|
|
3895
|
+
const diameter = 2 * boundingSphere.radius;
|
|
3896
|
+
const zoom = Math.min(width / diameter, height / diameter);
|
|
3897
|
+
promises.push(this.zoomTo(zoom, enableTransition));
|
|
3898
|
+
}
|
|
3899
|
+
promises.push(this.setFocalOffset(0, 0, 0, enableTransition));
|
|
3900
|
+
return Promise.all(promises);
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* Look at the `target` from the `position`.
|
|
3904
|
+
* @param positionX
|
|
3905
|
+
* @param positionY
|
|
3906
|
+
* @param positionZ
|
|
3907
|
+
* @param targetX
|
|
3908
|
+
* @param targetY
|
|
3909
|
+
* @param targetZ
|
|
3910
|
+
* @param enableTransition
|
|
3911
|
+
* @category Methods
|
|
3912
|
+
*/
|
|
3913
|
+
setLookAt(positionX, positionY, positionZ, targetX, targetY, targetZ, enableTransition = false) {
|
|
3914
|
+
this._isUserControllingRotate = false;
|
|
3915
|
+
this._isUserControllingDolly = false;
|
|
3916
|
+
this._isWheelControllingDolly = false;
|
|
3917
|
+
this._isUserControllingTruck = false;
|
|
3918
|
+
this._lastDollyDirection = DOLLY_DIRECTION.NONE;
|
|
3919
|
+
this._changedDolly = 0;
|
|
3920
|
+
const target = _v3B.set(targetX, targetY, targetZ);
|
|
3921
|
+
const position = _v3A.set(positionX, positionY, positionZ);
|
|
3922
|
+
this._targetEnd.copy(target);
|
|
3923
|
+
this._sphericalEnd.setFromVector3(position.sub(target).applyQuaternion(this._yAxisUpSpace));
|
|
3924
|
+
this._needsUpdate = true;
|
|
3925
|
+
if (!enableTransition) {
|
|
3926
|
+
this._target.copy(this._targetEnd);
|
|
3927
|
+
this._spherical.copy(this._sphericalEnd);
|
|
3928
|
+
}
|
|
3929
|
+
const resolveImmediately = !enableTransition || approxEquals(this._target.x, this._targetEnd.x, this.restThreshold) && approxEquals(this._target.y, this._targetEnd.y, this.restThreshold) && approxEquals(this._target.z, this._targetEnd.z, this.restThreshold) && approxEquals(this._spherical.theta, this._sphericalEnd.theta, this.restThreshold) && approxEquals(this._spherical.phi, this._sphericalEnd.phi, this.restThreshold) && approxEquals(this._spherical.radius, this._sphericalEnd.radius, this.restThreshold);
|
|
3930
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
3931
|
+
}
|
|
3932
|
+
/**
|
|
3933
|
+
* Interpolates between two states.
|
|
3934
|
+
* @param stateA
|
|
3935
|
+
* @param stateB
|
|
3936
|
+
* @param t
|
|
3937
|
+
* @param enableTransition
|
|
3938
|
+
* @category Methods
|
|
3939
|
+
*/
|
|
3940
|
+
lerp(stateA, stateB, t, enableTransition = false) {
|
|
3941
|
+
this._isUserControllingRotate = false;
|
|
3942
|
+
this._isUserControllingDolly = false;
|
|
3943
|
+
this._isWheelControllingDolly = false;
|
|
3944
|
+
this._isUserControllingTruck = false;
|
|
3945
|
+
this._lastDollyDirection = DOLLY_DIRECTION.NONE;
|
|
3946
|
+
this._changedDolly = 0;
|
|
3947
|
+
const targetA = _v3A.set(...stateA.target);
|
|
3948
|
+
if ("spherical" in stateA) {
|
|
3949
|
+
_sphericalA.set(...stateA.spherical);
|
|
3950
|
+
} else {
|
|
3951
|
+
const positionA = _v3B.set(...stateA.position);
|
|
3952
|
+
_sphericalA.setFromVector3(positionA.sub(targetA).applyQuaternion(this._yAxisUpSpace));
|
|
3953
|
+
}
|
|
3954
|
+
const targetB = _v3C.set(...stateB.target);
|
|
3955
|
+
if ("spherical" in stateB) {
|
|
3956
|
+
_sphericalB.set(...stateB.spherical);
|
|
3957
|
+
} else {
|
|
3958
|
+
const positionB = _v3B.set(...stateB.position);
|
|
3959
|
+
_sphericalB.setFromVector3(positionB.sub(targetB).applyQuaternion(this._yAxisUpSpace));
|
|
3960
|
+
}
|
|
3961
|
+
this._targetEnd.copy(targetA.lerp(targetB, t));
|
|
3962
|
+
const deltaTheta = _sphericalB.theta - _sphericalA.theta;
|
|
3963
|
+
const deltaPhi = _sphericalB.phi - _sphericalA.phi;
|
|
3964
|
+
const deltaRadius = _sphericalB.radius - _sphericalA.radius;
|
|
3965
|
+
this._sphericalEnd.set(_sphericalA.radius + deltaRadius * t, _sphericalA.phi + deltaPhi * t, _sphericalA.theta + deltaTheta * t);
|
|
3966
|
+
this._needsUpdate = true;
|
|
3967
|
+
if (!enableTransition) {
|
|
3968
|
+
this._target.copy(this._targetEnd);
|
|
3969
|
+
this._spherical.copy(this._sphericalEnd);
|
|
3970
|
+
}
|
|
3971
|
+
const resolveImmediately = !enableTransition || approxEquals(this._target.x, this._targetEnd.x, this.restThreshold) && approxEquals(this._target.y, this._targetEnd.y, this.restThreshold) && approxEquals(this._target.z, this._targetEnd.z, this.restThreshold) && approxEquals(this._spherical.theta, this._sphericalEnd.theta, this.restThreshold) && approxEquals(this._spherical.phi, this._sphericalEnd.phi, this.restThreshold) && approxEquals(this._spherical.radius, this._sphericalEnd.radius, this.restThreshold);
|
|
3972
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
3973
|
+
}
|
|
3974
|
+
/**
|
|
3975
|
+
* Similar to setLookAt, but it interpolates between two states.
|
|
3976
|
+
* @param positionAX
|
|
3977
|
+
* @param positionAY
|
|
3978
|
+
* @param positionAZ
|
|
3979
|
+
* @param targetAX
|
|
3980
|
+
* @param targetAY
|
|
3981
|
+
* @param targetAZ
|
|
3982
|
+
* @param positionBX
|
|
3983
|
+
* @param positionBY
|
|
3984
|
+
* @param positionBZ
|
|
3985
|
+
* @param targetBX
|
|
3986
|
+
* @param targetBY
|
|
3987
|
+
* @param targetBZ
|
|
3988
|
+
* @param t
|
|
3989
|
+
* @param enableTransition
|
|
3990
|
+
* @category Methods
|
|
3991
|
+
*/
|
|
3992
|
+
lerpLookAt(positionAX, positionAY, positionAZ, targetAX, targetAY, targetAZ, positionBX, positionBY, positionBZ, targetBX, targetBY, targetBZ, t, enableTransition = false) {
|
|
3993
|
+
return this.lerp({
|
|
3994
|
+
position: [positionAX, positionAY, positionAZ],
|
|
3995
|
+
target: [targetAX, targetAY, targetAZ]
|
|
3996
|
+
}, {
|
|
3997
|
+
position: [positionBX, positionBY, positionBZ],
|
|
3998
|
+
target: [targetBX, targetBY, targetBZ]
|
|
3999
|
+
}, t, enableTransition);
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
4002
|
+
* Set angle and distance by given position.
|
|
4003
|
+
* An alias of `setLookAt()`, without target change. Thus keep gazing at the current target
|
|
4004
|
+
* @param positionX
|
|
4005
|
+
* @param positionY
|
|
4006
|
+
* @param positionZ
|
|
4007
|
+
* @param enableTransition
|
|
4008
|
+
* @category Methods
|
|
4009
|
+
*/
|
|
4010
|
+
setPosition(positionX, positionY, positionZ, enableTransition = false) {
|
|
4011
|
+
return this.setLookAt(positionX, positionY, positionZ, this._targetEnd.x, this._targetEnd.y, this._targetEnd.z, enableTransition);
|
|
4012
|
+
}
|
|
4013
|
+
/**
|
|
4014
|
+
* Set the target position where gaze at.
|
|
4015
|
+
* An alias of `setLookAt()`, without position change. Thus keep the same position.
|
|
4016
|
+
* @param targetX
|
|
4017
|
+
* @param targetY
|
|
4018
|
+
* @param targetZ
|
|
4019
|
+
* @param enableTransition
|
|
4020
|
+
* @category Methods
|
|
4021
|
+
*/
|
|
4022
|
+
setTarget(targetX, targetY, targetZ, enableTransition = false) {
|
|
4023
|
+
const pos = this.getPosition(_v3A);
|
|
4024
|
+
const promise = this.setLookAt(pos.x, pos.y, pos.z, targetX, targetY, targetZ, enableTransition);
|
|
4025
|
+
this._sphericalEnd.phi = clamp(this._sphericalEnd.phi, this.minPolarAngle, this.maxPolarAngle);
|
|
4026
|
+
return promise;
|
|
4027
|
+
}
|
|
4028
|
+
/**
|
|
4029
|
+
* Set focal offset using the screen parallel coordinates. z doesn't affect in Orthographic as with Dolly.
|
|
4030
|
+
* @param x
|
|
4031
|
+
* @param y
|
|
4032
|
+
* @param z
|
|
4033
|
+
* @param enableTransition
|
|
4034
|
+
* @category Methods
|
|
4035
|
+
*/
|
|
4036
|
+
setFocalOffset(x, y, z, enableTransition = false) {
|
|
4037
|
+
this._isUserControllingOffset = false;
|
|
4038
|
+
this._focalOffsetEnd.set(x, y, z);
|
|
4039
|
+
this._needsUpdate = true;
|
|
4040
|
+
if (!enableTransition)
|
|
4041
|
+
this._focalOffset.copy(this._focalOffsetEnd);
|
|
4042
|
+
const resolveImmediately = !enableTransition || approxEquals(this._focalOffset.x, this._focalOffsetEnd.x, this.restThreshold) && approxEquals(this._focalOffset.y, this._focalOffsetEnd.y, this.restThreshold) && approxEquals(this._focalOffset.z, this._focalOffsetEnd.z, this.restThreshold);
|
|
4043
|
+
return this._createOnRestPromise(resolveImmediately);
|
|
4044
|
+
}
|
|
4045
|
+
/**
|
|
4046
|
+
* Set orbit point without moving the camera.
|
|
4047
|
+
* SHOULD NOT RUN DURING ANIMATIONS. `setOrbitPoint()` will immediately fix the positions.
|
|
4048
|
+
* @param targetX
|
|
4049
|
+
* @param targetY
|
|
4050
|
+
* @param targetZ
|
|
4051
|
+
* @category Methods
|
|
4052
|
+
*/
|
|
4053
|
+
setOrbitPoint(targetX, targetY, targetZ) {
|
|
4054
|
+
this._camera.updateMatrixWorld();
|
|
4055
|
+
_xColumn.setFromMatrixColumn(this._camera.matrixWorldInverse, 0);
|
|
4056
|
+
_yColumn.setFromMatrixColumn(this._camera.matrixWorldInverse, 1);
|
|
4057
|
+
_zColumn.setFromMatrixColumn(this._camera.matrixWorldInverse, 2);
|
|
4058
|
+
const position = _v3A.set(targetX, targetY, targetZ);
|
|
4059
|
+
const distance = position.distanceTo(this._camera.position);
|
|
4060
|
+
const cameraToPoint = position.sub(this._camera.position);
|
|
4061
|
+
_xColumn.multiplyScalar(cameraToPoint.x);
|
|
4062
|
+
_yColumn.multiplyScalar(cameraToPoint.y);
|
|
4063
|
+
_zColumn.multiplyScalar(cameraToPoint.z);
|
|
4064
|
+
_v3A.copy(_xColumn).add(_yColumn).add(_zColumn);
|
|
4065
|
+
_v3A.z = _v3A.z + distance;
|
|
4066
|
+
this.dollyTo(distance, false);
|
|
4067
|
+
this.setFocalOffset(-_v3A.x, _v3A.y, -_v3A.z, false);
|
|
4068
|
+
this.moveTo(targetX, targetY, targetZ, false);
|
|
4069
|
+
}
|
|
4070
|
+
/**
|
|
4071
|
+
* Set the boundary box that encloses the target of the camera. box3 is in THREE.Box3
|
|
4072
|
+
* @param box3
|
|
4073
|
+
* @category Methods
|
|
4074
|
+
*/
|
|
4075
|
+
setBoundary(box3) {
|
|
4076
|
+
if (!box3) {
|
|
4077
|
+
this._boundary.min.set(-Infinity, -Infinity, -Infinity);
|
|
4078
|
+
this._boundary.max.set(Infinity, Infinity, Infinity);
|
|
4079
|
+
this._needsUpdate = true;
|
|
4080
|
+
return;
|
|
4081
|
+
}
|
|
4082
|
+
this._boundary.copy(box3);
|
|
4083
|
+
this._boundary.clampPoint(this._targetEnd, this._targetEnd);
|
|
4084
|
+
this._needsUpdate = true;
|
|
4085
|
+
}
|
|
4086
|
+
/**
|
|
4087
|
+
* Set (or unset) the current viewport.
|
|
4088
|
+
* Set this when you want to use renderer viewport and .dollyToCursor feature at the same time.
|
|
4089
|
+
* @param viewportOrX
|
|
4090
|
+
* @param y
|
|
4091
|
+
* @param width
|
|
4092
|
+
* @param height
|
|
4093
|
+
* @category Methods
|
|
4094
|
+
*/
|
|
4095
|
+
setViewport(viewportOrX, y, width, height) {
|
|
4096
|
+
if (viewportOrX === null) {
|
|
4097
|
+
this._viewport = null;
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
4100
|
+
this._viewport = this._viewport || new THREE.Vector4();
|
|
4101
|
+
if (typeof viewportOrX === "number") {
|
|
4102
|
+
this._viewport.set(viewportOrX, y, width, height);
|
|
4103
|
+
} else {
|
|
4104
|
+
this._viewport.copy(viewportOrX);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
/**
|
|
4108
|
+
* Calculate the distance to fit the box.
|
|
4109
|
+
* @param width box width
|
|
4110
|
+
* @param height box height
|
|
4111
|
+
* @param depth box depth
|
|
4112
|
+
* @returns distance
|
|
4113
|
+
* @category Methods
|
|
4114
|
+
*/
|
|
4115
|
+
getDistanceToFitBox(width, height, depth, cover = false) {
|
|
4116
|
+
if (notSupportedInOrthographicCamera(this._camera, "getDistanceToFitBox"))
|
|
4117
|
+
return this._spherical.radius;
|
|
4118
|
+
const boundingRectAspect = width / height;
|
|
4119
|
+
const fov = this._camera.getEffectiveFOV() * DEG2RAD;
|
|
4120
|
+
const aspect = this._camera.aspect;
|
|
4121
|
+
const heightToFit = (cover ? boundingRectAspect > aspect : boundingRectAspect < aspect) ? height : width / aspect;
|
|
4122
|
+
return heightToFit * 0.5 / Math.tan(fov * 0.5) + depth * 0.5;
|
|
4123
|
+
}
|
|
4124
|
+
/**
|
|
4125
|
+
* Calculate the distance to fit the sphere.
|
|
4126
|
+
* @param radius sphere radius
|
|
4127
|
+
* @returns distance
|
|
4128
|
+
* @category Methods
|
|
4129
|
+
*/
|
|
4130
|
+
getDistanceToFitSphere(radius) {
|
|
4131
|
+
if (notSupportedInOrthographicCamera(this._camera, "getDistanceToFitSphere"))
|
|
4132
|
+
return this._spherical.radius;
|
|
4133
|
+
const vFOV = this._camera.getEffectiveFOV() * DEG2RAD;
|
|
4134
|
+
const hFOV = Math.atan(Math.tan(vFOV * 0.5) * this._camera.aspect) * 2;
|
|
4135
|
+
const fov = 1 < this._camera.aspect ? vFOV : hFOV;
|
|
4136
|
+
return radius / Math.sin(fov * 0.5);
|
|
4137
|
+
}
|
|
4138
|
+
/**
|
|
4139
|
+
* Returns the orbit center position, where the camera looking at.
|
|
4140
|
+
* @param out The receiving Vector3 instance to copy the result
|
|
4141
|
+
* @param receiveEndValue Whether receive the transition end coords or current. default is `true`
|
|
4142
|
+
* @category Methods
|
|
4143
|
+
*/
|
|
4144
|
+
getTarget(out, receiveEndValue = true) {
|
|
4145
|
+
const _out = !!out && out.isVector3 ? out : new THREE.Vector3();
|
|
4146
|
+
return _out.copy(receiveEndValue ? this._targetEnd : this._target);
|
|
4147
|
+
}
|
|
4148
|
+
/**
|
|
4149
|
+
* Returns the camera position.
|
|
4150
|
+
* @param out The receiving Vector3 instance to copy the result
|
|
4151
|
+
* @param receiveEndValue Whether receive the transition end coords or current. default is `true`
|
|
4152
|
+
* @category Methods
|
|
4153
|
+
*/
|
|
4154
|
+
getPosition(out, receiveEndValue = true) {
|
|
4155
|
+
const _out = !!out && out.isVector3 ? out : new THREE.Vector3();
|
|
4156
|
+
return _out.setFromSpherical(receiveEndValue ? this._sphericalEnd : this._spherical).applyQuaternion(this._yAxisUpSpaceInverse).add(receiveEndValue ? this._targetEnd : this._target);
|
|
4157
|
+
}
|
|
4158
|
+
/**
|
|
4159
|
+
* Returns the spherical coordinates of the orbit.
|
|
4160
|
+
* @param out The receiving Spherical instance to copy the result
|
|
4161
|
+
* @param receiveEndValue Whether receive the transition end coords or current. default is `true`
|
|
4162
|
+
* @category Methods
|
|
4163
|
+
*/
|
|
4164
|
+
getSpherical(out, receiveEndValue = true) {
|
|
4165
|
+
const _out = out || new THREE.Spherical();
|
|
4166
|
+
return _out.copy(receiveEndValue ? this._sphericalEnd : this._spherical);
|
|
4167
|
+
}
|
|
4168
|
+
/**
|
|
4169
|
+
* Returns the focal offset, which is how much the camera appears to be translated in screen parallel coordinates.
|
|
4170
|
+
* @param out The receiving Vector3 instance to copy the result
|
|
4171
|
+
* @param receiveEndValue Whether receive the transition end coords or current. default is `true`
|
|
4172
|
+
* @category Methods
|
|
4173
|
+
*/
|
|
4174
|
+
getFocalOffset(out, receiveEndValue = true) {
|
|
4175
|
+
const _out = !!out && out.isVector3 ? out : new THREE.Vector3();
|
|
4176
|
+
return _out.copy(receiveEndValue ? this._focalOffsetEnd : this._focalOffset);
|
|
4177
|
+
}
|
|
4178
|
+
/**
|
|
4179
|
+
* Normalize camera azimuth angle (horizontal rotation) between -180 and 180 degrees.
|
|
4180
|
+
* @returns This CameraControls instance.
|
|
4181
|
+
* @category Methods
|
|
4182
|
+
*/
|
|
4183
|
+
normalizeRotations() {
|
|
4184
|
+
this._sphericalEnd.theta = (this._sphericalEnd.theta % PI_2 + PI_2) % PI_2;
|
|
4185
|
+
if (this._sphericalEnd.theta > Math.PI)
|
|
4186
|
+
this._sphericalEnd.theta -= PI_2;
|
|
4187
|
+
this._spherical.theta += PI_2 * Math.round((this._sphericalEnd.theta - this._spherical.theta) / PI_2);
|
|
4188
|
+
return this;
|
|
4189
|
+
}
|
|
4190
|
+
/**
|
|
4191
|
+
* stop all transitions.
|
|
4192
|
+
*/
|
|
4193
|
+
stop() {
|
|
4194
|
+
this._focalOffset.copy(this._focalOffsetEnd);
|
|
4195
|
+
this._target.copy(this._targetEnd);
|
|
4196
|
+
this._spherical.copy(this._sphericalEnd);
|
|
4197
|
+
this._zoom = this._zoomEnd;
|
|
4198
|
+
}
|
|
4199
|
+
/**
|
|
4200
|
+
* Reset all rotation and position to defaults.
|
|
4201
|
+
* @param enableTransition
|
|
4202
|
+
* @category Methods
|
|
4203
|
+
*/
|
|
4204
|
+
reset(enableTransition = false) {
|
|
4205
|
+
if (!approxEquals(this._camera.up.x, this._cameraUp0.x) || !approxEquals(this._camera.up.y, this._cameraUp0.y) || !approxEquals(this._camera.up.z, this._cameraUp0.z)) {
|
|
4206
|
+
this._camera.up.copy(this._cameraUp0);
|
|
4207
|
+
const position = this.getPosition(_v3A);
|
|
4208
|
+
this.updateCameraUp();
|
|
4209
|
+
this.setPosition(position.x, position.y, position.z);
|
|
4210
|
+
}
|
|
4211
|
+
const promises = [
|
|
4212
|
+
this.setLookAt(this._position0.x, this._position0.y, this._position0.z, this._target0.x, this._target0.y, this._target0.z, enableTransition),
|
|
4213
|
+
this.setFocalOffset(this._focalOffset0.x, this._focalOffset0.y, this._focalOffset0.z, enableTransition),
|
|
4214
|
+
this.zoomTo(this._zoom0, enableTransition)
|
|
4215
|
+
];
|
|
4216
|
+
return Promise.all(promises);
|
|
4217
|
+
}
|
|
4218
|
+
/**
|
|
4219
|
+
* Set current camera position as the default position.
|
|
4220
|
+
* @category Methods
|
|
4221
|
+
*/
|
|
4222
|
+
saveState() {
|
|
4223
|
+
this._cameraUp0.copy(this._camera.up);
|
|
4224
|
+
this.getTarget(this._target0);
|
|
4225
|
+
this.getPosition(this._position0);
|
|
4226
|
+
this._zoom0 = this._zoom;
|
|
4227
|
+
this._focalOffset0.copy(this._focalOffset);
|
|
4228
|
+
}
|
|
4229
|
+
/**
|
|
4230
|
+
* Sync camera-up direction.
|
|
4231
|
+
* When camera-up vector is changed, `.updateCameraUp()` must be called.
|
|
4232
|
+
* @category Methods
|
|
4233
|
+
*/
|
|
4234
|
+
updateCameraUp() {
|
|
4235
|
+
this._yAxisUpSpace.setFromUnitVectors(this._camera.up, _AXIS_Y);
|
|
4236
|
+
this._yAxisUpSpaceInverse.copy(this._yAxisUpSpace).invert();
|
|
4237
|
+
}
|
|
4238
|
+
/**
|
|
4239
|
+
* Apply current camera-up direction to the camera.
|
|
4240
|
+
* The orbit system will be re-initialized with the current position.
|
|
4241
|
+
* @category Methods
|
|
4242
|
+
*/
|
|
4243
|
+
applyCameraUp() {
|
|
4244
|
+
const cameraDirection = _v3A.subVectors(this._target, this._camera.position).normalize();
|
|
4245
|
+
const side = _v3B.crossVectors(cameraDirection, this._camera.up);
|
|
4246
|
+
this._camera.up.crossVectors(side, cameraDirection).normalize();
|
|
4247
|
+
this._camera.updateMatrixWorld();
|
|
4248
|
+
const position = this.getPosition(_v3A);
|
|
4249
|
+
this.updateCameraUp();
|
|
4250
|
+
this.setPosition(position.x, position.y, position.z);
|
|
4251
|
+
}
|
|
4252
|
+
/**
|
|
4253
|
+
* Update camera position and directions.
|
|
4254
|
+
* This should be called in your tick loop every time, and returns true if re-rendering is needed.
|
|
4255
|
+
* @param delta
|
|
4256
|
+
* @returns updated
|
|
4257
|
+
* @category Methods
|
|
4258
|
+
*/
|
|
4259
|
+
update(delta) {
|
|
4260
|
+
const deltaTheta = this._sphericalEnd.theta - this._spherical.theta;
|
|
4261
|
+
const deltaPhi = this._sphericalEnd.phi - this._spherical.phi;
|
|
4262
|
+
const deltaRadius = this._sphericalEnd.radius - this._spherical.radius;
|
|
4263
|
+
const deltaTarget = _deltaTarget.subVectors(this._targetEnd, this._target);
|
|
4264
|
+
const deltaOffset = _deltaOffset.subVectors(this._focalOffsetEnd, this._focalOffset);
|
|
4265
|
+
const deltaZoom = this._zoomEnd - this._zoom;
|
|
4266
|
+
if (approxZero(deltaTheta)) {
|
|
4267
|
+
this._thetaVelocity.value = 0;
|
|
4268
|
+
this._spherical.theta = this._sphericalEnd.theta;
|
|
4269
|
+
} else {
|
|
4270
|
+
const smoothTime = this._isUserControllingRotate ? this.draggingSmoothTime : this.azimuthTime;
|
|
4271
|
+
this._spherical.theta = smoothDamp(this._spherical.theta, this._sphericalEnd.theta, this._thetaVelocity, smoothTime, Infinity, delta);
|
|
4272
|
+
this._needsUpdate = true;
|
|
4273
|
+
}
|
|
4274
|
+
if (approxZero(deltaPhi)) {
|
|
4275
|
+
this._phiVelocity.value = 0;
|
|
4276
|
+
this._spherical.phi = this._sphericalEnd.phi;
|
|
4277
|
+
} else {
|
|
4278
|
+
const smoothTime = this._isUserControllingRotate ? this.draggingSmoothTime : this.polarTime;
|
|
4279
|
+
this._spherical.phi = smoothDamp(this._spherical.phi, this._sphericalEnd.phi, this._phiVelocity, smoothTime, Infinity, delta);
|
|
4280
|
+
this._needsUpdate = true;
|
|
4281
|
+
}
|
|
4282
|
+
if (approxZero(deltaRadius)) {
|
|
4283
|
+
this._radiusVelocity.value = 0;
|
|
4284
|
+
this._spherical.radius = this._sphericalEnd.radius;
|
|
4285
|
+
} else {
|
|
4286
|
+
const smoothTime = this._isUserControllingDolly ? this._isWheelControllingDolly ? this.wheelSmoothTime : this.draggingSmoothTime : this.dollyTime;
|
|
4287
|
+
this._spherical.radius = smoothDamp(this._spherical.radius, this._sphericalEnd.radius, this._radiusVelocity, smoothTime, this.maxSpeed, delta);
|
|
4288
|
+
this._needsUpdate = true;
|
|
4289
|
+
}
|
|
4290
|
+
if (approxZero(deltaTarget.x) && approxZero(deltaTarget.y) && approxZero(deltaTarget.z)) {
|
|
4291
|
+
this._targetVelocity.set(0, 0, 0);
|
|
4292
|
+
this._target.copy(this._targetEnd);
|
|
4293
|
+
} else {
|
|
4294
|
+
const smoothTime = this._isUserControllingTruck ? this.draggingSmoothTime : this.truckTime;
|
|
4295
|
+
smoothDampVec3(this._target, this._targetEnd, this._targetVelocity, smoothTime, this.maxSpeed, delta, this._target);
|
|
4296
|
+
this._needsUpdate = true;
|
|
4297
|
+
}
|
|
4298
|
+
if (approxZero(deltaOffset.x) && approxZero(deltaOffset.y) && approxZero(deltaOffset.z)) {
|
|
4299
|
+
this._focalOffsetVelocity.set(0, 0, 0);
|
|
4300
|
+
this._focalOffset.copy(this._focalOffsetEnd);
|
|
4301
|
+
} else {
|
|
4302
|
+
const smoothTime = this._isUserControllingOffset ? this.draggingSmoothTime : this.smoothTime;
|
|
4303
|
+
smoothDampVec3(this._focalOffset, this._focalOffsetEnd, this._focalOffsetVelocity, smoothTime, this.maxSpeed, delta, this._focalOffset);
|
|
4304
|
+
this._needsUpdate = true;
|
|
4305
|
+
}
|
|
4306
|
+
if (approxZero(deltaZoom)) {
|
|
4307
|
+
this._zoomVelocity.value = 0;
|
|
4308
|
+
this._zoom = this._zoomEnd;
|
|
4309
|
+
} else {
|
|
4310
|
+
const smoothTime = this._isUserControllingZoom ? this._isWheelControllingZoom ? this.wheelSmoothTime : this.draggingSmoothTime : this.smoothTime;
|
|
4311
|
+
this._zoom = smoothDamp(this._zoom, this._zoomEnd, this._zoomVelocity, smoothTime, Infinity, delta);
|
|
4312
|
+
}
|
|
4313
|
+
if (this.dollyToCursor) {
|
|
4314
|
+
if (isPerspectiveCamera(this._camera) && this._changedDolly !== 0) {
|
|
4315
|
+
const dollyControlAmount = this._spherical.radius - this._lastDistance;
|
|
4316
|
+
const prevRadius = this._sphericalEnd.radius - dollyControlAmount;
|
|
4317
|
+
const lerpRatio = (prevRadius - this._sphericalEnd.radius) / this._sphericalEnd.radius;
|
|
4318
|
+
const intersection = this._cursorToPlane(this._dollyControlCoord.x, this._dollyControlCoord.y);
|
|
4319
|
+
if (intersection) this._dollyToCursorTarget.copy(intersection);
|
|
4320
|
+
const newTargetEnd = _v3B.copy(this._targetEnd).lerp(this._dollyToCursorTarget, lerpRatio);
|
|
4321
|
+
const isMin = this._lastDollyDirection === DOLLY_DIRECTION.IN && this._spherical.radius <= this.minDistance;
|
|
4322
|
+
const isMax = this._lastDollyDirection === DOLLY_DIRECTION.OUT && this.maxDistance <= this._spherical.radius;
|
|
4323
|
+
if (this.infinityDolly && (isMin || isMax)) {
|
|
4324
|
+
this._sphericalEnd.radius -= dollyControlAmount;
|
|
4325
|
+
this._spherical.radius -= dollyControlAmount;
|
|
4326
|
+
const cameraDirection = this._getCameraDirection(_cameraDirection);
|
|
4327
|
+
const dollyAmount = _v3C.copy(cameraDirection).multiplyScalar(-dollyControlAmount);
|
|
4328
|
+
newTargetEnd.add(dollyAmount);
|
|
4329
|
+
}
|
|
4330
|
+
this._boundary.clampPoint(newTargetEnd, newTargetEnd);
|
|
4331
|
+
const targetEndDiff = _v3C.subVectors(newTargetEnd, this._targetEnd);
|
|
4332
|
+
this._targetEnd.copy(newTargetEnd);
|
|
4333
|
+
this._target.add(targetEndDiff);
|
|
4334
|
+
this._changedDolly -= dollyControlAmount;
|
|
4335
|
+
if (approxZero(this._changedDolly)) this._changedDolly = 0;
|
|
4336
|
+
} else if (isOrthographicCamera(this._camera) && this._changedZoom !== 0) {
|
|
4337
|
+
const dollyControlAmount = this._zoom - this._lastZoom;
|
|
4338
|
+
const camera = this._camera;
|
|
4339
|
+
const worldCursorPosition = _v3A.set(this._dollyControlCoord.x, this._dollyControlCoord.y, (camera.near + camera.far) / (camera.near - camera.far)).unproject(camera);
|
|
4340
|
+
const quaternion = _v3B.set(0, 0, -1).applyQuaternion(camera.quaternion);
|
|
4341
|
+
const cursor = _v3C.copy(worldCursorPosition).add(quaternion.multiplyScalar(-worldCursorPosition.dot(camera.up)));
|
|
4342
|
+
const prevZoom = this._zoom - dollyControlAmount;
|
|
4343
|
+
const lerpRatio = -(prevZoom - this._zoom) / this._zoom;
|
|
4344
|
+
const cameraDirection = this._getCameraDirection(_cameraDirection);
|
|
4345
|
+
const prevPlaneConstant = this._targetEnd.dot(cameraDirection);
|
|
4346
|
+
const newTargetEnd = _v3A.copy(this._targetEnd).lerp(cursor, lerpRatio);
|
|
4347
|
+
const newPlaneConstant = newTargetEnd.dot(cameraDirection);
|
|
4348
|
+
const pullBack = cameraDirection.multiplyScalar(newPlaneConstant - prevPlaneConstant);
|
|
4349
|
+
newTargetEnd.sub(pullBack);
|
|
4350
|
+
this._boundary.clampPoint(newTargetEnd, newTargetEnd);
|
|
4351
|
+
const targetEndDiff = _v3B.subVectors(newTargetEnd, this._targetEnd);
|
|
4352
|
+
this._targetEnd.copy(newTargetEnd);
|
|
4353
|
+
this._target.add(targetEndDiff);
|
|
4354
|
+
this._changedZoom -= dollyControlAmount;
|
|
4355
|
+
if (approxZero(this._changedZoom))
|
|
4356
|
+
this._changedZoom = 0;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
if (this._camera.zoom !== this._zoom) {
|
|
4360
|
+
this._camera.zoom = this._zoom;
|
|
4361
|
+
this._camera.updateProjectionMatrix();
|
|
4362
|
+
this._updateNearPlaneCorners();
|
|
4363
|
+
this._needsUpdate = true;
|
|
4364
|
+
}
|
|
4365
|
+
this._dragNeedsUpdate = true;
|
|
4366
|
+
const maxDistance = this._collisionTest();
|
|
4367
|
+
this._spherical.radius = Math.min(this._spherical.radius, maxDistance);
|
|
4368
|
+
this._spherical.makeSafe();
|
|
4369
|
+
this._camera.position.setFromSpherical(this._spherical).applyQuaternion(this._yAxisUpSpaceInverse).add(this._target);
|
|
4370
|
+
this._camera.lookAt(this._target);
|
|
4371
|
+
const affectOffset = !approxZero(this._focalOffset.x) || !approxZero(this._focalOffset.y) || !approxZero(this._focalOffset.z);
|
|
4372
|
+
if (affectOffset) {
|
|
4373
|
+
this._camera.matrix.compose(this._camera.position, this._camera.quaternion, this._camera.scale);
|
|
4374
|
+
_xColumn.setFromMatrixColumn(this._camera.matrix, 0);
|
|
4375
|
+
_yColumn.setFromMatrixColumn(this._camera.matrix, 1);
|
|
4376
|
+
_zColumn.setFromMatrixColumn(this._camera.matrix, 2);
|
|
4377
|
+
_xColumn.multiplyScalar(this._focalOffset.x);
|
|
4378
|
+
_yColumn.multiplyScalar(-this._focalOffset.y);
|
|
4379
|
+
_zColumn.multiplyScalar(this._focalOffset.z);
|
|
4380
|
+
_v3A.copy(_xColumn).add(_yColumn).add(_zColumn);
|
|
4381
|
+
this._camera.position.add(_v3A);
|
|
4382
|
+
this._camera.updateMatrixWorld();
|
|
4383
|
+
}
|
|
4384
|
+
if (this._boundaryEnclosesCamera) {
|
|
4385
|
+
this._encloseToBoundary(this._camera.position.copy(this._target), _v3A.setFromSpherical(this._spherical).applyQuaternion(this._yAxisUpSpaceInverse), 1);
|
|
4386
|
+
}
|
|
4387
|
+
const updated = this._needsUpdate;
|
|
4388
|
+
if (updated && !this._updatedLastTime) {
|
|
4389
|
+
this._hasRested = false;
|
|
4390
|
+
this.dispatchEvent({ type: "wake" });
|
|
4391
|
+
this.dispatchEvent({ type: "update" });
|
|
4392
|
+
} else if (updated) {
|
|
4393
|
+
this.dispatchEvent({ type: "update" });
|
|
4394
|
+
if (approxZero(deltaTheta, this.restThreshold) && approxZero(deltaPhi, this.restThreshold) && approxZero(deltaRadius, this.restThreshold) && approxZero(deltaTarget.x, this.restThreshold) && approxZero(deltaTarget.y, this.restThreshold) && approxZero(deltaTarget.z, this.restThreshold) && approxZero(deltaOffset.x, this.restThreshold) && approxZero(deltaOffset.y, this.restThreshold) && approxZero(deltaOffset.z, this.restThreshold) && approxZero(deltaZoom, this.restThreshold) && !this._hasRested) {
|
|
4395
|
+
this._hasRested = true;
|
|
4396
|
+
this.dispatchEvent({ type: "rest" });
|
|
4397
|
+
}
|
|
4398
|
+
} else if (!updated && this._updatedLastTime) {
|
|
4399
|
+
this.dispatchEvent({ type: "sleep" });
|
|
4400
|
+
}
|
|
4401
|
+
this._lastDistance = this._spherical.radius;
|
|
4402
|
+
this._lastZoom = this._zoom;
|
|
4403
|
+
this._updatedLastTime = updated;
|
|
4404
|
+
this._needsUpdate = false;
|
|
4405
|
+
return updated;
|
|
4406
|
+
}
|
|
4407
|
+
/**
|
|
4408
|
+
* Get all state in JSON string
|
|
4409
|
+
* @category Methods
|
|
4410
|
+
*/
|
|
4411
|
+
toJSON() {
|
|
4412
|
+
return JSON.stringify({
|
|
4413
|
+
enabled: this._enabled,
|
|
4414
|
+
minDistance: this.minDistance,
|
|
4415
|
+
maxDistance: infinityToMaxNumber(this.maxDistance),
|
|
4416
|
+
minZoom: this.minZoom,
|
|
4417
|
+
maxZoom: infinityToMaxNumber(this.maxZoom),
|
|
4418
|
+
minPolarAngle: this.minPolarAngle,
|
|
4419
|
+
maxPolarAngle: infinityToMaxNumber(this.maxPolarAngle),
|
|
4420
|
+
minAzimuthAngle: infinityToMaxNumber(this.minAzimuthAngle),
|
|
4421
|
+
maxAzimuthAngle: infinityToMaxNumber(this.maxAzimuthAngle),
|
|
4422
|
+
smoothTime: this.smoothTime,
|
|
4423
|
+
draggingSmoothTime: this.draggingSmoothTime,
|
|
4424
|
+
wheelSmoothTime: this.wheelSmoothTime,
|
|
4425
|
+
azimuthTime: this.azimuthTime,
|
|
4426
|
+
polarTime: this.polarTime,
|
|
4427
|
+
dollyTime: this.dollyTime,
|
|
4428
|
+
truckTime: this.truckTime,
|
|
4429
|
+
dollySpeed: this.dollySpeed,
|
|
4430
|
+
truckSpeed: this.truckSpeed,
|
|
4431
|
+
dollyToCursor: this.dollyToCursor,
|
|
4432
|
+
target: this._targetEnd.toArray(),
|
|
4433
|
+
position: _v3A.setFromSpherical(this._sphericalEnd).add(this._targetEnd).toArray(),
|
|
4434
|
+
zoom: this._zoomEnd,
|
|
4435
|
+
focalOffset: this._focalOffsetEnd.toArray(),
|
|
4436
|
+
target0: this._target0.toArray(),
|
|
4437
|
+
position0: this._position0.toArray(),
|
|
4438
|
+
zoom0: this._zoom0,
|
|
4439
|
+
focalOffset0: this._focalOffset0.toArray()
|
|
4440
|
+
});
|
|
4441
|
+
}
|
|
4442
|
+
/**
|
|
4443
|
+
* Reproduce the control state with JSON. enableTransition is where anim or not in a boolean.
|
|
4444
|
+
* @param json
|
|
4445
|
+
* @param enableTransition
|
|
4446
|
+
* @category Methods
|
|
4447
|
+
*/
|
|
4448
|
+
fromJSON(json, enableTransition = false) {
|
|
4449
|
+
const obj = JSON.parse(json);
|
|
4450
|
+
this.enabled = obj.enabled;
|
|
4451
|
+
this.minDistance = obj.minDistance;
|
|
4452
|
+
this.maxDistance = maxNumberToInfinity(obj.maxDistance);
|
|
4453
|
+
this.minZoom = obj.minZoom;
|
|
4454
|
+
this.maxZoom = maxNumberToInfinity(obj.maxZoom);
|
|
4455
|
+
this.minPolarAngle = obj.minPolarAngle;
|
|
4456
|
+
this.maxPolarAngle = maxNumberToInfinity(obj.maxPolarAngle);
|
|
4457
|
+
this.minAzimuthAngle = maxNumberToInfinity(obj.minAzimuthAngle);
|
|
4458
|
+
this.maxAzimuthAngle = maxNumberToInfinity(obj.maxAzimuthAngle);
|
|
4459
|
+
this.smoothTime = obj.smoothTime;
|
|
4460
|
+
this.draggingSmoothTime = obj.draggingSmoothTime;
|
|
4461
|
+
this.wheelSmoothTime = obj.wheelSmoothTime !== void 0 ? obj.wheelSmoothTime : this.draggingSmoothTime;
|
|
4462
|
+
this.azimuthTime = obj.azimuthTime !== void 0 ? obj.azimuthTime : this.smoothTime;
|
|
4463
|
+
this.polarTime = obj.polarTime !== void 0 ? obj.polarTime : this.smoothTime;
|
|
4464
|
+
this.dollyTime = obj.dollyTime !== void 0 ? obj.dollyTime : this.smoothTime;
|
|
4465
|
+
this.truckTime = obj.truckTime !== void 0 ? obj.truckTime : this.smoothTime;
|
|
4466
|
+
this.dollySpeed = obj.dollySpeed;
|
|
4467
|
+
this.truckSpeed = obj.truckSpeed;
|
|
4468
|
+
this.dollyToCursor = obj.dollyToCursor;
|
|
4469
|
+
this._target0.fromArray(obj.target0);
|
|
4470
|
+
this._position0.fromArray(obj.position0);
|
|
4471
|
+
this._zoom0 = obj.zoom0;
|
|
4472
|
+
this._focalOffset0.fromArray(obj.focalOffset0);
|
|
4473
|
+
this.moveTo(obj.target[0], obj.target[1], obj.target[2], enableTransition);
|
|
4474
|
+
_sphericalA.setFromVector3(_v3A.fromArray(obj.position).sub(this._targetEnd).applyQuaternion(this._yAxisUpSpace));
|
|
4475
|
+
this.rotateTo(_sphericalA.theta, _sphericalA.phi, enableTransition);
|
|
4476
|
+
this.dollyTo(_sphericalA.radius, enableTransition);
|
|
4477
|
+
this.zoomTo(obj.zoom, enableTransition);
|
|
4478
|
+
this.setFocalOffset(obj.focalOffset[0], obj.focalOffset[1], obj.focalOffset[2], enableTransition);
|
|
4479
|
+
this._needsUpdate = true;
|
|
4480
|
+
}
|
|
4481
|
+
/**
|
|
4482
|
+
* Attach all internal event handlers to enable drag control.
|
|
4483
|
+
* @category Methods
|
|
4484
|
+
*/
|
|
4485
|
+
connect(domElement) {
|
|
4486
|
+
if (this._domElement) {
|
|
4487
|
+
console.warn("camera-controls is already connected.");
|
|
4488
|
+
return;
|
|
4489
|
+
}
|
|
4490
|
+
domElement.setAttribute("data-camera-controls-version", VERSION);
|
|
4491
|
+
this._addAllEventListeners(domElement);
|
|
4492
|
+
this._getClientRect(this._elementRect);
|
|
4493
|
+
}
|
|
4494
|
+
/**
|
|
4495
|
+
* Detach all internal event handlers to disable drag control.
|
|
4496
|
+
*/
|
|
4497
|
+
disconnect() {
|
|
4498
|
+
this.cancel();
|
|
4499
|
+
this._removeAllEventListeners();
|
|
4500
|
+
if (this._domElement) {
|
|
4501
|
+
this._domElement.removeAttribute("data-camera-controls-version");
|
|
4502
|
+
this._domElement = void 0;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Dispose the cameraControls instance itself, remove all eventListeners.
|
|
4507
|
+
* @category Methods
|
|
4508
|
+
*/
|
|
4509
|
+
dispose() {
|
|
4510
|
+
this.removeAllEventListeners();
|
|
4511
|
+
this.disconnect();
|
|
4512
|
+
}
|
|
4513
|
+
// it's okay to expose public though
|
|
4514
|
+
_getTargetDirection(out) {
|
|
4515
|
+
return out.setFromSpherical(this._spherical).divideScalar(this._spherical.radius).applyQuaternion(this._yAxisUpSpaceInverse);
|
|
4516
|
+
}
|
|
4517
|
+
// it's okay to expose public though
|
|
4518
|
+
_getCameraDirection(out) {
|
|
4519
|
+
return this._getTargetDirection(out).negate();
|
|
4520
|
+
}
|
|
4521
|
+
_findPointerById(pointerId) {
|
|
4522
|
+
return this._activePointers.find((activePointer) => activePointer.pointerId === pointerId);
|
|
4523
|
+
}
|
|
4524
|
+
_findPointerByMouseButton(mouseButton) {
|
|
4525
|
+
return this._activePointers.find((activePointer) => activePointer.mouseButton === mouseButton);
|
|
4526
|
+
}
|
|
4527
|
+
_disposePointer(pointer) {
|
|
4528
|
+
this._activePointers.splice(this._activePointers.indexOf(pointer), 1);
|
|
4529
|
+
}
|
|
4530
|
+
_encloseToBoundary(position, offset, friction) {
|
|
4531
|
+
const offsetLength2 = offset.lengthSq();
|
|
4532
|
+
if (offsetLength2 === 0) {
|
|
4533
|
+
return position;
|
|
4534
|
+
}
|
|
4535
|
+
const newTarget = _v3B.copy(offset).add(position);
|
|
4536
|
+
const clampedTarget = this._boundary.clampPoint(newTarget, _v3C);
|
|
4537
|
+
const deltaClampedTarget = clampedTarget.sub(newTarget);
|
|
4538
|
+
const deltaClampedTargetLength2 = deltaClampedTarget.lengthSq();
|
|
4539
|
+
if (deltaClampedTargetLength2 === 0) {
|
|
4540
|
+
return position.add(offset);
|
|
4541
|
+
} else if (deltaClampedTargetLength2 === offsetLength2) {
|
|
4542
|
+
return position;
|
|
4543
|
+
} else if (friction === 0) {
|
|
4544
|
+
return position.add(offset).add(deltaClampedTarget);
|
|
4545
|
+
} else {
|
|
4546
|
+
const offsetFactor = 1 + friction * deltaClampedTargetLength2 / offset.dot(deltaClampedTarget);
|
|
4547
|
+
return position.add(_v3B.copy(offset).multiplyScalar(offsetFactor)).add(deltaClampedTarget.multiplyScalar(1 - friction));
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
_updateNearPlaneCorners() {
|
|
4551
|
+
if (isPerspectiveCamera(this._camera)) {
|
|
4552
|
+
const camera = this._camera;
|
|
4553
|
+
const near = camera.near;
|
|
4554
|
+
const fov = camera.getEffectiveFOV() * DEG2RAD;
|
|
4555
|
+
const heightHalf = Math.tan(fov * 0.5) * near;
|
|
4556
|
+
const widthHalf = heightHalf * camera.aspect;
|
|
4557
|
+
this._nearPlaneCorners[0].set(-widthHalf, -heightHalf, 0);
|
|
4558
|
+
this._nearPlaneCorners[1].set(widthHalf, -heightHalf, 0);
|
|
4559
|
+
this._nearPlaneCorners[2].set(widthHalf, heightHalf, 0);
|
|
4560
|
+
this._nearPlaneCorners[3].set(-widthHalf, heightHalf, 0);
|
|
4561
|
+
} else if (isOrthographicCamera(this._camera)) {
|
|
4562
|
+
const camera = this._camera;
|
|
4563
|
+
const zoomInv = 1 / camera.zoom;
|
|
4564
|
+
const left = camera.left * zoomInv;
|
|
4565
|
+
const right = camera.right * zoomInv;
|
|
4566
|
+
const top = camera.top * zoomInv;
|
|
4567
|
+
const bottom = camera.bottom * zoomInv;
|
|
4568
|
+
this._nearPlaneCorners[0].set(left, top, 0);
|
|
4569
|
+
this._nearPlaneCorners[1].set(right, top, 0);
|
|
4570
|
+
this._nearPlaneCorners[2].set(right, bottom, 0);
|
|
4571
|
+
this._nearPlaneCorners[3].set(left, bottom, 0);
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
// lateUpdate
|
|
4575
|
+
_collisionTest() {
|
|
4576
|
+
let distance = Infinity;
|
|
4577
|
+
const hasCollider = this.colliderMeshes.length >= 1;
|
|
4578
|
+
if (!hasCollider)
|
|
4579
|
+
return distance;
|
|
4580
|
+
if (notSupportedInOrthographicCamera(this._camera, "_collisionTest"))
|
|
4581
|
+
return distance;
|
|
4582
|
+
const rayDirection = this._getTargetDirection(_cameraDirection);
|
|
4583
|
+
_rotationMatrix.lookAt(_ORIGIN, rayDirection, this._camera.up);
|
|
4584
|
+
for (let i = 0; i < 4; i++) {
|
|
4585
|
+
const nearPlaneCorner = _v3B.copy(this._nearPlaneCorners[i]);
|
|
4586
|
+
nearPlaneCorner.applyMatrix4(_rotationMatrix);
|
|
4587
|
+
const origin2 = _v3C.addVectors(this._target, nearPlaneCorner);
|
|
4588
|
+
_raycaster.set(origin2, rayDirection);
|
|
4589
|
+
_raycaster.far = this._spherical.radius + 1;
|
|
4590
|
+
const intersects = _raycaster.intersectObjects(this.colliderMeshes);
|
|
4591
|
+
if (intersects.length !== 0 && intersects[0].distance < distance) {
|
|
4592
|
+
distance = intersects[0].distance;
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
return distance;
|
|
4596
|
+
}
|
|
4597
|
+
/**
|
|
4598
|
+
* Get its client rect and package into given `DOMRect` .
|
|
4599
|
+
*/
|
|
4600
|
+
_getClientRect(target) {
|
|
4601
|
+
if (!this._domElement)
|
|
4602
|
+
return;
|
|
4603
|
+
const rect = this._domElement.getBoundingClientRect();
|
|
4604
|
+
target.x = rect.left;
|
|
4605
|
+
target.y = rect.top;
|
|
4606
|
+
if (this._viewport) {
|
|
4607
|
+
target.x += this._viewport.x;
|
|
4608
|
+
target.y += rect.height - this._viewport.w - this._viewport.y;
|
|
4609
|
+
target.width = this._viewport.z;
|
|
4610
|
+
target.height = this._viewport.w;
|
|
4611
|
+
} else {
|
|
4612
|
+
target.width = rect.width;
|
|
4613
|
+
target.height = rect.height;
|
|
4614
|
+
}
|
|
4615
|
+
return target;
|
|
4616
|
+
}
|
|
4617
|
+
_createOnRestPromise(resolveImmediately) {
|
|
4618
|
+
if (resolveImmediately)
|
|
4619
|
+
return Promise.resolve();
|
|
4620
|
+
this._hasRested = false;
|
|
4621
|
+
this.dispatchEvent({ type: "transitionstart" });
|
|
4622
|
+
return new Promise((resolve) => {
|
|
4623
|
+
const onResolve = () => {
|
|
4624
|
+
this.removeEventListener("rest", onResolve);
|
|
4625
|
+
resolve();
|
|
4626
|
+
};
|
|
4627
|
+
this.addEventListener("rest", onResolve);
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4630
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4631
|
+
_addAllEventListeners(_domElement) {
|
|
4632
|
+
}
|
|
4633
|
+
_removeAllEventListeners() {
|
|
4634
|
+
}
|
|
4635
|
+
/**
|
|
4636
|
+
* backward compatible
|
|
4637
|
+
* @deprecated use smoothTime (in seconds) instead
|
|
4638
|
+
* @category Properties
|
|
4639
|
+
*/
|
|
4640
|
+
get dampingFactor() {
|
|
4641
|
+
console.warn(".dampingFactor has been deprecated. use smoothTime (in seconds) instead.");
|
|
4642
|
+
return 0;
|
|
4643
|
+
}
|
|
4644
|
+
/**
|
|
4645
|
+
* backward compatible
|
|
4646
|
+
* @deprecated use smoothTime (in seconds) instead
|
|
4647
|
+
* @category Properties
|
|
4648
|
+
*/
|
|
4649
|
+
set dampingFactor(_) {
|
|
4650
|
+
console.warn(".dampingFactor has been deprecated. use smoothTime (in seconds) instead.");
|
|
4651
|
+
}
|
|
4652
|
+
/**
|
|
4653
|
+
* backward compatible
|
|
4654
|
+
* @deprecated use draggingSmoothTime (in seconds) instead
|
|
4655
|
+
* @category Properties
|
|
4656
|
+
*/
|
|
4657
|
+
get draggingDampingFactor() {
|
|
4658
|
+
console.warn(".draggingDampingFactor has been deprecated. use draggingSmoothTime (in seconds) instead.");
|
|
4659
|
+
return 0;
|
|
4660
|
+
}
|
|
4661
|
+
/**
|
|
4662
|
+
* backward compatible
|
|
4663
|
+
* @deprecated use draggingSmoothTime (in seconds) instead
|
|
4664
|
+
* @category Properties
|
|
4665
|
+
*/
|
|
4666
|
+
set draggingDampingFactor(_) {
|
|
4667
|
+
console.warn(".draggingDampingFactor has been deprecated. use draggingSmoothTime (in seconds) instead.");
|
|
4668
|
+
}
|
|
4669
|
+
static createBoundingSphere(object3d, out = new THREE.Sphere()) {
|
|
4670
|
+
const boundingSphere = out;
|
|
4671
|
+
const center = boundingSphere.center;
|
|
4672
|
+
_box3A.makeEmpty();
|
|
4673
|
+
object3d.traverseVisible((object) => {
|
|
4674
|
+
if (!object.isMesh)
|
|
4675
|
+
return;
|
|
4676
|
+
_box3A.expandByObject(object);
|
|
4677
|
+
});
|
|
4678
|
+
_box3A.getCenter(center);
|
|
4679
|
+
let maxRadiusSq = 0;
|
|
4680
|
+
object3d.traverseVisible((object) => {
|
|
4681
|
+
if (!object.isMesh)
|
|
4682
|
+
return;
|
|
4683
|
+
const mesh = object;
|
|
4684
|
+
if (!mesh.geometry)
|
|
4685
|
+
return;
|
|
4686
|
+
const geometry = mesh.geometry.clone();
|
|
4687
|
+
geometry.applyMatrix4(mesh.matrixWorld);
|
|
4688
|
+
const bufferGeometry = geometry;
|
|
4689
|
+
const position = bufferGeometry.attributes.position;
|
|
4690
|
+
for (let i = 0, l = position.count; i < l; i++) {
|
|
4691
|
+
_v3A.fromBufferAttribute(position, i);
|
|
4692
|
+
maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_v3A));
|
|
4693
|
+
}
|
|
4694
|
+
});
|
|
4695
|
+
boundingSphere.radius = Math.sqrt(maxRadiusSq);
|
|
4696
|
+
return boundingSphere;
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
2184
4699
|
const subsetOfTHREE = {
|
|
2185
4700
|
Vector2,
|
|
2186
4701
|
Vector3,
|
|
@@ -2204,9 +4719,9 @@ class CameraController extends CameraControls {
|
|
|
2204
4719
|
this.renderer = renderer;
|
|
2205
4720
|
}
|
|
2206
4721
|
update(delta) {
|
|
2207
|
-
var
|
|
4722
|
+
var _a2;
|
|
2208
4723
|
const needsUpdate = super.update(delta);
|
|
2209
|
-
if (needsUpdate && ((
|
|
4724
|
+
if (needsUpdate && ((_a2 = this.renderer) == null ? void 0 : _a2.debugLog)) {
|
|
2210
4725
|
const position = this.camera.position.toArray().map((value) => value.toFixed(2)).join(", ");
|
|
2211
4726
|
const target = this._target.toArray().map((value) => value.toFixed(2)).join(", ");
|
|
2212
4727
|
const spherical = [this._spherical.theta * RAD2DEG, this._spherical.phi * RAD2DEG, this._spherical.radius].map((value) => value.toFixed(2)).join(", ");
|
|
@@ -2234,9 +4749,10 @@ class CameraSystem {
|
|
|
2234
4749
|
this.camera = new PerspectiveCamera(90, w / (h || 1));
|
|
2235
4750
|
this.camera.up.set(0, 0, -1);
|
|
2236
4751
|
this.zoomIdentityDistance = h / 2;
|
|
2237
|
-
this.camera.position.
|
|
4752
|
+
this.camera.position.z = this.zoomIdentityDistance;
|
|
2238
4753
|
this.controller = new CameraController(this.camera, renderer);
|
|
2239
|
-
|
|
4754
|
+
this.controller.polarAngle = 0;
|
|
4755
|
+
this.controller.distance = this.zoomIdentityDistance;
|
|
2240
4756
|
}
|
|
2241
4757
|
/** Current camera instance. */
|
|
2242
4758
|
get currentCamera() {
|
|
@@ -2277,9 +4793,9 @@ class CameraSystem {
|
|
|
2277
4793
|
this.syncController(minDistance, maxDistance, zoomFactor);
|
|
2278
4794
|
}
|
|
2279
4795
|
computeCameraClipPlanes(minDistance, maxDistance, nearSafetyFactor = 0.5, farSafetyFactor = 1.5) {
|
|
2280
|
-
const fov = this.camera.fov * DEG2RAD;
|
|
4796
|
+
const fov = this.camera.fov * DEG2RAD$1;
|
|
2281
4797
|
const aspect = this.camera.aspect;
|
|
2282
|
-
const maxPolarAngle = 85 * DEG2RAD;
|
|
4798
|
+
const maxPolarAngle = 85 * DEG2RAD$1;
|
|
2283
4799
|
const halfFovY = fov / 2;
|
|
2284
4800
|
const halfFovX = Math.atan(Math.tan(halfFovY) * aspect);
|
|
2285
4801
|
const diagonalFov = 2 * Math.atan(Math.sqrt(Math.tan(halfFovX) ** 2 + Math.tan(halfFovY) ** 2));
|
|
@@ -2412,6 +4928,10 @@ class ViewportSystem {
|
|
|
2412
4928
|
get scaleFactor() {
|
|
2413
4929
|
return this.sceneSystem.scaleFactor;
|
|
2414
4930
|
}
|
|
4931
|
+
/** Pixel to SVG scale factor */
|
|
4932
|
+
get pxToSvgScale() {
|
|
4933
|
+
return 1 / (this.scaleFactor * this.zoomFactor);
|
|
4934
|
+
}
|
|
2415
4935
|
/**
|
|
2416
4936
|
* Initializes the viewport and zoom bounds with the given scene definition.
|
|
2417
4937
|
* @param sceneDef {@link SceneDef} scene definition
|
|
@@ -2419,13 +4939,6 @@ class ViewportSystem {
|
|
|
2419
4939
|
initViewport(sceneDef) {
|
|
2420
4940
|
this.sceneSystem.initScene(sceneDef.viewbox);
|
|
2421
4941
|
this.cameraSystem.initCamera([0.1, sceneDef.viewbox.size.width > 1e5 ? 100 : 35]);
|
|
2422
|
-
if (sceneDef.bounds) {
|
|
2423
|
-
const boundary = new Box3(
|
|
2424
|
-
new Vector3(sceneDef.bounds.min.x, sceneDef.bounds.min.y, 0),
|
|
2425
|
-
new Vector3(sceneDef.bounds.max.x, sceneDef.bounds.max.y, 0)
|
|
2426
|
-
).applyMatrix4(this.sceneSystem.worldMatrix);
|
|
2427
|
-
this.cameraController.setBoundary(boundary);
|
|
2428
|
-
}
|
|
2429
4942
|
}
|
|
2430
4943
|
/** Updates the viewport when the renderer size changes. */
|
|
2431
4944
|
updateViewport() {
|
|
@@ -2436,8 +4949,7 @@ class ViewportSystem {
|
|
|
2436
4949
|
* Recalculates the svg to pixel scale factor and emits the event if necessary.
|
|
2437
4950
|
*/
|
|
2438
4951
|
updatePtScale() {
|
|
2439
|
-
const
|
|
2440
|
-
const pxToSvgScale = 1 / denominator;
|
|
4952
|
+
const pxToSvgScale = this.pxToSvgScale;
|
|
2441
4953
|
if (Math.abs(pxToSvgScale - (this.prevPxToSvgScale ?? 0)) < this.pxToSvgScaleThreshold) return;
|
|
2442
4954
|
if (this.renderer.debugLog) console.log("pxToSvgScale", +pxToSvgScale.toFixed(3));
|
|
2443
4955
|
this.eventSystem.emit("viewport:ptscale", pxToSvgScale);
|
|
@@ -2464,6 +4976,15 @@ class ViewportSystem {
|
|
|
2464
4976
|
svg3D.applyMatrix4(this.sceneSystem.worldMatrix);
|
|
2465
4977
|
return new Vector2(svg3D.x, svg3D.y);
|
|
2466
4978
|
}
|
|
4979
|
+
/**
|
|
4980
|
+
* Converts a point from world coordinates to SVG coordinates. Z axis is ignored.
|
|
4981
|
+
* @param worldCoords Point in world coordinates
|
|
4982
|
+
* @returns Point in SVG coordinates
|
|
4983
|
+
*/
|
|
4984
|
+
worldToSvg(worldCoords) {
|
|
4985
|
+
const svgCoords = worldCoords.clone().applyMatrix4(this.sceneSystem.inverseWorldMatrix);
|
|
4986
|
+
return new Vector2(svgCoords.x, svgCoords.y);
|
|
4987
|
+
}
|
|
2467
4988
|
/**
|
|
2468
4989
|
* Converts a point from screen coordinates to the given coordinate space.
|
|
2469
4990
|
* @param space Space to convert to (either "svg" or "world")
|
|
@@ -2592,7 +5113,7 @@ class ControlsSystem {
|
|
|
2592
5113
|
* @returns Promise that resolves when the roll animation completes
|
|
2593
5114
|
*/
|
|
2594
5115
|
rollBy(angle, immediate) {
|
|
2595
|
-
const angleRad = -angle * DEG2RAD;
|
|
5116
|
+
const angleRad = -angle * DEG2RAD$1;
|
|
2596
5117
|
const spherical = this.controller.getSpherical(new Spherical());
|
|
2597
5118
|
const azimuthAngle = spherical.theta;
|
|
2598
5119
|
const newAzimuthAngle = azimuthAngle + angleRad;
|
|
@@ -2606,7 +5127,7 @@ class ControlsSystem {
|
|
|
2606
5127
|
* @returns Promise that resolves when the roll animation completes
|
|
2607
5128
|
*/
|
|
2608
5129
|
rollTo(angle, immediate) {
|
|
2609
|
-
const targetAngleRad = -angle * DEG2RAD;
|
|
5130
|
+
const targetAngleRad = -angle * DEG2RAD$1;
|
|
2610
5131
|
const spherical = this.controller.getSpherical(new Spherical());
|
|
2611
5132
|
const azimuthAngle = spherical.theta;
|
|
2612
5133
|
const deltaAngleRad = shortestRotationAngle(targetAngleRad, azimuthAngle);
|
|
@@ -2622,7 +5143,7 @@ class ControlsSystem {
|
|
|
2622
5143
|
* @returns Promise that resolves when the pitch animation completes
|
|
2623
5144
|
*/
|
|
2624
5145
|
pitchBy(angle, immediate) {
|
|
2625
|
-
const angleRad = angle * DEG2RAD;
|
|
5146
|
+
const angleRad = angle * DEG2RAD$1;
|
|
2626
5147
|
const spherical = this.controller.getSpherical(new Spherical());
|
|
2627
5148
|
const polarAngle = spherical.phi;
|
|
2628
5149
|
const newPolarAngle = polarAngle + angleRad;
|
|
@@ -2637,7 +5158,7 @@ class ControlsSystem {
|
|
|
2637
5158
|
* @returns Promise that resolves when the pitch animation completes
|
|
2638
5159
|
*/
|
|
2639
5160
|
pitchTo(angle, immediate) {
|
|
2640
|
-
const angleRad = angle * DEG2RAD;
|
|
5161
|
+
const angleRad = angle * DEG2RAD$1;
|
|
2641
5162
|
const clampedAngleRad = MathUtils.euclideanModulo(angleRad, Math.PI / 2);
|
|
2642
5163
|
return this.controller.rotatePolarTo(clampedAngleRad, !immediate);
|
|
2643
5164
|
}
|
|
@@ -2669,6 +5190,34 @@ class ControlsSystem {
|
|
|
2669
5190
|
if (options.rollTime !== void 0) controller.azimuthTime = options.rollTime;
|
|
2670
5191
|
if (options.pitchTime !== void 0) controller.polarTime = options.pitchTime;
|
|
2671
5192
|
}
|
|
5193
|
+
/**
|
|
5194
|
+
* Sets the camera bounds to restrict camera movement.
|
|
5195
|
+
* Note: This method must be called after the viewport is initialized by `renderer.start()`.
|
|
5196
|
+
* @param rect Rectangle in SVG coordinates defining the camera boundary or `undefined` to remove the boundary
|
|
5197
|
+
*/
|
|
5198
|
+
setCameraBounds(rect) {
|
|
5199
|
+
if (!rect) {
|
|
5200
|
+
this.controller.setBoundary(void 0);
|
|
5201
|
+
return;
|
|
5202
|
+
}
|
|
5203
|
+
const worldMin = this.viewportSystem.svgToWorld(rect.min);
|
|
5204
|
+
const worldMax = this.viewportSystem.svgToWorld(rect.max);
|
|
5205
|
+
const boundary = new Box3(new Vector3(worldMin.x, worldMin.y, 0), new Vector3(worldMax.x, worldMax.y, 0));
|
|
5206
|
+
this.controller.setBoundary(boundary);
|
|
5207
|
+
}
|
|
5208
|
+
/**
|
|
5209
|
+
* Gets the current camera state.
|
|
5210
|
+
* @returns Camera state object with center, zoom, roll, pitch, and ptScale
|
|
5211
|
+
*/
|
|
5212
|
+
getCameraState() {
|
|
5213
|
+
const target = this.controller.getTarget(new Vector3());
|
|
5214
|
+
const center = this.viewportSystem.worldToSvg(target);
|
|
5215
|
+
const zoom = this.viewportSystem.zoomFactor;
|
|
5216
|
+
const roll = this.interactionsSystem.bearing * RAD2DEG;
|
|
5217
|
+
const pitch = this.controller.polarAngle * RAD2DEG;
|
|
5218
|
+
const ptScale = this.viewportSystem.pxToSvgScale;
|
|
5219
|
+
return { center, zoom, roll, pitch, ptScale };
|
|
5220
|
+
}
|
|
2672
5221
|
}
|
|
2673
5222
|
function asControlsAPI(controlsSystem) {
|
|
2674
5223
|
return {
|
|
@@ -2682,7 +5231,9 @@ function asControlsAPI(controlsSystem) {
|
|
|
2682
5231
|
pitchBy: controlsSystem.pitchBy.bind(controlsSystem),
|
|
2683
5232
|
pitchTo: controlsSystem.pitchTo.bind(controlsSystem),
|
|
2684
5233
|
resetCamera: controlsSystem.resetCamera.bind(controlsSystem),
|
|
2685
|
-
configure: controlsSystem.configure.bind(controlsSystem)
|
|
5234
|
+
configure: controlsSystem.configure.bind(controlsSystem),
|
|
5235
|
+
setCameraBounds: controlsSystem.setCameraBounds.bind(controlsSystem),
|
|
5236
|
+
getCameraState: controlsSystem.getCameraState.bind(controlsSystem)
|
|
2686
5237
|
};
|
|
2687
5238
|
}
|
|
2688
5239
|
function shortestRotationAngle(targetAngle, sourceAngle) {
|
|
@@ -3033,13 +5584,13 @@ class PitchHandler extends Handler {
|
|
|
3033
5584
|
this.lastPoints = [p0, p1];
|
|
3034
5585
|
const yDeltaAverage = (vectorA.y + vectorB.y) / 2;
|
|
3035
5586
|
const degreesPerPixelMoved = -0.5;
|
|
3036
|
-
const deltaAngle = yDeltaAverage * degreesPerPixelMoved * DEG2RAD;
|
|
5587
|
+
const deltaAngle = yDeltaAverage * degreesPerPixelMoved * DEG2RAD$1;
|
|
3037
5588
|
void this.controller.rotatePolarTo(this.controller.polarAngle + deltaAngle, false);
|
|
3038
5589
|
});
|
|
3039
5590
|
this.updatePolarAngles();
|
|
3040
5591
|
}
|
|
3041
5592
|
reset(enableTransition = true) {
|
|
3042
|
-
const polarAngle = this.enabled ? this.minPitch * DEG2RAD : 0;
|
|
5593
|
+
const polarAngle = this.enabled ? this.minPitch * DEG2RAD$1 : 0;
|
|
3043
5594
|
return this.controller.rotatePolarTo(polarAngle, enableTransition);
|
|
3044
5595
|
}
|
|
3045
5596
|
/**
|
|
@@ -3074,8 +5625,8 @@ class PitchHandler extends Handler {
|
|
|
3074
5625
|
* Update controller polar angles based on current configuration
|
|
3075
5626
|
*/
|
|
3076
5627
|
updatePolarAngles() {
|
|
3077
|
-
this.controller.minPolarAngle = this.minPitch * DEG2RAD;
|
|
3078
|
-
this.controller.maxPolarAngle = this.maxPitch * DEG2RAD;
|
|
5628
|
+
this.controller.minPolarAngle = this.minPitch * DEG2RAD$1;
|
|
5629
|
+
this.controller.maxPolarAngle = this.maxPitch * DEG2RAD$1;
|
|
3079
5630
|
if (this.controller.polarAngle < this.controller.minPolarAngle) {
|
|
3080
5631
|
void this.controller.rotatePolarTo(this.controller.minPolarAngle, false);
|
|
3081
5632
|
}
|
|
@@ -3146,7 +5697,7 @@ class RollHandler extends Handler {
|
|
|
3146
5697
|
this.isRolling = true;
|
|
3147
5698
|
this.prevAngle = e.rotation;
|
|
3148
5699
|
}
|
|
3149
|
-
const deltaAngle = (e.rotation - this.prevAngle) * -DEG2RAD;
|
|
5700
|
+
const deltaAngle = (e.rotation - this.prevAngle) * -DEG2RAD$1;
|
|
3150
5701
|
this.prevAngle = e.rotation;
|
|
3151
5702
|
if (Math.abs(deltaAngle) < 1e-3) return;
|
|
3152
5703
|
this.setPivot(e);
|
|
@@ -3165,14 +5716,6 @@ class RollHandler extends Handler {
|
|
|
3165
5716
|
);
|
|
3166
5717
|
});
|
|
3167
5718
|
}
|
|
3168
|
-
/**
|
|
3169
|
-
* Get bearing angle between current camera orientation and true north (in radians).
|
|
3170
|
-
* Angle is in range [0, 2π), going clockwise from north.
|
|
3171
|
-
*/
|
|
3172
|
-
get bearing() {
|
|
3173
|
-
const tau = Math.PI * 2;
|
|
3174
|
-
return MathUtils$1.euclideanModulo(-this.controller.azimuthAngle, tau);
|
|
3175
|
-
}
|
|
3176
5719
|
reset(enableTransition = true) {
|
|
3177
5720
|
return this.controller.normalizeRotations().rotateAzimuthTo(0, enableTransition);
|
|
3178
5721
|
}
|
|
@@ -3312,8 +5855,15 @@ class InteractionsSystem {
|
|
|
3312
5855
|
this.handlerArray = Object.values(handlers);
|
|
3313
5856
|
this.handlers.pan.enable();
|
|
3314
5857
|
this.handlers.zoom.enable();
|
|
3315
|
-
|
|
3316
|
-
|
|
5858
|
+
}
|
|
5859
|
+
/**
|
|
5860
|
+
* Get bearing angle between current camera orientation and true north (in radians).
|
|
5861
|
+
* Angle is in range [0, 2π), going clockwise from north.
|
|
5862
|
+
*/
|
|
5863
|
+
// TODO: Move somewhere else
|
|
5864
|
+
get bearing() {
|
|
5865
|
+
const tau = Math.PI * 2;
|
|
5866
|
+
return MathUtils.euclideanModulo(-this.viewportSystem.cameraController.azimuthAngle, tau);
|
|
3317
5867
|
}
|
|
3318
5868
|
/**
|
|
3319
5869
|
* Update camera position and directions.
|
|
@@ -3328,9 +5878,9 @@ class InteractionsSystem {
|
|
|
3328
5878
|
needsUpdate = handler.update(delta) || needsUpdate;
|
|
3329
5879
|
}
|
|
3330
5880
|
}
|
|
3331
|
-
if (this.
|
|
3332
|
-
this.prevBearing = this.
|
|
3333
|
-
this.events.emit("navigation:roll", this.
|
|
5881
|
+
if (this.bearing !== this.prevBearing) {
|
|
5882
|
+
this.prevBearing = this.bearing;
|
|
5883
|
+
this.events.emit("navigation:roll", this.bearing);
|
|
3334
5884
|
}
|
|
3335
5885
|
return needsUpdate;
|
|
3336
5886
|
}
|
|
@@ -3417,7 +5967,7 @@ class Renderer {
|
|
|
3417
5967
|
__publicField(this, "needsRedraw", true);
|
|
3418
5968
|
__publicField(this, "memoryInfoExtension");
|
|
3419
5969
|
__publicField(this, "memoryInfo", "");
|
|
3420
|
-
var
|
|
5970
|
+
var _a2, _b;
|
|
3421
5971
|
const { canvas, gl, debugLog = false, ui } = opts;
|
|
3422
5972
|
this.canvas = canvas;
|
|
3423
5973
|
this.debugLog = debugLog;
|
|
@@ -3440,7 +5990,7 @@ class Renderer {
|
|
|
3440
5990
|
this.memoryInfoExtension = this.renderer.getContext().getExtension("GMAN_webgl_memory");
|
|
3441
5991
|
this.canvas.addEventListener("webglcontextlost", (e) => this.onContextLost(e), false);
|
|
3442
5992
|
this.canvas.addEventListener("webglcontextrestored", (e) => this.onContextRestored(e), false);
|
|
3443
|
-
void ((_b = (
|
|
5993
|
+
void ((_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.init(this.renderer.getContext()));
|
|
3444
5994
|
}
|
|
3445
5995
|
/**
|
|
3446
5996
|
* {@link ControlsAPI} instance for controlling the viewport
|
|
@@ -3531,8 +6081,8 @@ class Renderer {
|
|
|
3531
6081
|
* Main rendering loop
|
|
3532
6082
|
*/
|
|
3533
6083
|
render() {
|
|
3534
|
-
var
|
|
3535
|
-
(_b = (
|
|
6084
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
6085
|
+
(_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.begin();
|
|
3536
6086
|
if (this.gl !== void 0) this.renderer.resetState();
|
|
3537
6087
|
else this.resizeCanvasToDisplaySize();
|
|
3538
6088
|
this.viewportSystem.updatePtScale();
|
|
@@ -3563,8 +6113,8 @@ class Renderer {
|
|
|
3563
6113
|
}
|
|
3564
6114
|
}
|
|
3565
6115
|
updateMemoryInfo() {
|
|
3566
|
-
var
|
|
3567
|
-
if (this.memoryInfoExtension && ((
|
|
6116
|
+
var _a2;
|
|
6117
|
+
if (this.memoryInfoExtension && ((_a2 = this.ui) == null ? void 0 : _a2.memoryInfoPanel)) {
|
|
3568
6118
|
const memoryInfo = this.memoryInfoExtension.getMemoryInfo();
|
|
3569
6119
|
const memoryInfoContent = JSON.stringify(memoryInfo.memory, null, 2);
|
|
3570
6120
|
const elapsedTime = this.clock.getElapsedTime() * 1e3;
|