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.
121 lines (105 loc) • 4.56 kB
text/typescript
import {CatmullRomCurve3, EventDispatcher, Quaternion, Vector3} from 'three'
import {onChange3, serializable, serialize} from 'ts-browser-helpers'
import {IUiConfigContainer, uiButton, uiInput, uiNumber, UiObjectConfig, uiPanelContainer, uiVector} from 'uiconfig.js'
import {ICamera} from '../ICamera'
import {generateUUID} from '../../three'
export interface ICameraView extends IUiConfigContainer{
name: string
position: Vector3
target: Vector3
quaternion: Quaternion
zoom: number
/*
* Duration multiplier when the camera is animating to the view.
*/
duration?: number
isWorldSpace?: boolean
animate?(camera?: ICamera, duration?: number): void
set?(camera?: ICamera): void
update?(camera?: ICamera): void
delete?(camera?: ICamera): void
setDirty?: (ops?: any)=> void
}
export interface CameraViewEventMap {
setView: {camera?: ICamera, view: ICameraView}
animateView: {camera?: ICamera, duration?: number, view: ICameraView}
updateView: {camera?: ICamera, view: ICameraView}
deleteView: {camera?: ICamera, view: ICameraView}
update: {key?: string}
}
export class CameraView extends EventDispatcher<CameraViewEventMap> implements ICameraView, IUiConfigContainer {
uuid = generateUUID()
name = 'Camera View'
position = new Vector3()
target = new Vector3()
quaternion = new Quaternion()
zoom = 1
/**
* Duration multiplier. Set to 0 for instant camera jump.
*/
duration = 1
isWorldSpace = true
set = (camera?: ICamera) => this.dispatchEvent({type: 'setView', camera, view: this})
update = (camera?: ICamera) => this.dispatchEvent({type: 'updateView', camera, view: this})
delete = (camera?: ICamera) => this.dispatchEvent({type: 'deleteView', camera, view: this})
animate = (camera?: ICamera, duration?: number) => this.dispatchEvent({type: 'animateView', camera, duration, view: this})
constructor(name?: string, position?: Vector3, target?: Vector3, quaternion?: Quaternion, zoom?: number, duration = 1, isWoldSpace?: boolean) {
super()
if (name !== undefined) this.name = name
if (position) this.position.copy(position)
if (target) this.target.copy(target)
if (quaternion) this.quaternion.copy(quaternion)
if (zoom !== undefined) this.zoom = zoom
if (duration !== undefined && duration !== 0) this.duration = duration
if (isWoldSpace !== undefined) this.isWorldSpace = isWoldSpace
}
setDirty = (ops?: any) => {
this.dispatchEvent({...ops, type: 'update'})
if (this.uiConfig) {
if (ops?.key === 'name') {
this.uiConfig.label = this.name
this.uiConfig.uiRefresh?.()
} else {
this.uiConfig.uiRefresh?.(true, 'postFrame')
}
}
}
clone() {
return new CameraView(this.name, this.position, this.target, this.quaternion, this.zoom, this.duration, this.isWorldSpace)
}
uiConfig?: UiObjectConfig
// uiConfig = generateUiFolder(this.name, this)
}
export function createCameraPath(views: CameraView[]) {
const splineCurve = 'chordal'
const points = views.map(c => c.position.clone())
const spline = new CatmullRomCurve3(points, true, splineCurve, 0.75)
const getPosition = (t: number, viewIndex: number, v?: Vector3) => {
v = v || new Vector3()
const ip = 1. / points.length
const i = viewIndex === 0 ? points.length : viewIndex
const d = (i - 1) * ip
spline.getPointAt(d + t * ip, v)
return v
}
const targets = views.map(c => c.target.clone())
const targetSpline = new CatmullRomCurve3(targets, true, splineCurve, 0.75)
const getTarget = (t: number, viewIndex: number, v?: Vector3) => {
v = v || new Vector3()
const ip = 1. / targets.length
const i = viewIndex === 0 ? targets.length : viewIndex
const d = (i - 1) * ip
targetSpline.getPointAt(d + t * ip, v)
return v
}
return {getPosition, getTarget}
}