@itwin/core-frontend
Version:
iTwin.js frontend components
140 lines • 8.75 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Views
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FrustumAnimator = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const Viewport_1 = require("./Viewport");
/**
* Compute an intermediate eye point as it swings around a moving target with rotating axes and varying distance to target.
* (eye, target, distance) is redundant -- implementation problem is to figure out which to use for compatibility with subsequent view setup.
*/
function interpolateSwingingEye(axes0, eye0, distance0, axes1, eye1, distance1, fraction, axesAtFraction) {
const z0 = axes0.rowZ();
const z1 = axes1.rowZ();
const zA = axesAtFraction.rowZ();
// back up from the eye points to the targets.
const target0 = eye0.plusScaled(z0, -distance0);
const target1 = eye1.plusScaled(z1, -distance1);
// RULE: Target point moves by simple interpolation
const targetA = target0.interpolate(fraction, target1);
// RULE: Distance from target to eye is simple interpolation
const distanceA = core_geometry_1.Geometry.interpolate(distance0, fraction, distance1);
// The interpolated target, interpolated distance, and specified axes give the intermediate eyepoint.
const eyeA = targetA.plusScaled(zA, distanceA);
return {
target: targetA,
eye: eyeA,
distance: distanceA,
};
}
/** Animates the transition of a [[Viewport]] from one [Frustum]($common) to another. The viewport will render as many frames as necessary during the supplied duration.
* @see [[Viewport.animateFrustumChange]] to conveniently animate a viewport from one frustum to another.
* @public
* @extensions
*/
class FrustumAnimator {
options;
_tweens = new core_common_1.Tweens();
_duration = 0;
/** Construct an animator that animates from `begin` to `end`. */
constructor(options, viewport, begin, end) {
this.options = options;
const settings = Viewport_1.ScreenViewport.animation;
const zoomSettings = settings.zoomOut;
let duration = undefined !== options.animationTime ? options.animationTime : settings.time.normal.milliseconds;
if (duration <= 0 || begin.cameraOn !== end.cameraOn) // no duration means skip animation. We can't animate if the camera toggles.
return;
this._duration = duration;
let extentBias;
let eyeBias;
const zVec = begin.zVec;
const view = viewport.view;
const view3 = view;
const begin3 = begin;
const end3 = end;
const beginTarget = begin.target;
const endTarget = end.target;
const axis = (0, core_bentley_1.expectDefined)(end.rotation.multiplyMatrixMatrixInverse(begin.rotation)).getAxisAndAngleOfRotation(); // axis to rotate begin to get to end
const timing = { fraction: 0.0, height: 0, position: 0 }; // updated by tween.
// don't do "zoom out" if the two views aren't pointing in the same direction, or if they request cancelOnAbort (since that implies that the view
// is a linear interpolation from begin to end), or if it's disabled.
if (zoomSettings.enable && !options.cancelOnAbort && zVec.isAlmostEqual(end.zVec)) {
view.applyPose(end); // start with the pose at the end
const viewTransform = core_geometry_1.Transform.createOriginAndMatrix(undefined, view.getRotation());
const endRange = core_geometry_1.Range3d.createTransformedArray(viewTransform, view.calculateFocusCorners()); // get the view-aligned range of the focus plane at the end
const beginRange = core_geometry_1.Range3d.createTransformedArray(viewTransform, view.applyPose(begin).calculateFocusCorners()); // get the view-aligned range of the focus plane at the beginning
// do the starting and ending views (plus the margin) overlap? If not we need to zoom out to show how to get from one to the other
const expand = (range) => {
const r = range.clone();
r.scaleAboutCenterInPlace(zoomSettings.margin);
return r;
};
if (!expand(beginRange).intersectsRangeXY(expand(endRange))) {
view3.lookAtViewAlignedVolume(beginRange.union(endRange), viewport.viewRect.aspect); // set up a view that would show both extents
duration *= zoomSettings.durationFactor; // increase duration so the zooming isn't too fast
extentBias = view.getExtents().minus(begin.extents); // if the camera is off, the "bias" is the amount the union-ed view is larger than the starting view
if (begin.cameraOn)
eyeBias = zVec.scaleToLength(zVec.dotProduct(begin3.camera.eye.vectorTo(view3.camera.eye))); // if the camera is on, the bias is the difference in height of the two eye positions
}
}
this._tweens.create(timing, {
to: { fraction: 1.0, height: zoomSettings.heights, position: zoomSettings.positions },
duration,
start: true,
easing: options.easingFunction ? options.easingFunction : settings.easing,
interpolation: zoomSettings.interpolation,
onComplete: () => viewport.setupFromView(end), // when we're done, set up from final state
onUpdate: () => {
const fraction = extentBias ? timing.position : timing.fraction; // if we're zooming, fraction comes from position interpolation
const rot = (0, core_bentley_1.expectDefined)(core_geometry_1.Matrix3d.createRotationAroundVector(axis.axis, core_geometry_1.Angle.createDegrees(fraction * axis.angle.degrees))).multiplyMatrixMatrix(begin.rotation);
if (begin.cameraOn) {
const newExtents = begin.extents.interpolate(fraction, end.extents);
if (undefined !== eyeBias) {
const eyePoint = begin3.camera.eye.interpolate(fraction, end3.camera.eye);
eyePoint.plusScaled(eyeBias, timing.height, eyePoint);
const targetPoint = eyePoint.plusScaled(rot.getRow(2), -1.0 * (core_geometry_1.Geometry.interpolate(begin3.camera.focusDist, fraction, end3.camera.focusDist)));
view3.lookAt({ eyePoint, targetPoint, upVector: rot.getRow(1), newExtents });
}
else {
const data = interpolateSwingingEye(begin3.rotation, begin3.camera.eye, begin3.camera.focusDist, end3.rotation, end3.camera.eye, end3.camera.focusDist, fraction, rot);
view3.lookAt({ eyePoint: data.eye, targetPoint: data.target, upVector: rot.getRow(1), newExtents });
}
}
else {
const extents = begin.extents.interpolate(timing.fraction, end.extents);
if (undefined !== extentBias)
extents.plusScaled(extentBias, timing.height, extents); // no camera, zooming out expands extents
view.setExtents(extents);
view.setRotation(rot);
view.setCenter(beginTarget.interpolate(fraction, endTarget)); // must be done last - depends on extents and rotation
}
viewport.setupFromView();
},
});
}
/** @internal */
animate() {
const didFinish = !this._tweens.update();
if (didFinish && this.options.animationFinishedCallback)
this.options.animationFinishedCallback(true);
return didFinish;
}
/** @internal */
interrupt() {
// We were interrupted. Either go to: the final frame (normally) or, add a small fraction of the total duration (30ms for a .5 second duration) to
// the current time for cancelOnAbort. That makes aborted animations show some progress, as happens when the mouse wheel rolls quickly.
this._tweens.update(this.options.cancelOnAbort ? Date.now() + (this._duration * .06) : Infinity);
if (this.options.animationFinishedCallback)
this.options.animationFinishedCallback(false);
}
}
exports.FrustumAnimator = FrustumAnimator;
//# sourceMappingURL=FrustumAnimator.js.map