threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

PerspectiveCamera2.ts 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. import {Camera, Event, IUniform, Object3D, PerspectiveCamera, Vector3} from 'three'
  2. import {generateUiConfig, uiInput, UiObjectConfig, uiSlider, uiToggle, uiVector} from 'uiconfig.js'
  3. import {onChange, onChange2, onChange3, serialize} from 'ts-browser-helpers'
  4. import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera'
  5. import {ICameraSetDirtyOptions} from '../ICamera'
  6. import type {ICameraControls, TControlsCtor} from './ICameraControls'
  7. import {OrbitControls3} from '../../three/controls/OrbitControls3'
  8. import {IObject3D} from '../IObject'
  9. import {ThreeSerialization} from '../../utils'
  10. import {iCameraCommons} from '../object/iCameraCommons'
  11. import {bindToValue} from '../../three/utils/decorators'
  12. import {makeICameraCommonUiConfig} from '../object/IObjectUi'
  13. import {CameraView, ICameraView} from './CameraView'
  14. // todo: maybe change domElement to some wrapper/base class of viewer
  15. export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
  16. assetType = 'camera' as const
  17. get controls(): ICameraControls | undefined {
  18. return this._controls
  19. }
  20. @uiInput('Name') name: string
  21. @serialize('camControls')
  22. private _controls?: ICameraControls
  23. private _currentControlsMode: TCameraControlsMode = ''
  24. @onChange2(PerspectiveCamera2.prototype.refreshCameraControls)
  25. controlsMode: TCameraControlsMode
  26. /**
  27. * It should be the canvas actually
  28. * @private
  29. */
  30. private _canvas?: HTMLCanvasElement
  31. get isMainCamera(): boolean {
  32. return this.userData.__isMainCamera || false
  33. }
  34. @serialize()
  35. userData: ICameraUserData = {}
  36. @onChange3(PerspectiveCamera2.prototype.setDirty)
  37. @uiSlider('Field Of View', [1, 180], 0.001)
  38. @serialize() fov: number
  39. @onChange3(PerspectiveCamera2.prototype.setDirty)
  40. @serialize() focus: number
  41. @onChange3(PerspectiveCamera2.prototype.setDirty)
  42. @uiSlider('FoV Zoom', [0.001, 10], 0.001)
  43. @serialize() zoom: number
  44. @uiVector('Position', undefined, undefined, (that:PerspectiveCamera2)=>({onChange: ()=>that.setDirty()}))
  45. @serialize() readonly position: Vector3
  46. /**
  47. * 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.
  48. * Note: this is always in world-space
  49. * Note: {@link autoLookAtTarget} must be set to trye to make the camera look at the target when no controls are enabled
  50. */
  51. @uiVector('Target', undefined, undefined, (that:PerspectiveCamera2)=>({onChange: ()=>that.setDirty()}))
  52. @serialize() readonly target: Vector3 = new Vector3(0, 0, 0)
  53. /**
  54. * Automatically manage aspect ratio based on window/canvas size.
  55. * Defaults to `true` if {@link domElement}(canvas) is set.
  56. */
  57. @serialize()
  58. @onChange2(PerspectiveCamera2.prototype.refreshAspect)
  59. @uiToggle('Auto Aspect')
  60. autoAspect: boolean
  61. /**
  62. * Near clipping plane.
  63. * This is managed by RootScene for active cameras
  64. * To change the minimum that's possible set {@link minNearPlane}
  65. * To use a fixed value set {@link autoNearFar} to false and set {@link minNearPlane}
  66. */
  67. @onChange2(PerspectiveCamera2.prototype._nearFarChanged)
  68. near = 0.01
  69. /**
  70. * Far clipping plane.
  71. * This is managed by RootScene for active cameras
  72. * To change the maximum that's possible set {@link maxFarPlane}
  73. * To use a fixed value set {@link autoNearFar} to false and set {@link maxFarPlane}
  74. */
  75. @onChange2(PerspectiveCamera2.prototype._nearFarChanged)
  76. far = 50
  77. /**
  78. * Automatically make the camera look at the {@link target} on {@link setDirty} call
  79. * Defaults to false. Note that this must be set to true to make the camera look at the target without any controls
  80. */
  81. @bindToValue({obj: 'userData', onChange: 'setDirty'})
  82. autoLookAtTarget = false // bound to userData so that it's saved in the glb.
  83. /**
  84. * Automatically manage near and far clipping planes based on scene size.
  85. */
  86. @bindToValue({obj: 'userData', onChange: 'setDirty'})
  87. autoNearFar = true // bound to userData so that it's saved in the glb.
  88. /**
  89. * Minimum near clipping plane allowed. (Distance from camera)
  90. * Used in RootScene when {@link autoNearFar} is true.
  91. * @default 0.2
  92. */
  93. @bindToValue({obj: 'userData', onChange: 'setDirty'})
  94. minNearPlane = 0.5
  95. /**
  96. * Maximum far clipping plane allowed. (Distance from camera)
  97. * Used in RootScene when {@link autoNearFar} is true.
  98. */
  99. @bindToValue({obj: 'userData', onChange: 'setDirty'})
  100. maxFarPlane = 1000
  101. constructor(controlsMode?: TCameraControlsMode, domElement?: HTMLCanvasElement, autoAspect?: boolean, fov?: number, aspect?: number) {
  102. super(fov, aspect)
  103. this._canvas = domElement
  104. this.autoAspect = autoAspect ?? !!domElement
  105. 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.
  106. this.controlsMode = controlsMode || ''
  107. this.refreshTarget(undefined, false)
  108. // if (!camera)
  109. // this.targetUpdated(false)
  110. this.setDirty()
  111. // if (domElement)
  112. // domElement.style.touchAction = 'none' // this is done in orbit controls anyway
  113. // const ae = this._canvas.addEventListener
  114. // todo: this breaks tweakpane UI.
  115. // this._canvas.addEventListener = (type: string, listener: any, options1: any) => { // see https://github.com/mrdoob/three.js/pull/19782
  116. // ae(type, listener, type === 'wheel' && typeof options1 !== 'boolean' ? {
  117. // ...typeof options1 === 'object' ? options1 : {},
  118. // capture: false,
  119. // passive: false,
  120. // } : options1)
  121. // }
  122. // this.refreshCameraControls() // this is done on set controlsMode
  123. // const target = this.target
  124. }
  125. // @serialize('camOptions') //todo handle deserialization of this
  126. // region interactionsEnabled
  127. // private _interactionsEnabled = true
  128. //
  129. // get interactionsEnabled(): boolean {
  130. // return this._interactionsEnabled
  131. // }
  132. //
  133. // set interactionsEnabled(value: boolean) {
  134. // if (this._interactionsEnabled !== value) {
  135. // this._interactionsEnabled = value
  136. // this.refreshCameraControls(true)
  137. // }
  138. // }
  139. private _interactionsDisabledBy = new Set<string>()
  140. /**
  141. * If interactions are enabled for this camera. It can be disabled by some code or plugin.
  142. * see also {@link setInteractions}
  143. * @deprecated use {@link canUserInteract} to check if the user can interact with this camera
  144. * @readonly
  145. */
  146. get interactionsEnabled(): boolean {
  147. return this._interactionsDisabledBy.size === 0
  148. }
  149. setInteractions(enabled: boolean, by: string): void {
  150. const size = this._interactionsDisabledBy.size
  151. if (enabled) {
  152. this._interactionsDisabledBy.delete(by)
  153. } else {
  154. this._interactionsDisabledBy.add(by)
  155. }
  156. if (size !== this._interactionsDisabledBy.size) this.refreshCameraControls(true)
  157. }
  158. get canUserInteract() {
  159. return this._interactionsDisabledBy.size === 0 && this.isMainCamera && this.controlsMode !== ''
  160. }
  161. // endregion
  162. // region refreshing
  163. setDirty(options?: ICameraSetDirtyOptions|Event): void {
  164. if (!this._positionWorld) return // class not initialized
  165. if (options?.key === 'fov' || options?.key === 'zoom') this.updateProjectionMatrix()
  166. this.getWorldPosition(this._positionWorld)
  167. iCameraCommons.setDirty.call(this, options)
  168. 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
  169. }
  170. /**
  171. * when aspect ratio is set to auto it must be refreshed on resize, this is done by the viewer for the main camera.
  172. * @param setDirty
  173. */
  174. refreshAspect(setDirty = true): void {
  175. if (this.autoAspect) {
  176. if (!this._canvas) console.error('cannot calculate aspect ratio without canvas/container')
  177. else {
  178. this.aspect = this._canvas.clientWidth / this._canvas.clientHeight
  179. this.updateProjectionMatrix?.()
  180. }
  181. }
  182. if (setDirty) this.setDirty()
  183. // console.log('refreshAspect', this._options.aspect)
  184. }
  185. protected _nearFarChanged() {
  186. if (this.view === undefined) return // not initialized yet
  187. this.updateProjectionMatrix?.()
  188. }
  189. refreshUi = iCameraCommons.refreshUi
  190. refreshTarget = iCameraCommons.refreshTarget
  191. activateMain = iCameraCommons.activateMain
  192. deactivateMain = iCameraCommons.deactivateMain
  193. // endregion
  194. // region controls
  195. // todo: move orbit to a plugin maybe? so that its not forced
  196. private _controlsCtors = new Map<string, TControlsCtor>([['orbit', (object, domElement)=>{
  197. const controls = new OrbitControls3(object, domElement ? !domElement.ownerDocument ? domElement.documentElement : domElement : document.body)
  198. // this._controls.enabled = false
  199. // this._controls.listenToKeyEvents(window as any) // optional // todo: this breaks keyboard events in UI like cursor left/right, make option for this
  200. // this._controls.enableKeys = true
  201. controls.screenSpacePanning = true
  202. return controls
  203. }]])
  204. setControlsCtor(key: string, ctor: TControlsCtor, replace = false): void {
  205. if (!replace && this._controlsCtors.has(key)) {
  206. console.error(key + ' already exists.')
  207. return
  208. }
  209. this._controlsCtors.set(key, ctor)
  210. }
  211. removeControlsCtor(key: string): void {
  212. this._controlsCtors.delete(key)
  213. }
  214. private _controlsChanged = ()=>{
  215. if (this._controls && this._controls.target) this.refreshTarget(undefined, false)
  216. this.setDirty({change: 'controls'})
  217. }
  218. private _initCameraControls() {
  219. const mode = this.controlsMode
  220. this._controls = this._controlsCtors.get(mode)?.(this, this._canvas) ?? undefined
  221. if (!this._controls && mode !== '') console.error('Unable to create controls with mode ' + mode + '. Are you missing a plugin?')
  222. this._controls?.addEventListener('change', this._controlsChanged)
  223. this._currentControlsMode = this._controls ? mode : ''
  224. // todo maybe set target like this:
  225. // if (this._controls) this._controls.target = this.target
  226. }
  227. private _disposeCameraControls() {
  228. if (this._controls) {
  229. if (this._controls.target === this.target) this._controls.target = new Vector3() // just in case
  230. this._controls?.removeEventListener('change', this._controlsChanged)
  231. this._controls?.dispose()
  232. }
  233. this._currentControlsMode = ''
  234. this._controls = undefined
  235. }
  236. refreshCameraControls(setDirty = true): void {
  237. if (!this._controlsCtors) return // class not initialized
  238. if (this._controls) {
  239. if (this._currentControlsMode !== this.controlsMode || this !== this._controls.object) { // in-case camera changed or mode changed
  240. this._disposeCameraControls()
  241. this._initCameraControls()
  242. }
  243. } else {
  244. this._initCameraControls()
  245. }
  246. // todo: only for orbit control like controls?
  247. if (this._controls) {
  248. const ce = this.canUserInteract
  249. this._controls.enabled = ce
  250. if (ce) this.up.copy(Object3D.DEFAULT_UP)
  251. }
  252. if (setDirty) this.setDirty()
  253. this.refreshUi()
  254. }
  255. // endregion
  256. // region serialization
  257. /**
  258. * Serializes this camera with controls to JSON.
  259. * @param meta - metadata for serialization
  260. * @param baseOnly - Calls only super.toJSON, does internal three.js serialization. Set it to true only if you know what you are doing.
  261. */
  262. toJSON(meta?: any, baseOnly = false): any {
  263. if (baseOnly) return super.toJSON(meta)
  264. // todo add camOptions for backwards compatibility?
  265. return ThreeSerialization.Serialize(this, meta, true)
  266. }
  267. fromJSON(data: any, meta?: any): this | null {
  268. if (data.camOptions || data.aspect === 'auto')
  269. data = {...data}
  270. if (data.camOptions) {
  271. const op = data.camOptions
  272. if (op.fov) data.fov = op.fov
  273. if (op.focus) data.focus = op.focus
  274. if (op.zoom) data.zoom = op.zoom
  275. if (op.aspect) data.aspect = op.aspect
  276. if (op.controlsMode) data.controlsMode = op.controlsMode
  277. // todo: add support for this
  278. // if (op.left) data.left = op.left
  279. // if (op.right) data.right = op.right
  280. // if (op.top) data.top = op.top
  281. // if (op.bottom) data.bottom = op.bottom
  282. // if (op.frustumSize) data.frustumSize = op.frustumSize
  283. // if (op.controlsEnabled) data.controlsEnabled = op.controlsEnabled
  284. delete data.camOptions
  285. }
  286. if (data.aspect === 'auto') {
  287. data.aspect = this.aspect
  288. this.autoAspect = true
  289. }
  290. // if (data.cameraObject) this._camera.fromJSON(data.cameraObject)
  291. // todo: add check for OrbitControls being not deserialized(inited properly) if it doesn't exist yet (if it is not inited properly)
  292. // console.log(JSON.parse(JSON.stringify(data)))
  293. ThreeSerialization.Deserialize(data, this, meta, true)
  294. this.setDirty({change: 'deserialize'})
  295. return this
  296. }
  297. // endregion
  298. // region camera views
  299. getView<T extends ICameraView = CameraView>(worldSpace = true, _view?: T) {
  300. const up = new Vector3()
  301. this.updateWorldMatrix(true, false)
  302. const matrix = this.matrixWorld
  303. up.x = matrix.elements[4]
  304. up.y = matrix.elements[5]
  305. up.z = matrix.elements[6]
  306. up.normalize()
  307. const view = _view || new CameraView()
  308. view.name = this.name
  309. view.position.copy(this.position)
  310. view.target.copy(this.target)
  311. view.quaternion.copy(this.quaternion)
  312. view.zoom = this.zoom
  313. // view.up.copy(up)
  314. const parent = this.parent
  315. if (parent) {
  316. if (worldSpace) {
  317. view.position.applyMatrix4(parent.matrixWorld)
  318. this.getWorldQuaternion(view.quaternion)
  319. // target, up is already in world space
  320. } else {
  321. up.transformDirection(parent.matrixWorld.clone().invert())
  322. // pos is already in local space
  323. // target should always be in world space
  324. }
  325. }
  326. view.isWorldSpace = worldSpace
  327. view.uiConfig?.uiRefresh?.(true, 'postFrame')
  328. return view as T
  329. }
  330. setView(view: ICameraView) {
  331. this.position.copy(view.position)
  332. this.target.copy(view.target)
  333. // this.up.copy(view.up)
  334. this.quaternion.copy(view.quaternion)
  335. this.zoom = view.zoom
  336. this.setDirty()
  337. }
  338. setViewFromCamera(camera: Camera|ICamera, distanceFromTarget?: number, worldSpace = true) {
  339. // todo: getView, setView can also be used, do we need copy? as that will copy all the properties
  340. this.copy(camera, undefined, distanceFromTarget, worldSpace)
  341. }
  342. setViewToMain(eventOptions: Partial<ICameraEvent>) {
  343. this.dispatchEvent({type: 'setView', ...eventOptions, camera: this, bubbleToParent: true})
  344. }
  345. // endregion
  346. // region utils/others
  347. // for shader prop updater
  348. private _positionWorld = new Vector3()
  349. /**
  350. * See also cameraHelpers.glsl
  351. * @param material
  352. */
  353. updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this {
  354. material.uniforms.cameraPositionWorld?.value?.copy(this._positionWorld)
  355. material.uniforms.cameraNearFar?.value?.set(this.near, this.far)
  356. if (material.uniforms.projection) material.uniforms.projection.value = this.projectionMatrix // todo: rename to projectionMatrix2?
  357. material.defines.PERSPECTIVE_CAMERA = this.type === 'PerspectiveCamera' ? '1' : '0'
  358. // material.defines.ORTHOGRAPHIC_CAMERA = this.type === 'OrthographicCamera' ? '1' : '0' // todo
  359. return this
  360. }
  361. dispose(): void {
  362. this._disposeCameraControls()
  363. // todo: anything else?
  364. // iObjectCommons.dispose and dispatch event dispose is called automatically because of updateObject3d
  365. }
  366. // endregion
  367. // region ui
  368. private _camUi: UiObjectConfig[] = [
  369. ...generateUiConfig(this) || [],
  370. {
  371. type: 'input',
  372. label: ()=>(this.autoNearFar ? 'Min' : '') + ' Near',
  373. property: [this, 'minNearPlane'],
  374. },
  375. {
  376. type: 'input',
  377. label: ()=>(this.autoNearFar ? 'Max' : '') + ' Far',
  378. property: [this, 'maxFarPlane'],
  379. },
  380. {
  381. type: 'input',
  382. label: 'Auto Near Far',
  383. property: [this, 'autoNearFar'],
  384. },
  385. ()=>({ // because _controlsCtors can change
  386. type: 'dropdown',
  387. label: 'Controls Mode',
  388. property: [this, 'controlsMode'],
  389. children: ['', 'orbit', ...this._controlsCtors.keys()].map(v=>({label: v === '' ? 'none' : v, value:v})),
  390. onChange: () => this.refreshCameraControls(),
  391. }),
  392. ()=>makeICameraCommonUiConfig.call(this, this.uiConfig),
  393. ]
  394. uiConfig: UiObjectConfig = {
  395. type: 'folder',
  396. label: ()=>this.name || 'Camera',
  397. children: [
  398. ...this._camUi,
  399. // todo hack for zoom in and out for now.
  400. ()=>(this._controls as OrbitControls3)?.zoomIn ? {
  401. type: 'button',
  402. label: 'Zoom in',
  403. value: ()=> (this._controls as OrbitControls3)?.zoomIn(1),
  404. } : {},
  405. ()=>(this._controls as OrbitControls3)?.zoomOut ? {
  406. type: 'button',
  407. label: 'Zoom out',
  408. value: ()=> (this._controls as OrbitControls3)?.zoomOut(1),
  409. } : {},
  410. ()=>this._controls?.uiConfig,
  411. ],
  412. }
  413. // endregion
  414. // region deprecated/old
  415. @onChange((k: string, v: boolean)=>{
  416. if (!v) console.warn('Setting camera invisible is not supported', k, v)
  417. })
  418. visible: boolean
  419. get isActiveCamera(): boolean {
  420. return this.isMainCamera
  421. }
  422. /**
  423. * @deprecated use `<T>camera.controls` instead
  424. */
  425. getControls<T extends ICameraControls>(): T|undefined {
  426. return this._controls as any as T
  427. }
  428. /**
  429. * @deprecated use `this` instead
  430. */
  431. get cameraObject(): this {
  432. return this
  433. }
  434. /**
  435. * @deprecated use `this` instead
  436. */
  437. get modelObject(): this {
  438. return this
  439. }
  440. /**
  441. * @deprecated - use setDirty directly
  442. * @param setDirty
  443. */
  444. targetUpdated(setDirty = true): void {
  445. if (setDirty) this.setDirty()
  446. }
  447. // setCameraOptions<T extends Partial<IPerspectiveCameraOptions | IOrthographicCameraOptions>>(value: T, setDirty = true): void {
  448. // const ops: any = {...value}
  449. //
  450. // this._refreshCameraOptions(false)
  451. // this.refreshCameraControls(false)
  452. // if (setDirty) this.setDirty()
  453. // }
  454. // not to be used
  455. // private _changeType(setDirty = true) {
  456. // // let cam = this._camera.modelObject
  457. //
  458. // // change of type, not supported now.
  459. // // if (this._options.type !== cam.type) {
  460. // // const cam2 = this._options.type === 'PerspectiveCamera' ? new PerspectiveCamera() : new OrthographicCamera()
  461. // // cam2.name = this._camera.name
  462. // // cam2.near = this._camera.modelObject.near
  463. // // cam2.far = this._camera.modelObject.far
  464. // // cam2.zoom = this._camera.modelObject.zoom
  465. // // cam2.scale.copy(this._camera.modelObject.scale)
  466. // //
  467. // // const isActive = this._isMainCamera
  468. // // if (isActive) this.deactivateMain()
  469. // // this._camera = this._setCameraObject(cam2)
  470. // // cam = this._camera.modelObject
  471. // // if (isActive) this.activateMain()
  472. // // this._camera.modelObject.updateProjectionMatrix()
  473. // // }
  474. //
  475. // // this._nearFarChanged() // this updates projection matrix todo: move to setDirty
  476. //
  477. // if (setDirty) this.setDirty()
  478. // }
  479. // private _cameraObjectUpdate = (e: any)=>{
  480. // this.setDirty(e)
  481. // }
  482. // private _setCameraObject(cam: OrthographicCamera | PerspectiveCamera) {
  483. // if (this._camera) this._camera.removeEventListener('objectUpdate', this._cameraObjectUpdate)
  484. // this._camera = setupIModel(cam as any)
  485. // this._camera.addEventListener('objectUpdate', this._cameraObjectUpdate)
  486. // return this._camera
  487. // }
  488. // for ortho
  489. // private _frustumSize: number | undefined = undefined
  490. //
  491. // get frustumSize(): number | undefined {
  492. // return this._frustumSize
  493. // }
  494. //
  495. // set frustumSize(value: number | undefined) {
  496. // this._frustumSize = value
  497. // if (value !== undefined) {
  498. // cam.top = value / 2
  499. // cam.bottom = -value / 2
  500. // cam.left = aspect * value / 2
  501. // cam.right = -aspect * value / 2
  502. // }
  503. // this.setDirty()
  504. // }
  505. // endregion
  506. // region inherited type fixes
  507. // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936
  508. traverse: (callback: (object: IObject3D) => void) => void
  509. traverseVisible: (callback: (object: IObject3D) => void) => void
  510. traverseAncestors: (callback: (object: IObject3D) => void) => void
  511. getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined
  512. getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined
  513. getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined
  514. copy: (source: ICamera|Camera|IObject3D, recursive?: boolean, distanceFromTarget?: number, worldSpace?: boolean) => this
  515. clone: (recursive?: boolean) => this
  516. add: (...object: IObject3D[]) => this
  517. remove: (...object: IObject3D[]) => this
  518. dispatchEvent: (event: ICameraEvent) => void
  519. parent: IObject3D | null
  520. children: IObject3D[]
  521. // endregion
  522. }
  523. /**
  524. * Empty class with the constructor same as PerspectiveCamera in three.js.
  525. * This can be used to remain compatible with three.js construct signature.
  526. */
  527. export class PerspectiveCamera0 extends PerspectiveCamera2 {
  528. constructor(fov?: number, aspect?: number, near?: number, far?: number) {
  529. super(undefined, undefined, undefined, fov, aspect || 1)
  530. if (near || far) {
  531. this.autoNearFar = false
  532. if (near) {
  533. this.near = near
  534. this.minNearPlane = near
  535. }
  536. if (far) {
  537. this.far = far
  538. this.maxFarPlane = far
  539. }
  540. }
  541. }
  542. }