| cube.setDirty() | cube.setDirty() | ||||
| }, | }, | ||||
| onComplete: () => isMovedUp = !isMovedUp, | onComplete: () => isMovedUp = !isMovedUp, | ||||
| onStop: () => throw(new Error('Animation stopped')), | |||||
| }) | }) | ||||
| // await for animation | |||||
| await anim.promise; | |||||
| // await for animation. This promise will reject only if an exception is thrown in onStop | |||||
| await anim.promise.catch((e)=>{ | |||||
| console.log(e, 'animation stopped before completion') | |||||
| }); | |||||
| // or stop the animation | // or stop the animation | ||||
| // anim.stop() | // anim.stop() | ||||
| await popmotion.animateAsync({ // Also await for the animation. | await popmotion.animateAsync({ // Also await for the animation. | ||||
| from: '#' + cube.material.color.getHexString(), | from: '#' + cube.material.color.getHexString(), | ||||
| to: '#' + new Color().setHSL(Math.random(), 1, 0.5).getHexString(), | to: '#' + new Color().setHSL(Math.random(), 1, 0.5).getHexString(), | ||||
| duration: 500, | |||||
| duration: 1000, // 1s | |||||
| onUpdate: (v) => { | onUpdate: (v) => { | ||||
| cube.material.color.set(v) | cube.material.color.set(v) | ||||
| cube.material.setDirty() | cube.material.setDirty() |
| const pos = camera.getWorldPosition(new Vector3()).sub(bbox.getCenter(new Vector3())) | const pos = camera.getWorldPosition(new Vector3()).sub(bbox.getCenter(new Vector3())) | ||||
| const radius = 1.5 * bbox.getSize(new Vector3()).length() / 2. | const radius = 1.5 * bbox.getSize(new Vector3()).length() / 2. | ||||
| const dist = pos.length() | const dist = pos.length() | ||||
| const near = Math.max(camera.userData.minNearPlane ?? 0.2, dist - radius) | |||||
| const far = Math.min(Math.max(near + 1, dist + radius), camera.cameraObject.userData.maxFarPlane ?? 1000) | |||||
| // new way | |||||
| // todo there is still some clipping when you are inside the model like a room. | |||||
| const dist1 = -pos.clone().normalize().dot(camera.getWorldDirection(new Vector3())) | |||||
| const near = Math.max(camera.userData.minNearPlane ?? 0.2, dist1 * (dist - radius)) | |||||
| const far = Math.min(Math.max(near + 1, dist1 * (dist + radius)), camera.userData.maxFarPlane ?? 1000) | |||||
| // old way, has issues when panning very far from the camera target | |||||
| // const near = Math.max(camera.userData.minNearPlane ?? 0.2, dist - radius) | |||||
| // const far = Math.min(Math.max(near + 1, dist + radius), camera.userData.maxFarPlane ?? 1000) | |||||
| camera.near = near | camera.near = near | ||||
| camera.far = far | camera.far = far | ||||
| // todo try using minimum of all 6 endpoints of bbox. | |||||
| // camera.near = 3 | // camera.near = 3 | ||||
| // camera.far = 20 | // camera.far = 20 | ||||
| } | } |
| }, | }, | ||||
| } | } | ||||
| this.animations[uuid] = a | this.animations[uuid] = a | ||||
| a.promise = new Promise<void>((resolve) => { | |||||
| a.promise = new Promise<void>((resolve, reject) => { | |||||
| const opts: AnimationOptions<V> = { | const opts: AnimationOptions<V> = { | ||||
| driver: this.defaultDriver, | driver: this.defaultDriver, | ||||
| ...options, | ...options, | ||||
| onComplete: ()=>{ | onComplete: ()=>{ | ||||
| options.onComplete?.() | |||||
| try { | |||||
| options.onComplete && options.onComplete() | |||||
| } catch (e: any) { | |||||
| reject(e) | |||||
| return | |||||
| } | |||||
| resolve() | resolve() | ||||
| }, | }, | ||||
| onStop: ()=>{ | onStop: ()=>{ | ||||
| options.onStop?.() | |||||
| try { | |||||
| options.onStop && options.onStop() | |||||
| } catch (e: any) { | |||||
| reject(e) | |||||
| return | |||||
| } | |||||
| resolve() | resolve() | ||||
| }, | }, | ||||
| } | } |
| import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js' | import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js' | ||||
| import {IUiConfigContainer, uiInput, UiObjectConfig, uiPanelContainer, uiToggle} from 'uiconfig.js' | |||||
| import {IUiConfigContainer, uiInput, UiObjectConfig, uiPanelContainer, uiToggle, uiVector} from 'uiconfig.js' | |||||
| import {serialize} from 'ts-browser-helpers' | import {serialize} from 'ts-browser-helpers' | ||||
| import {ICameraControls} from '../../core' | import {ICameraControls} from '../../core' | ||||
| import {Vector3} from 'three' | |||||
| export type TOrbitControlsEvents = 'change' | 'end' | 'start' | export type TOrbitControlsEvents = 'change' | 'end' | 'start' | ||||
| @uiPanelContainer('Orbit Controls') | @uiPanelContainer('Orbit Controls') | ||||
| @uiInput() @serialize() maxPolarAngle = Math.PI | @uiInput() @serialize() maxPolarAngle = Math.PI | ||||
| @uiInput() @serialize() minAzimuthAngle = -10000 // should be -Infinity but this breaks the UI | @uiInput() @serialize() minAzimuthAngle = -10000 // should be -Infinity but this breaks the UI | ||||
| @uiInput() @serialize() maxAzimuthAngle = 10000 | |||||
| @uiInput() @serialize() maxAzimuthAngle = 10000 // should be Infinity but this breaks the UI | |||||
| @uiVector() @serialize() clampMin = new Vector3(-10000, -10000, -10000) // should be -Infinity but this breaks the UI | |||||
| @uiVector() @serialize() clampMax = new Vector3(10000, 10000, 10000) // should be Infinity but this breaks the UI | |||||
| // @uiToggle() | // @uiToggle() | ||||
| @serialize() screenSpacePanning = true | @serialize() screenSpacePanning = true |