control/CameraLock.tstypescript
import { Control, type IControlParams } from "./Control";
import { LonLat } from "../LonLat";
import { Vec3 } from "../math/Vec3";
import { createEvents, type EventsHandler } from "../Events";
import { Entity } from "../entity/Entity";
import type { IMouseState } from "../renderer/RendererEvents";
import { Navigation } from "./Navigation";
import { Planet } from "../scene/Planet";
import { input } from "../input/input";
import { RADIANS } from "../math";
import { PlanetCamera } from "../camera";
const MIN_LOCK_DISTANCE = 0.001;
const MIN_VIEW_DISTANCE = 120.0;
type CameraLockEventsList = ["lockview", "unlockview"];
const CameraLockEvents: CameraLockEventsList = ["lockview", "unlockview"];
interface ICameraLockParams extends IControlParams {
planet?: Planet | null;
}
export class CameraLock extends Control {
public events: EventsHandler<CameraLockEventsList>;
protected _lockDistance: number;
protected _isFromTheBack: boolean;
protected _lockEntity: Entity | null;
protected _viewDir: Vec3;
constructor(options: ICameraLockParams = {}) {
super(options);
this.events = createEvents(CameraLockEvents);
this._name = "CameraLock";
this.planet = options.planet || null;
this._lockDistance = 0;
this._isFromTheBack = false;
this._lockEntity = null;
this._viewDir = new Vec3(0, 0, 0);
}
public override onactivate() {
if (this.renderer) {
}
}
public override ondeactivate() {
if (this.renderer) {
this.unlockView();
}
}
public override oninit() {
this.activate();
if (this.renderer) {
}
}
public flyCartesian(cartesian: Vec3, dist = MIN_VIEW_DISTANCE) {
if (cartesian.isZero() || !this.renderer) {
return;
}
this.unlockView();
if (this.planet) {
let cam = this.planet.camera;
if (this.isVisibleDistance(cartesian)) {
cam.flyDistance(cartesian, dist);
}
if (cam.eye.distance(cartesian) < 1000000.0) {
cam.flyDistance(cartesian, dist);
} else {
cam.viewDistance(cartesian, dist);
}
} else {
this.renderer.activeCamera.viewDistance(cartesian, dist);
}
}
public lockView(entity: Entity, fromTheBack = false) {
if (!this.renderer) return;
this._lockDistance = this._getDistance(entity, this._lockEntity);
this._isFromTheBack = fromTheBack;
this._deactivateLockViewEvents();
this._lockEntity = entity;
let cam = this.renderer.activeCamera;
if (this.planet) {
this.planet.camera.stopFlying();
}
cam.viewDistance(entity.getAbsoluteCartesian(), this._lockDistance);
if (cam.isOrthographic) {
cam.focusDistance = this._lockDistance;
}
this._deactivateNav();
this._activateLockViewEvents();
this._viewDir = entity.getAbsoluteCartesian().sub(cam.eye).normalize();
this.events.dispatch(this.events.lockview, this._lockEntity, fromTheBack);
}
protected _activateNav() {
if (this.renderer && this.renderer.controls.navigation) {
this.renderer.controls.navigation.activate();
}
if (this.renderer && this.renderer.controls.simpleNavigation) {
this.renderer.controls.simpleNavigation.activate();
}
}
protected _deactivateNav() {
if (this.renderer && this.renderer.controls.navigation) {
this.renderer.controls.navigation.deactivate();
(this.renderer.controls.navigation as Navigation).stop();
}
if (this.renderer && this.renderer.controls.simpleNavigation) {
this.renderer.controls.simpleNavigation.deactivate();
}
}
public unlockView() {
if (this._lockEntity) {
this._deactivateLockViewEvents();
this._activateNav();
this.events.dispatch(this.events.unlockview, this._lockEntity);
this._lockEntity = null;
}
}
private _getCenterDist(): number {
if (this.renderer) {
return this.renderer.getDistanceFromPixel(this.renderer.handler.getCenter()) || 0;
}
return 0;
}
protected _getDistance(entity: Entity, prevEntity?: Entity | null): number {
if (this.renderer) {
let cartesian = entity.getAbsoluteCartesian();
let cam = this.renderer.activeCamera;
let dist = cam.eye.distance(cartesian);
if (prevEntity) {
dist = cam.eye.distance(prevEntity.getAbsoluteCartesian());
}
if (dist <= MIN_LOCK_DISTANCE) {
return MIN_VIEW_DISTANCE;
}
if (this.isVisibleDistance(cartesian)) {
return dist;
}
if (cam.eye.distance(cartesian) < 1000000.0) {
return this._getCenterDist();
} else {
return MIN_VIEW_DISTANCE;
}
}
return 0;
}
public isVisibleDistance(cart: Vec3, C: number = 0.0): boolean {
if (this.planet) {
let R = this.planet.ellipsoid.equatorialSize;
let eye = this.planet.camera.eye;
let camDist = eye.distance(cart);
return camDist < Math.sqrt(eye.length2() - R * R) + C;
}
return true;
}
public get lockEntity() {
return this._lockEntity;
}
public flyLonLat(lonLat: LonLat, dist = MIN_VIEW_DISTANCE) {
if (this.planet) {
let cartesian = this.planet.ellipsoid.lonLatToCartesian(lonLat);
this.flyCartesian(cartesian, dist);
}
}
protected _activateLockViewEvents() {
if (this.renderer) {
this.renderer.events.on("mousewheel", this._onMouseWheel);
this.renderer.events.on("mousemove", this._onMouseMove);
this.renderer.events.on("predraw", this._onLockViewDraw);
}
}
protected _deactivateLockViewEvents() {
if (this.renderer) {
this.renderer.events.off("mousewheel", this._onMouseWheel);
this.renderer.events.off("mousemove", this._onMouseMove);
if (this._lockEntity) {
this.renderer.events.off("predraw", this._onLockViewDraw);
}
}
}
private _onLockViewDraw = () => {
if (this.renderer && this._lockEntity) {
if (this._isFromTheBack) {
// let cam = this.planet.camera;
// let vehPos = this._lockEntity.getCartesian(),
// vehDir = this._lockEntity.direction;
// let currPos = vehPos.sub(this._viewDir.scaleTo(this._lockDistance));
// let dh = new Vec3();
// let currPosProj = new Plane(vehPos, vehPos.normal()).getProjectionPoint(currPos, dh);
// let projViewDir = vehPos.sub(currPosProj);
// Quat.getRotationBetweenVectorsRes(projViewDir.normal(), vehDir, qOrientationRot);
// let rot = qOrientationRot.slerp(Quat.IDENTITY, 1.0 - t).normalize();
// let projViewDir_t = rot.mulVec3(projViewDir.normal());
// projViewDir = projViewDir_t.scale(projViewDir.length());
// let newPos = vehPos.add(dh).sub(projViewDir);
// this._viewDir = vehPos.sub(newPos).normalize();
// cam.set(newPos, vehPos, vehPos.normal());
// cam.update();
} else {
const cam = this.renderer.activeCamera;
cam.viewDistance(this._lockEntity.getAbsoluteCartesian(), this._lockDistance);
if (cam.isOrthographic) {
cam.focusDistance = this._lockDistance;
}
}
}
};
protected _onMouseWheel = (e: IMouseState) => {
if (this.renderer && this._lockEntity) {
if (this._isFromTheBack) {
//...
} else {
const cam = this.renderer.activeCamera;
let d = cam.eye.distance(this._lockEntity.getAbsoluteCartesian());
this._lockDistance -= 0.33 * d * Math.sign(e.wheelDelta);
if (this._lockDistance < MIN_LOCK_DISTANCE) {
this._lockDistance = MIN_LOCK_DISTANCE;
}
cam.viewDistance(this._lockEntity.getAbsoluteCartesian(), this._lockDistance);
if (cam.isOrthographic) {
cam.focusDistance = this._lockDistance;
}
}
}
};
protected _onMouseMove = (ms: IMouseState) => {
if (this._lockEntity && this.renderer) {
if (ms.rightButtonDown || this.renderer.events.isKeyPressed(input.KEY_ALT)) {
let p = this._lockEntity.getAbsoluteCartesian(),
cam = this.renderer.activeCamera,
l = 0.5 / RADIANS;
if (l > 0.007) l = 0.007;
if (this.planet) {
cam.rotateHorizontal(l * (ms.x - ms.prev_x), false, p, p.isZero() ? Vec3.UP : p.normal());
const pc = cam as PlanetCamera;
const savedEye = cam.eye.clone();
const savedU = cam._u.clone();
const savedR = cam._r.clone();
const savedB = cam._b.clone();
const savedF = cam._f.clone();
pc.rotateVertical(l * (ms.y - ms.prev_y), p);
pc.update();
pc.setTerrainCollisionActivity(false);
pc.checkTerrainCollision();
pc.setTerrainCollisionActivity(true);
if (pc.getAltitude() < pc.minAltitude) {
cam.eye.copy(savedEye);
cam._u.copy(savedU);
cam._r.copy(savedR);
cam._b.copy(savedB);
cam._f.copy(savedF);
pc.update();
}
} else {
cam.rotateHorizontal(l * (ms.x - ms.prev_x), false, p, Vec3.UP);
cam.rotateVertical(l * (ms.y - ms.prev_y), p);
}
this._viewDir = p.sub(cam.eye).normalize();
}
}
};
}