|
|
|
@@ -0,0 +1,568 @@ |
|
|
|
import {Camera, Event, IUniform, Object3D, OrthographicCamera, Vector3} from 'three' |
|
|
|
import {generateUiConfig, uiInput, uiNumber, UiObjectConfig, uiToggle, uiVector} from 'uiconfig.js' |
|
|
|
import {onChange, onChange2, onChange3, serialize} from 'ts-browser-helpers' |
|
|
|
import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera' |
|
|
|
import {ICameraSetDirtyOptions} from '../ICamera' |
|
|
|
import type {ICameraControls, TControlsCtor} from './ICameraControls' |
|
|
|
import {OrbitControls3} from '../../three/controls/OrbitControls3' |
|
|
|
import {IObject3D} from '../IObject' |
|
|
|
import {ThreeSerialization} from '../../utils' |
|
|
|
import {iCameraCommons} from '../object/iCameraCommons' |
|
|
|
import {bindToValue} from '../../three/utils/decorators' |
|
|
|
import {makeICameraCommonUiConfig} from '../object/IObjectUi' |
|
|
|
import {CameraView, ICameraView} from './CameraView' |
|
|
|
|
|
|
|
// todo: extract out common functions with perspective camera into iCameraCommons |
|
|
|
// todo: maybe change domElement to some wrapper/base class of viewer |
|
|
|
export class OrthographicCamera2 extends OrthographicCamera implements ICamera { |
|
|
|
assetType = 'camera' as const |
|
|
|
get controls(): ICameraControls | undefined { |
|
|
|
return this._controls |
|
|
|
} |
|
|
|
|
|
|
|
@uiInput('Name') declare name: string |
|
|
|
|
|
|
|
@serialize('camControls') |
|
|
|
private _controls?: ICameraControls |
|
|
|
private _currentControlsMode: TCameraControlsMode = '' |
|
|
|
@onChange2(OrthographicCamera2.prototype.refreshCameraControls) |
|
|
|
controlsMode: TCameraControlsMode |
|
|
|
/** |
|
|
|
* It should be the canvas actually |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
private _canvas?: HTMLCanvasElement |
|
|
|
get isMainCamera(): boolean { |
|
|
|
return this.userData ? this.userData.__isMainCamera || false : false |
|
|
|
} |
|
|
|
|
|
|
|
@serialize() |
|
|
|
userData: ICameraUserData = {} |
|
|
|
|
|
|
|
@onChange3(OrthographicCamera2.prototype.setDirty) |
|
|
|
@uiNumber('Zoom') |
|
|
|
@serialize() declare zoom: number |
|
|
|
|
|
|
|
@onChange3(OrthographicCamera2.prototype.setDirty) |
|
|
|
@uiNumber<OrthographicCamera2>('Left', (t)=>({hidden: ()=>t._frustumSize !== undefined})) |
|
|
|
@serialize() declare left: number |
|
|
|
|
|
|
|
@onChange3(OrthographicCamera2.prototype.setDirty) |
|
|
|
@uiNumber<OrthographicCamera2>('Right', (t)=>({hidden: ()=>t._frustumSize !== undefined})) |
|
|
|
@serialize() declare right: number |
|
|
|
|
|
|
|
@onChange3(OrthographicCamera2.prototype.setDirty) |
|
|
|
@uiNumber<OrthographicCamera2>('Top', (t)=>({hidden: ()=>t._frustumSize !== undefined})) |
|
|
|
@serialize() declare top: number |
|
|
|
|
|
|
|
@onChange3(OrthographicCamera2.prototype.setDirty) |
|
|
|
@uiNumber<OrthographicCamera2>('Bottom', (t)=>({hidden: ()=>t._frustumSize !== undefined})) |
|
|
|
@serialize() declare bottom: number |
|
|
|
|
|
|
|
private _frustumSize: number | undefined = undefined |
|
|
|
|
|
|
|
/** |
|
|
|
* Frustum size of the camera. This is used to calculate bounds (left, right, top, bottom) based on aspect ratio. |
|
|
|
* Set to 0 (or negative) value to disable automatic, and to set the bounds manually. |
|
|
|
*/ |
|
|
|
@uiInput<OrthographicCamera2>('Frustum Size'/* , (t)=>({hidden: ()=>t.frustumSize === undefined})*/) |
|
|
|
get frustumSize(): number { |
|
|
|
return this._frustumSize ?? 0 |
|
|
|
} |
|
|
|
|
|
|
|
set frustumSize(value: number) { |
|
|
|
this._frustumSize = value <= 0 ? undefined : value |
|
|
|
this.refreshFrustum(false) |
|
|
|
this.setDirty() |
|
|
|
} |
|
|
|
|
|
|
|
// @onChange3(OrthographicCamera2.prototype.setDirty) |
|
|
|
// @serialize() declare focus: number |
|
|
|
|
|
|
|
// @onChange3(OrthographicCamera2.prototype.setDirty) |
|
|
|
// @uiSlider('FoV Zoom', [0.001, 10], 0.001) |
|
|
|
// @serialize() declare zoom: number |
|
|
|
|
|
|
|
@uiVector('Position', undefined, undefined, (that:OrthographicCamera2)=>({onChange: ()=>that.setDirty()})) |
|
|
|
@serialize() declare readonly position: Vector3 |
|
|
|
|
|
|
|
/** |
|
|
|
* The target position of the camera (where the camera looks at). Also syncs with the controls.target, so it's not required to set that separately. |
|
|
|
* Note: this is always in world-space |
|
|
|
* Note: {@link autoLookAtTarget} must be set to `true` to make the camera look at the target when no controls are enabled |
|
|
|
*/ |
|
|
|
@uiVector('Target', undefined, undefined, (that:OrthographicCamera2)=>({onChange: ()=>that.setDirty()})) |
|
|
|
@serialize() readonly target: Vector3 = new Vector3(0, 0, 0) |
|
|
|
|
|
|
|
/** |
|
|
|
* Automatically manage aspect ratio based on window/canvas size. |
|
|
|
* Defaults to `true` if {@link domElement}(canvas) is set. |
|
|
|
*/ |
|
|
|
@serialize() |
|
|
|
@onChange2(OrthographicCamera2.prototype.refreshAspect) |
|
|
|
@uiToggle('Auto Aspect') |
|
|
|
autoAspect: boolean |
|
|
|
|
|
|
|
/** |
|
|
|
* Aspect ratio to use when {@link frustumSize} is defined |
|
|
|
*/ |
|
|
|
@serialize() |
|
|
|
@onChange2(OrthographicCamera2.prototype.refreshAspect) |
|
|
|
@uiToggle<OrthographicCamera2>('Aspect Ratio', (t)=>({disabled: ()=>t.autoAspect})) |
|
|
|
aspect: number |
|
|
|
|
|
|
|
/** |
|
|
|
* Near clipping plane. |
|
|
|
* This is managed by RootScene for active cameras |
|
|
|
* To change the minimum that's possible set {@link minNearPlane} |
|
|
|
* To use a fixed value set {@link autoNearFar} to false and set {@link minNearPlane} |
|
|
|
*/ |
|
|
|
@onChange2(OrthographicCamera2.prototype._nearFarChanged) |
|
|
|
near = 0.01 |
|
|
|
|
|
|
|
/** |
|
|
|
* Far clipping plane. |
|
|
|
* This is managed by RootScene for active cameras |
|
|
|
* To change the maximum that's possible set {@link maxFarPlane} |
|
|
|
* To use a fixed value set {@link autoNearFar} to false and set {@link maxFarPlane} |
|
|
|
*/ |
|
|
|
@onChange2(OrthographicCamera2.prototype._nearFarChanged) |
|
|
|
far = 50 |
|
|
|
|
|
|
|
/** |
|
|
|
* Automatically make the camera look at the {@link target} on {@link setDirty} call |
|
|
|
* Defaults to false. Note that this must be set to true to make the camera look at the target without any controls |
|
|
|
*/ |
|
|
|
@bindToValue({obj: 'userData', onChange: 'setDirty'}) |
|
|
|
autoLookAtTarget = false // bound to userData so that it's saved in the glb. |
|
|
|
|
|
|
|
/** |
|
|
|
* Automatically manage near and far clipping planes based on scene size. |
|
|
|
*/ |
|
|
|
@bindToValue({obj: 'userData', onChange: 'setDirty'}) |
|
|
|
autoNearFar = true // bound to userData so that it's saved in the glb. |
|
|
|
|
|
|
|
/** |
|
|
|
* Minimum near clipping plane allowed. (Distance from camera) |
|
|
|
* Used in RootScene when {@link autoNearFar} is true. |
|
|
|
* @default 0.2 |
|
|
|
*/ |
|
|
|
@bindToValue({obj: 'userData', onChange: 'setDirty'}) |
|
|
|
minNearPlane = 0.5 |
|
|
|
|
|
|
|
/** |
|
|
|
* Maximum far clipping plane allowed. (Distance from camera) |
|
|
|
* Used in RootScene when {@link autoNearFar} is true. |
|
|
|
*/ |
|
|
|
@bindToValue({obj: 'userData', onChange: 'setDirty'}) |
|
|
|
maxFarPlane = 1000 |
|
|
|
|
|
|
|
constructor(controlsMode?: TCameraControlsMode, domElement?: HTMLCanvasElement, autoAspect?: boolean, frustumSize?: number, left?: number, right?: number, top?: number, bottom?: number, near?: number, far?: number, aspect?: number) { |
|
|
|
super(left, right, top, bottom, near, far) |
|
|
|
this._canvas = domElement |
|
|
|
this.aspect = aspect || 1 |
|
|
|
this._frustumSize = frustumSize ?? 4 |
|
|
|
this.autoAspect = autoAspect ?? !!domElement |
|
|
|
|
|
|
|
iCameraCommons.upgradeCamera.call(this) // todo: test if autoUpgrade = false works as expected if we call upgradeObject3D externally after constructor, because we have setDirty, refreshTarget below. |
|
|
|
|
|
|
|
this.controlsMode = controlsMode || '' |
|
|
|
|
|
|
|
this.refreshTarget(undefined, false) |
|
|
|
this.refreshFrustum(false) |
|
|
|
|
|
|
|
// if (!camera) |
|
|
|
// this.targetUpdated(false) |
|
|
|
this.setDirty() |
|
|
|
|
|
|
|
|
|
|
|
// if (domElement) |
|
|
|
// domElement.style.touchAction = 'none' // this is done in orbit controls anyway |
|
|
|
|
|
|
|
// this.refreshCameraControls() // this is done on set controlsMode |
|
|
|
// const target = this.target |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
private _interactionsDisabledBy = new Set<string>() |
|
|
|
|
|
|
|
/** |
|
|
|
* If interactions are enabled for this camera. It can be disabled by some code or plugin. |
|
|
|
* see also {@link setInteractions} |
|
|
|
* @deprecated use {@link canUserInteract} to check if the user can interact with this camera |
|
|
|
* @readonly |
|
|
|
*/ |
|
|
|
get interactionsEnabled(): boolean { |
|
|
|
return this._interactionsDisabledBy.size === 0 |
|
|
|
} |
|
|
|
|
|
|
|
setInteractions(enabled: boolean, by: string): void { |
|
|
|
const size = this._interactionsDisabledBy.size |
|
|
|
if (enabled) { |
|
|
|
this._interactionsDisabledBy.delete(by) |
|
|
|
} else { |
|
|
|
this._interactionsDisabledBy.add(by) |
|
|
|
} |
|
|
|
if (size !== this._interactionsDisabledBy.size) this.refreshCameraControls(true) |
|
|
|
} |
|
|
|
|
|
|
|
get canUserInteract() { |
|
|
|
return this._interactionsDisabledBy.size === 0 && this.isMainCamera && this.controlsMode !== '' |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region refreshing |
|
|
|
|
|
|
|
setDirty(options?: ICameraSetDirtyOptions|Event): void { |
|
|
|
if (!this._positionWorld) return // class not initialized |
|
|
|
|
|
|
|
if (!options?.key || ['zoom', 'left', 'right', 'top', 'bottom', 'aspect', 'frustumSize'].includes(options.key)) { |
|
|
|
this.updateProjectionMatrix() |
|
|
|
} |
|
|
|
|
|
|
|
this.getWorldPosition(this._positionWorld) |
|
|
|
|
|
|
|
iCameraCommons.setDirty.call(this, options) |
|
|
|
|
|
|
|
if (options?.last !== false) |
|
|
|
this._camUi.forEach(u=>u?.uiRefresh?.(false, 'postFrame', 1)) // because camera changes a lot. so we dont want to deep refresh ui on every change |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* when aspect ratio is set to auto it must be refreshed on resize, this is done by the viewer for the main camera. |
|
|
|
* @param setDirty |
|
|
|
*/ |
|
|
|
refreshAspect(setDirty = true): void { |
|
|
|
if (this.autoAspect) { |
|
|
|
if (!this._canvas) console.error('OrthographicCamera2: 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(false) |
|
|
|
} |
|
|
|
} |
|
|
|
if (setDirty) this.setDirty() |
|
|
|
// console.log('refreshAspect', this._options.aspect) |
|
|
|
} |
|
|
|
|
|
|
|
protected _nearFarChanged() { |
|
|
|
if (this.view === undefined) return // not initialized yet |
|
|
|
this.updateProjectionMatrix && this.updateProjectionMatrix() |
|
|
|
} |
|
|
|
|
|
|
|
refreshUi = iCameraCommons.refreshUi |
|
|
|
refreshTarget = iCameraCommons.refreshTarget |
|
|
|
activateMain = iCameraCommons.activateMain |
|
|
|
deactivateMain = iCameraCommons.deactivateMain |
|
|
|
|
|
|
|
refreshFrustum(setDirty = true) { |
|
|
|
if (this._frustumSize === undefined) return |
|
|
|
this.top = this._frustumSize / 2 |
|
|
|
this.bottom = -this.top |
|
|
|
this.left = this.bottom * this.aspect |
|
|
|
this.right = this.top * this.aspect |
|
|
|
setDirty && this.setDirty() |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region controls |
|
|
|
|
|
|
|
// todo: move orbit to a plugin maybe? so that its not forced |
|
|
|
private _controlsCtors = new Map<string, TControlsCtor>([['orbit', (object, domElement)=>{ |
|
|
|
const elem = domElement ? !domElement.ownerDocument ? domElement.documentElement : domElement : document.body |
|
|
|
const controls = new OrbitControls3(object, elem) |
|
|
|
// this._controls.enabled = false |
|
|
|
|
|
|
|
// set tab index so that we get keyboard events |
|
|
|
if (elem.tabIndex === -1) { |
|
|
|
elem.tabIndex = 1000 |
|
|
|
// disable focus outline |
|
|
|
elem.style.outline = 'none' |
|
|
|
} |
|
|
|
|
|
|
|
controls.listenToKeyEvents(elem) // optional // todo: make option for this |
|
|
|
// controls.enableKeys = true |
|
|
|
controls.screenSpacePanning = true |
|
|
|
controls.enableZoom = false |
|
|
|
return controls |
|
|
|
}]]) |
|
|
|
setControlsCtor(key: string, ctor: TControlsCtor, replace = false): void { |
|
|
|
if (!replace && this._controlsCtors.has(key)) { |
|
|
|
console.error('OrthographicCamera2: ' + key + ' already exists.') |
|
|
|
return |
|
|
|
} |
|
|
|
this._controlsCtors.set(key, ctor) |
|
|
|
} |
|
|
|
removeControlsCtor(key: string): void { |
|
|
|
this._controlsCtors.delete(key) |
|
|
|
} |
|
|
|
|
|
|
|
private _controlsChanged = ()=>{ |
|
|
|
if (this._controls && this._controls.target) this.refreshTarget(undefined, false) |
|
|
|
this.setDirty({change: 'controls'}) |
|
|
|
} |
|
|
|
private _initCameraControls() { |
|
|
|
const mode = this.controlsMode |
|
|
|
this._controls = this._controlsCtors.get(mode)?.(this, this._canvas) ?? undefined |
|
|
|
if (!this._controls && mode !== '') console.error('OrthographicCamera2 - Unable to create controls with mode ' + mode + '. Are you missing a plugin?') |
|
|
|
this._controls?.addEventListener('change', this._controlsChanged) |
|
|
|
this._currentControlsMode = this._controls ? mode : '' |
|
|
|
// todo maybe set target like this: |
|
|
|
// if (this._controls) this._controls.target = this.target |
|
|
|
} |
|
|
|
|
|
|
|
private _disposeCameraControls() { |
|
|
|
if (this._controls) { |
|
|
|
if (this._controls.target === this.target) this._controls.target = new Vector3() // just in case |
|
|
|
this._controls?.removeEventListener('change', this._controlsChanged) |
|
|
|
this._controls?.dispose() |
|
|
|
} |
|
|
|
this._currentControlsMode = '' |
|
|
|
this._controls = undefined |
|
|
|
} |
|
|
|
|
|
|
|
refreshCameraControls(setDirty = true): void { |
|
|
|
if (!this._controlsCtors) return // class not initialized |
|
|
|
if (this._controls) { |
|
|
|
if (this._currentControlsMode !== this.controlsMode || this !== this._controls.object) { // in-case camera changed or mode changed |
|
|
|
this._disposeCameraControls() |
|
|
|
this._initCameraControls() |
|
|
|
} |
|
|
|
} else { |
|
|
|
this._initCameraControls() |
|
|
|
} |
|
|
|
|
|
|
|
// todo: only for orbit control like controls? |
|
|
|
if (this._controls) { |
|
|
|
const ce = this.canUserInteract |
|
|
|
this._controls.enabled = ce |
|
|
|
if (ce) this.up.copy(Object3D.DEFAULT_UP) |
|
|
|
} |
|
|
|
|
|
|
|
if (setDirty) this.setDirty() |
|
|
|
this.refreshUi() |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region serialization |
|
|
|
|
|
|
|
/** |
|
|
|
* Serializes this camera with controls to JSON. |
|
|
|
* @param meta - metadata for serialization |
|
|
|
* @param baseOnly - Calls only super.toJSON, does internal three.js serialization. Set it to true only if you know what you are doing. |
|
|
|
*/ |
|
|
|
toJSON(meta?: any, baseOnly = false): any { |
|
|
|
if (baseOnly) return super.toJSON(meta) |
|
|
|
return ThreeSerialization.Serialize(this, meta, true) |
|
|
|
} |
|
|
|
|
|
|
|
fromJSON(data: any, meta?: any): this | null { |
|
|
|
ThreeSerialization.Deserialize(data, this, meta, true) |
|
|
|
this.setDirty({change: 'deserialize'}) |
|
|
|
return this |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region camera views |
|
|
|
|
|
|
|
getView<T extends ICameraView = CameraView>(worldSpace = true, _view?: T) { |
|
|
|
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 |
|
|
|
view.uiConfig?.uiRefresh?.(true, 'postFrame') |
|
|
|
return view as T |
|
|
|
} |
|
|
|
|
|
|
|
setView(view: ICameraView) { |
|
|
|
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() |
|
|
|
} |
|
|
|
|
|
|
|
setViewFromCamera(camera: Camera|ICamera, distanceFromTarget?: number, 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(eventOptions: Partial<ICameraEvent>) { |
|
|
|
this.dispatchEvent({type: 'setView', ...eventOptions, camera: this, bubbleToParent: true}) |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region utils/others |
|
|
|
|
|
|
|
// for shader prop updater |
|
|
|
private _positionWorld = new Vector3() |
|
|
|
|
|
|
|
/** |
|
|
|
* See also cameraHelpers.glsl |
|
|
|
* @param material |
|
|
|
*/ |
|
|
|
updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { |
|
|
|
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 === 'OrthographicCamera' ? '1' : '0' |
|
|
|
material.defines.ORTHOGRAPHIC_CAMERA = this.type === 'OrthographicCamera' ? '1' : '0' |
|
|
|
return this |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
dispose(): void { |
|
|
|
this._disposeCameraControls() |
|
|
|
// todo: anything else? |
|
|
|
// iObjectCommons.dispose and dispatch event dispose is called automatically because of updateObject3d |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region ui |
|
|
|
|
|
|
|
private _camUi: UiObjectConfig[] = [ |
|
|
|
...generateUiConfig(this) || [], |
|
|
|
{ |
|
|
|
type: 'input', |
|
|
|
label: ()=>(this.autoNearFar ? 'Min' : '') + ' Near', |
|
|
|
property: [this, 'minNearPlane'], |
|
|
|
}, |
|
|
|
{ |
|
|
|
type: 'input', |
|
|
|
label: ()=>(this.autoNearFar ? 'Max' : '') + ' Far', |
|
|
|
property: [this, 'maxFarPlane'], |
|
|
|
}, |
|
|
|
()=>({ // because _controlsCtors can change |
|
|
|
type: 'dropdown', |
|
|
|
label: 'Controls Mode', |
|
|
|
property: [this, 'controlsMode'], |
|
|
|
children: ['', 'orbit', ...this._controlsCtors.keys()].map(v=>({label: v === '' ? 'none' : v, value:v})), |
|
|
|
onChange: () => this.refreshCameraControls(), |
|
|
|
}), |
|
|
|
()=>makeICameraCommonUiConfig.call(this, this.uiConfig), |
|
|
|
] |
|
|
|
|
|
|
|
uiConfig: UiObjectConfig = { |
|
|
|
type: 'folder', |
|
|
|
label: ()=>this.name || 'Camera', |
|
|
|
children: [ |
|
|
|
...this._camUi, |
|
|
|
()=>this._controls?.uiConfig, |
|
|
|
], |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region deprecated/old |
|
|
|
|
|
|
|
@onChange((k: string, v: boolean)=>{ |
|
|
|
if (!v) console.warn('Setting camera invisible is not supported', k, v) |
|
|
|
}) |
|
|
|
declare visible: boolean |
|
|
|
|
|
|
|
get isActiveCamera(): boolean { |
|
|
|
return this.isMainCamera |
|
|
|
} |
|
|
|
/** |
|
|
|
* @deprecated use `<T>camera.controls` instead |
|
|
|
*/ |
|
|
|
getControls<T extends ICameraControls>(): T|undefined { |
|
|
|
return this._controls as any as T |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @deprecated use `this` instead |
|
|
|
*/ |
|
|
|
get cameraObject(): this { |
|
|
|
return this |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @deprecated use `this` instead |
|
|
|
*/ |
|
|
|
get modelObject(): this { |
|
|
|
return this |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @deprecated - use setDirty directly |
|
|
|
* @param setDirty |
|
|
|
*/ |
|
|
|
targetUpdated(setDirty = true): void { |
|
|
|
if (setDirty) this.setDirty() |
|
|
|
} |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
// region inherited type fixes |
|
|
|
// re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 |
|
|
|
|
|
|
|
traverse: (callback: (object: IObject3D) => void) => void |
|
|
|
traverseVisible: (callback: (object: IObject3D) => void) => void |
|
|
|
traverseAncestors: (callback: (object: IObject3D) => void) => void |
|
|
|
getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined |
|
|
|
getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined |
|
|
|
getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined |
|
|
|
copy: (source: ICamera|Camera|IObject3D, recursive?: boolean, distanceFromTarget?: number, worldSpace?: boolean) => this |
|
|
|
clone: (recursive?: boolean) => this |
|
|
|
add: (...object: IObject3D[]) => this |
|
|
|
remove: (...object: IObject3D[]) => this |
|
|
|
dispatchEvent: (event: ICameraEvent) => void |
|
|
|
declare parent: IObject3D | null |
|
|
|
declare children: IObject3D[] |
|
|
|
|
|
|
|
// endregion |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Empty class with the constructor same as OrthographicCamera in three.js. |
|
|
|
* This can be used to remain compatible with three.js construct signature. |
|
|
|
*/ |
|
|
|
export class OrthographicCamera0 extends OrthographicCamera2 { |
|
|
|
constructor(left?: number, right?: number, top?: number, bottom?: number, near?: number, far?: number) { |
|
|
|
super(undefined, undefined, undefined, undefined, left, right, top, bottom, near, far, 1) |
|
|
|
if (near !== undefined || far) { |
|
|
|
this.autoNearFar = false |
|
|
|
if (near) { |
|
|
|
this.near = near |
|
|
|
this.minNearPlane = near |
|
|
|
} |
|
|
|
if (far) { |
|
|
|
this.far = far |
|
|
|
this.maxFarPlane = far |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |