UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

260 lines 11.7 kB
import { iObjectCommons } from './iObjectCommons'; import { Vector3 } from 'three'; import { CameraView } from '../camera/CameraView'; export const iCameraCommons = { setDirty: function (options) { if (!this._positionWorld) return; // not initialized yet // noinspection SuspiciousTypeOfGuard it can be string when called from bindToValue const isStr = typeof options === 'string'; const changeKey = isStr ? options : options?.key; if (!changeKey || ['zoom', 'fov', 'left', 'right', 'top', 'bottom', 'aspect', 'frustumSize'].includes(changeKey)) this.updateProjectionMatrix(); if (isStr) options = undefined; this.getWorldPosition(this._positionWorld); // console.log('target', target, this._controls, this._camera) // noinspection PointlessBooleanExpressionJS if (this.controls && this.controls.target && this.controls.enabled !== false && this.target !== this.controls.target) { this.controls.target.copy(this.target); // this.controls.update() // this should be done automatically postFrame } // if (!this.controls || !this.controls.enabled) { else if (this.userData.autoLookAtTarget) { this.lookAt(this.target); } // todo refresh target on rotation change if autoLookAtTarget is false? (calculate distanceToTarget from the current/prev target and position this.dispatchEvent({ ...options, type: 'update', bubbleToParent: false, camera: this }); // does not bubble this.dispatchEvent({ ...options, type: 'cameraUpdate', bubbleToParent: true, camera: this }); // this sets dirty in the viewer iObjectCommons.setDirty.call(this, { refreshScene: false, ...options }); }, activateMain: function (options = {}, _internal = false, _refresh = true, canvas) { if (!_internal) { if (options.camera === null) return this.deactivateMain(options, _internal, _refresh); return this.dispatchEvent({ type: 'activateMain', ...options, camera: this, bubbleToParent: true, }); } // this will be used by RootScene to deactivate other cameras and activate this one if (this.userData.__isMainCamera) return; this.userData.__isMainCamera = true; this.userData.__lastScale = this.scale.clone(); this.scale.divide(this.getWorldScale(new Vector3())); // make unit scale, for near far and all if (canvas) this.setCanvas(canvas, _refresh); else if (_refresh) { this.refreshCameraControls(false); this.refreshAspect(false); } this.setDirty({ change: 'activateMain', ...options }); // console.log({...this._camera.modelObject.position}) }, deactivateMain: function (options = {}, _internal = false, _refresh = true, clearCanvas = false) { if (!_internal) return this.dispatchEvent({ type: 'activateMain', ...options, camera: null, bubbleToParent: true, }); // this will be used by RootScene to deactivate other cameras and activate this one if (!this.userData.__isMainCamera) return; this.userData.__isMainCamera = false; // or delete? if (this.userData.__lastScale) { this.scale.copy(this.userData.__lastScale); delete this.userData.__lastScale; } if (clearCanvas) this.setCanvas(undefined, _refresh); else if (_refresh) this.refreshCameraControls(false); if (_refresh) { this.refreshCameraControls(false); } this.setDirty({ change: 'deactivateMain', ...options }); }, refreshUi: function () { // todo this.uiConfig?.uiRefresh?.(true, 'postFrame', 1); }, refreshTarget: function (distanceFromTarget = 4, setDirty = true) { if (this.controls?.enabled && this.controls.target) { if (this.controls.target !== this.target) this.target.copy(this.controls.target); } else { // this.cameraObject.updateWorldMatrix(true, false) this.getWorldDirection(this.target) // .transformDirection(this.cameraObject.matrixWorldInverse) // .multiplyScalar(distanceFromTarget).add(this._position) .multiplyScalar(distanceFromTarget).add(this.getWorldPosition(new Vector3())); // if (this.cameraObject.parent) this.cameraObject.parent.worldToLocal(this._target) } if (setDirty) this.setDirty({ change: 'target' }); }, refreshAspect: function (setDirty = true) { if (this.autoAspect) { if (!this._canvas) console.error('ICamera: cannot calculate aspect ratio without canvas/container'); else { let aspect = this._canvas.clientWidth / this._canvas.clientHeight; if (!isFinite(aspect)) aspect = 1; this.aspect = aspect; this.refreshFrustum && this.refreshFrustum(false); } } if (setDirty) this.setDirty({ change: 'aspect' }); }, updateShaderProperties: function (material) { material.uniforms.cameraPositionWorld?.value?.copy(this._positionWorld); material.uniforms.cameraNearFar?.value?.set(this.near, this.far); if (material.uniforms.projection) material.uniforms.projection.value = this.projectionMatrix; // todo: rename to projectionMatrix2? material.defines.PERSPECTIVE_CAMERA = this.type === 'PerspectiveCamera' ? '1' : '0'; material.defines.ORTHOGRAPHIC_CAMERA = this.type === 'OrthographicCamera' ? '1' : '0'; // todo return this; }, upgradeCamera: upgradeCamera, copy: (superCopy) => function (camera, recursive, distanceFromTarget, worldSpace, ...args) { if (!camera.isCamera) { console.error('ICamera.copy: camera is not a Camera', camera); return this; } superCopy.call(this, camera, recursive, ...args); // moved to setView in ThreeViewer // const worldPos = camera.getWorldPosition(this.position) // camera.getWorldQuaternion(this.quaternion) // if (this.parent) { // this.position.copy(this.parent.worldToLocal(worldPos)) // this.quaternion.premultiply(this.parent.quaternion.clone().invert()) // } if (camera.target?.isVector3) this.target.copy(camera.target); else { const minDistance = this.controls?.minDistance ?? distanceFromTarget ?? 4; camera.getWorldDirection(this.target).multiplyScalar(minDistance).add(this.getWorldPosition(new Vector3())); } if (worldSpace) { // default = false const worldPos = camera.getWorldPosition(this.position); // this.getWorldQuaternion(this.quaternion) // todo: do if autoLookAtTarget is false // todo up vector if (this.parent) { this.position.copy(this.parent.worldToLocal(worldPos)); // this.quaternion.premultiply(this.parent.quaternion.clone().invert()) } } this.updateMatrixWorld(true); this.updateProjectionMatrix(); this.refreshAspect(false); this.setDirty(); return this; }, getView: function (worldSpace = true, _view) { const up = new Vector3(); this.updateWorldMatrix(true, false); const matrix = this.matrixWorld; up.x = matrix.elements[4]; up.y = matrix.elements[5]; up.z = matrix.elements[6]; up.normalize(); const view = _view || new CameraView(); view.name = this.name; view.position.copy(this.position); view.target.copy(this.target); view.quaternion.copy(this.quaternion); view.zoom = this.zoom; // view.up.copy(up) const parent = this.parent; if (parent) { if (worldSpace) { view.position.applyMatrix4(parent.matrixWorld); this.getWorldQuaternion(view.quaternion); // target, up is already in world space } else { up.transformDirection(parent.matrixWorld.clone().invert()); // pos is already in local space // target should always be in world space } } view.isWorldSpace = worldSpace; return view; }, setView: function (view) { this.position.copy(view.position); this.target.copy(view.target); // this.up.copy(view.up) this.quaternion.copy(view.quaternion); this.zoom = view.zoom; this.setDirty(); }, // todo rename to setFromCamera? setViewFromCamera: function (camera, distanceFromTarget, worldSpace = true) { // todo: getView, setView can also be used, do we need copy? as that will copy all the properties this.copy(camera, undefined, distanceFromTarget, worldSpace); }, setViewToMain: function (eventOptions) { this.dispatchEvent({ type: 'setView', ...eventOptions, camera: this, bubbleToParent: true }); }, }; function upgradeCamera() { if (!this.isCamera) { console.error('Object is not a camera', this); return; } if (this.userData.__cameraSetup) return; this.userData.__cameraSetup = true; iObjectCommons.upgradeObject3D.call(this); this.copy = iCameraCommons.copy(this.copy); if (!this.target) this.target = new Vector3(); if (!this._positionWorld) this._positionWorld = new Vector3(); if (!this.refreshTarget) this.refreshTarget = iCameraCommons.refreshTarget; if (!this.refreshAspect) this.refreshAspect = iCameraCommons.refreshAspect; if (!this.updateShaderProperties) this.updateShaderProperties = iCameraCommons.updateShaderProperties; if (!this.activateMain) this.activateMain = iCameraCommons.activateMain; if (!this.deactivateMain) this.deactivateMain = iCameraCommons.deactivateMain; if (!this.refreshUi) this.refreshUi = iCameraCommons.refreshUi; if (!this.setDirty) this.setDirty = iCameraCommons.setDirty; // if (!this.controlsMode) this.controlsMode = '' if (!this.getView) this.getView = iCameraCommons.getView; if (!this.setView) this.setView = iCameraCommons.setView; if (!this.setViewFromCamera) this.setViewFromCamera = iCameraCommons.setViewFromCamera; if (!this.setViewToMain) this.setViewToMain = iCameraCommons.setViewToMain; if (!this.setCanvas) this.setCanvas = () => notSupported('setCanvas'); if (!this.setControlsCtor) this.setControlsCtor = () => notSupported('setControlsCtor'); if (!this.removeControlsCtor) this.removeControlsCtor = () => notSupported('removeControlsCtor'); if (!this.refreshCameraControls) this.refreshCameraControls = () => notSupported('refreshCameraControls'); if (!this.setInteractions) this.setInteractions = () => notSupported('setInteractions'); if (!this.dispose) this.dispose = () => notSupported('dispose'); this.assetType = 'camera'; // todo uiconfig, anything else? } function notSupported(n) { console.warn(`ICamera.${n} is not supported on this object. Please use objects of PerspectiveCamera2 or OrthographicCamera2 classes.`); } //# sourceMappingURL=iCameraCommons.js.map