Sfoglia il codice sorgente

bindToValue decorator and better accessible camera near far properties.

master
Palash Bansal 2 anni fa
parent
commit
db8a127970
Nessun account collegato all'indirizzo email del committer
3 ha cambiato i file con 98 aggiunte e 30 eliminazioni
  1. 5
    0
      src/core/ICamera.ts
  2. 39
    17
      src/core/camera/PerspectiveCamera2.ts
  3. 54
    13
      src/three/utils/decorators.ts

+ 5
- 0
src/core/ICamera.ts Vedi File

controlsMode?: TCameraControlsMode; // todo add more. controlsMode?: TCameraControlsMode; // todo add more.
// controlsEnabled: boolean; // use controlsMode = '' instead // controlsEnabled: boolean; // use controlsMode = '' instead


// also in userData
autoNearFar: boolean // default = true
minNearPlane: number // default = 0.2
maxFarPlane: number // default = 1000

// todo // todo
// Note: for userData: add _ in front of for private use, which is preserved while cloning but not serialisation, and __ for private use, which is not preserved while cloning and serialisation // Note: for userData: add _ in front of for private use, which is preserved while cloning but not serialisation, and __ for private use, which is not preserved while cloning and serialisation
userData: ICameraUserData userData: ICameraUserData

+ 39
- 17
src/core/camera/PerspectiveCamera2.ts Vedi File

import {IObject3D} from '../IObject' import {IObject3D} from '../IObject'
import {ThreeSerialization} from '../../utils' import {ThreeSerialization} from '../../utils'
import {iCameraCommons} from '../object/iCameraCommons' import {iCameraCommons} from '../object/iCameraCommons'
import {bindToValue} from '../../three/utils/decorators'


// todo: maybe change domElement to some wrapper/base class of viewer // todo: maybe change domElement to some wrapper/base class of viewer
export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
autoAspect: boolean autoAspect: boolean


/** /**
* Near clipping plane. This is managed by RootScene for active cameras
* 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(PerspectiveCamera2.prototype._nearFarChanged) @onChange2(PerspectiveCamera2.prototype._nearFarChanged)
near = 0.01 near = 0.01


/** /**
* Far clipping plane. This is managed by RootScene for active cameras
* 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(PerspectiveCamera2.prototype._nearFarChanged) @onChange2(PerspectiveCamera2.prototype._nearFarChanged)
far = 50 far = 50


/**
* Automatically manage near and far clipping planes based on scene size.
*/
@bindToValue({obj: 'userData', onChange: 'setDirty'})
autoNearFar = true

/**
* Minimum near clipping plane allowed. (Distance from camera)
* @default 0.2
*/
@bindToValue({obj: 'userData', onChange: 'setDirty'})
minNearPlane = 0.2
/**
* Maximum far clipping plane allowed. (Distance from camera)
*/
@bindToValue({obj: 'userData', onChange: 'setDirty'})
maxFarPlane = 1000

constructor(controlsMode?: TCameraControlsMode, domElement?: HTMLCanvasElement, autoAspect?: boolean, fov?: number, aspect?: number) { constructor(controlsMode?: TCameraControlsMode, domElement?: HTMLCanvasElement, autoAspect?: boolean, fov?: number, aspect?: number) {
super(fov, aspect) super(fov, aspect)
this._canvas = domElement this._canvas = domElement
if (op.focus) data.focus = op.focus if (op.focus) data.focus = op.focus
if (op.zoom) data.zoom = op.zoom if (op.zoom) data.zoom = op.zoom
if (op.aspect) data.aspect = op.aspect if (op.aspect) data.aspect = op.aspect
if (op.controlsMode) data.controlsMode = op.controlsMode
// todo: add support for this // todo: add support for this
// if (op.left) data.left = op.left // if (op.left) data.left = op.left
// if (op.right) data.right = op.right // if (op.right) data.right = op.right
// if (op.top) data.top = op.top // if (op.top) data.top = op.top
// if (op.bottom) data.bottom = op.bottom // if (op.bottom) data.bottom = op.bottom
// if (op.frustumSize) data.frustumSize = op.frustumSize // if (op.frustumSize) data.frustumSize = op.frustumSize
// if (op.controlsMode) data.controlsMode = op.controlsMode
// if (op.controlsEnabled) data.controlsEnabled = op.controlsEnabled // if (op.controlsEnabled) data.controlsEnabled = op.controlsEnabled
delete data.camOptions delete data.camOptions
} }
...generateUiConfig(this), ...generateUiConfig(this),
{ {
type: 'input', type: 'input',
label: 'Min Near',
getValue: () => this.userData.minNearPlane ?? 0.2,
setValue: (v) => this.userData.minNearPlane = v,
onChange: () => this.setDirty(),
label: ()=>(this.autoNearFar ? 'Min' : '') + ' Near',
property: [this, 'minNearPlane'],
},
{
type: 'input',
label: ()=>(this.autoNearFar ? 'Max' : '') + ' Far',
property: [this, 'maxFarPlane'],
}, },
{ {
type: 'input', type: 'input',
label: 'Max Far',
getValue: () => this.userData.maxFarPlane ?? 1000,
setValue: (v) => this.userData.maxFarPlane = v,
onChange: () => this.setDirty(),
label: 'Auto Near Far',
property: [this, 'autoNearFar'],
}, },
()=>({ // because _controlsCtors can change ()=>({ // because _controlsCtors can change
type: 'dropdown', type: 'dropdown',
()=>(this._controls as OrbitControls3)?.zoomIn ? { ()=>(this._controls as OrbitControls3)?.zoomIn ? {
type: 'button', type: 'button',
label: 'Zoom in', label: 'Zoom in',
value: ()=>{
(this._controls as OrbitControls3)?.zoomIn(1)
},
value: ()=> (this._controls as OrbitControls3)?.zoomIn(1),
} : {}, } : {},
()=>(this._controls as OrbitControls3)?.zoomOut ? { ()=>(this._controls as OrbitControls3)?.zoomOut ? {
type: 'button', type: 'button',
label: 'Zoom out', label: 'Zoom out',
value: ()=>{
(this._controls as OrbitControls3)?.zoomOut(1)
},
value: ()=> (this._controls as OrbitControls3)?.zoomOut(1),
} : {}, } : {},
()=>this._controls?.uiConfig, ()=>this._controls?.uiConfig,
], ],

+ 54
- 13
src/three/utils/decorators.ts Vedi File

import {AnyFunction, safeSetProperty} from 'ts-browser-helpers'
import {AnyFunction, getOrCall, safeSetProperty, ValOrFunc} from 'ts-browser-helpers'


/** /**
* *
} }
} }


function callOnChange(this: any, onChange: (...args: any[]) => any, params: any[]) {
// same logic as onChange in ts-browser-helpers. todo: loop through object prototype chain like in onChange?
if (onChange.name) {
const fn: AnyFunction = this[onChange.name]
if (fn === onChange)
onChange.call(this, ...params)
else if (fn.name.endsWith(`bound ${onChange.name}`))
fn(...params)
else onChange(...params)
} else onChange(...params)
}

/** /**
* *
* @param customDefines - object for setting define value (like ShaderMaterial.defines), otherwise this.material.defines is taken * @param customDefines - object for setting define value (like ShaderMaterial.defines), otherwise this.material.defines is taken
safeSetProperty(t, p, newVal, true) safeSetProperty(t, p, newVal, true)
if (newVal === undefined) delete t[p] if (newVal === undefined) delete t[p]
if (onChange && typeof onChange === 'function') { if (onChange && typeof onChange === 'function') {
const params = [p, newVal]
// same logic as onChange in ts-browser-helpers. todo: loop through object prototype chain like in onChange?
if (onChange.name) {
const fn: AnyFunction = this[onChange.name]
if (fn === onChange)
onChange.call(this, ...params)
else if (fn.name.endsWith(`bound ${onChange.name}`))
fn(...params)
else onChange(...params)
} else {
onChange(...params)
}
callOnChange.call(this, onChange, [p, newVal])
} else { } else {
safeSetProperty(thisMat ? this : this.material, 'needsUpdate', true, true) safeSetProperty(thisMat ? this : this.material, 'needsUpdate', true, true)
} }
}) })
} }
} }

/**
* Binds a property to a value in an object. If the object is a string, it is used as a property name in `this`.
* @param obj - object to bind to. If a string, it is used as a property name in `this`. If a function, it is called and the result is used as the object/string.
* @param key - key to bind to. If a string, it is used as a property name in `this`. If a function, it is called and the result is used as the key/string.
* @param onChange - function to call when the value changes. If a string, it is used as a property name in `this` and called. If a function, it is called. The function is called with the following parameters: key, newVal
* @param processVal - function that processes the value before setting it.
* @param invProcessVal - function that processes the value before returning it.
*/
export function bindToValue({obj, key, onChange, processVal, invProcessVal}: {obj?: ValOrFunc<any>, key?: ValOrFunc<string | symbol>, onChange?: ((...args: any[]) => any)|string, processVal?: (newVal: any) => any, invProcessVal?: (val: any) => any}): PropertyDecorator {
const cPropKey = !!key

return (targetPrototype: any, propertyKey: string|symbol) => {
const getTarget = (_this: any)=>{
let t = getOrCall(obj) || _this
if (typeof t === 'string') t = _this[t]
const p = cPropKey ? getOrCall(key) || propertyKey : propertyKey
return {t, p}
}
Object.defineProperty(targetPrototype, propertyKey, {
get() {
const {t, p} = getTarget(this)
let res = t[p]
if (invProcessVal) res = invProcessVal(res)
return res
},
set(newVal: any) {
const {t, p} = getTarget(this)
if (processVal) newVal = processVal(newVal)
safeSetProperty(t, p, newVal, true)
if (newVal === undefined) delete t[p]
let oc = onChange
if (oc && (typeof oc === 'string' || typeof oc === 'symbol')) oc = this[oc]
if (oc && typeof oc === 'function') callOnChange.call(this, oc, [p, newVal])
},
// configurable: true,
// enumerable: true,
})
}
}

Loading…
Annulla
Salva