infamous
Version:
A CSS3D/WebGL UI library.
199 lines (167 loc) • 6.79 kB
JavaScript
import Class from 'lowclass'
import { PerspectiveCamera as ThreePerspectiveCamera } from 'three'
import Node from './Node'
import Motor from './Motor'
import { props } from './props'
// TODO: update this to have a CSS3D-perspective-like API like with the Scene's
// default camera.
export default
Class('PerspectiveCamera').extends( Node, ({ Super, Public, Private }) => ({
static: {
defaultElementName: 'i-perspective-camera',
// TODO remove attributeChangedCallback, replace with updated based on these props
props: {
...Node.props,
fov: { ...props.number, default: 75 },
aspect: {
...props.number,
default() { return Private(this)._getDefaultAspect() },
deserialize(val) { val == null ? this.constructor.props.aspect.default.call(this) : props.number.deserialize(val) },
},
near: { ...props.number, default: 0.1 },
far: { ...props.number, default: 1000 },
zoom: { ...props.number, default: 1 },
active: { ...props.boolean, default: false },
},
},
updated(oldProps, oldState, modifiedProps) {
Super(this).updated(oldProps, oldState, modifiedProps)
if (!this.isConnected) return
if (modifiedProps.active) {
this._setSceneCamera( this.active ? undefined : 'unset' )
}
if (modifiedProps.aspect) {
if (!this.aspect)
// default aspect value based on the scene size.
privateThis._startAutoAspect()
else
privateThis._stopAutoAspect()
}
// TODO handle the other props here, remove attributeChangedCallback
},
makeThreeObject3d() {
return new ThreePerspectiveCamera(75, 16/9, 1, 1000)
},
connectedCallback() {
Super(this).connectedCallback()
const privateThis = Private(this)
privateThis._lastKnownScene = this.scene
},
// TODO replace with unmountedCallback #150
deinit() {
Super(this).deinit()
// TODO we want to call this in the upcoming
// unmountedCallback, but for now it's harmless but
// will run unnecessary logic. #150
Private(this)._setSceneCamera( 'unset' )
Private(this)._lastKnownScene = null
},
// TODO, unmountedCallback functionality. issue #150
unmountedCallback() {},
attributeChangedCallback( attr, oldVal, newVal ) {
Super(this).attributeChangedCallback( attr, oldVal, newVal )
if ( typeof newVal == 'string' ) {
Private(this)._attributeAddedOrChanged( attr, newVal )
}
else {
Private(this)._attributeRemoved( attr )
}
},
private: {
_lastKnownScene: null,
// TODO CAMERA-DEFAULTS, get defaults from somewhere common.
_attributeRemoved(attr, newVal) {
const publicThis = Public(this)
if ( attr == 'fov' ) {
publicThis.threeObject3d.fov = 75
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'aspect' ) {
this._startAutoAspect()
publicThis.threeObject3d.aspect = this._getDefaultAspect()
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'near' ) {
publicThis.threeObject3d.near = 0.1
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'far' ) {
publicThis.threeObject3d.far = 1000
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'zoom' ) {
publicThis.threeObject3d.zoom = 1
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'active' ) {
this._setSceneCamera( 'unset' )
}
},
_attributeAddedOrChanged(attr, newVal) {
const publicThis = Public(this)
if ( attr == 'fov' ) {
publicThis.threeObject3d.fov = parseFloat(newVal)
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'aspect' ) {
this._stopAutoAspect()
publicThis.threeObject3d.aspect = parseFloat(newVal)
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'near' ) {
publicThis.threeObject3d.near = parseFloat(newVal)
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'far' ) {
publicThis.threeObject3d.far = parseFloat(newVal)
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'zoom' ) {
publicThis.threeObject3d.zoom = parseFloat(newVal)
publicThis.threeObject3d.updateProjectionMatrix()
}
else if ( attr == 'active' ) {
this._setSceneCamera()
}
},
_startAutoAspect() {
if (!this._startedAutoAspect) {
this._startedAutoAspect = true
Public(this).scene.on('sizechange', this._updateAspectOnSceneResize, this)
}
},
_stopAutoAspect() {
if (this._startedAutoAspect) {
this._startedAutoAspect = false
Public(this).scene.off('sizechange', this._updateAspectOnSceneResize)
}
},
_updateAspectOnSceneResize({x, y}) {
Public(this).threeObject3d.aspect = x / y
},
_getDefaultAspect() {
let result = 0
const publicThis = Public(this)
if ( publicThis.scene ) {
result = publicThis.scene.calculatedSize.x / publicThis.scene.calculatedSize.y
}
// in case of a 0 or NaN (0 / 0 == NaN)
if (!result) result = 16 / 9
return result
},
_setSceneCamera( unset ) {
const publicThis = Public(this)
if ( unset ) {
// TODO: unset might be triggered before the scene was mounted, so
// there might not be a last known scene. We won't need this check
// when we add unmountedCallback. #150
if ( this._lastKnownScene )
this._lastKnownScene._removeCamera( publicThis )
}
else {
if (!publicThis.scene || !publicThis.isConnected) return
publicThis.scene._addCamera( publicThis )
}
},
},
}))