Przeglądaj źródła

bindToValue decorator and better accessible camera near far properties.

master
Palash Bansal 2 lat temu
rodzic
commit
db8a127970
No account linked to committer's email address

+ 5
- 0
src/core/ICamera.ts Wyświetl plik

@@ -71,6 +71,11 @@ export interface ICamera<E extends ICameraEvent = ICameraEvent, ET extends ICame
controlsMode?: TCameraControlsMode; // todo add more.
// controlsEnabled: boolean; // use controlsMode = '' instead

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

// 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
userData: ICameraUserData

+ 39
- 17
src/core/camera/PerspectiveCamera2.ts Wyświetl plik

@@ -8,6 +8,7 @@ import {OrbitControls3} from '../../three'
import {IObject3D} from '../IObject'
import {ThreeSerialization} from '../../utils'
import {iCameraCommons} from '../object/iCameraCommons'
import {bindToValue} from '../../three/utils/decorators'

// todo: maybe change domElement to some wrapper/base class of viewer
export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
@@ -58,17 +59,41 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
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)
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)
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) {
super(fov, aspect)
this._canvas = domElement
@@ -261,13 +286,13 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
if (op.focus) data.focus = op.focus
if (op.zoom) data.zoom = op.zoom
if (op.aspect) data.aspect = op.aspect
if (op.controlsMode) data.controlsMode = op.controlsMode
// todo: add support for this
// if (op.left) data.left = op.left
// if (op.right) data.right = op.right
// if (op.top) data.top = op.top
// if (op.bottom) data.bottom = op.bottom
// if (op.frustumSize) data.frustumSize = op.frustumSize
// if (op.controlsMode) data.controlsMode = op.controlsMode
// if (op.controlsEnabled) data.controlsEnabled = op.controlsEnabled
delete data.camOptions
}
@@ -312,17 +337,18 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
...generateUiConfig(this),
{
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',
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
type: 'dropdown',
@@ -342,16 +368,12 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
()=>(this._controls as OrbitControls3)?.zoomIn ? {
type: 'button',
label: 'Zoom in',
value: ()=>{
(this._controls as OrbitControls3)?.zoomIn(1)
},
value: ()=> (this._controls as OrbitControls3)?.zoomIn(1),
} : {},
()=>(this._controls as OrbitControls3)?.zoomOut ? {
type: 'button',
label: 'Zoom out',
value: ()=>{
(this._controls as OrbitControls3)?.zoomOut(1)
},
value: ()=> (this._controls as OrbitControls3)?.zoomOut(1),
} : {},
()=>this._controls?.uiConfig,
],

+ 54
- 13
src/three/utils/decorators.ts Wyświetl plik

@@ -1,4 +1,4 @@
import {AnyFunction, safeSetProperty} from 'ts-browser-helpers'
import {AnyFunction, getOrCall, safeSetProperty, ValOrFunc} from 'ts-browser-helpers'

/**
*
@@ -38,6 +38,18 @@ export function uniform({uniforms, propKey, thisTarget = false}: {uniforms?: any
}
}

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
@@ -68,18 +80,7 @@ export function matDefine(key?: string|symbol, customDefines?: any, thisMat = fa
safeSetProperty(t, p, newVal, true)
if (newVal === undefined) delete t[p]
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 {
safeSetProperty(thisMat ? this : this.material, 'needsUpdate', true, true)
}
@@ -89,3 +90,43 @@ export function matDefine(key?: string|symbol, customDefines?: any, thisMat = fa
})
}
}

/**
* 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,
})
}
}

Ładowanie…
Anuluj
Zapisz