-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add a set of facades for common primitive meshes, using shared …
…geometries and with setters for builtin material properties.
- Loading branch information
Showing
6 changed files
with
274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { utils } from 'troika-core' | ||
import { BoxBufferGeometry } from 'three' | ||
import { MeshFacade } from './MeshFacade.js' | ||
|
||
/** | ||
* Return a singleton instance of a 1x1x1 BoxBufferGeometry | ||
* @type {function(): BoxBufferGeometry} | ||
*/ | ||
export const getBoxGeometry = utils.memoize(() => { | ||
return new BoxBufferGeometry(1, 1, 1, 1, 1) | ||
}) | ||
|
||
|
||
/** | ||
* A simple sphere, centered on the origin. | ||
* The `width` property controls x scale, the `height` property controls y scale, and the `depth` | ||
* property controls z scale. | ||
* To control the material, see {@link MeshFacade}. | ||
*/ | ||
export class BoxFacade extends MeshFacade { | ||
get geometry() { | ||
return getBoxGeometry() | ||
} | ||
|
||
set width(width) { | ||
this.scaleX = width | ||
} | ||
get width() { | ||
return this.scaleX | ||
} | ||
|
||
set height(height) { | ||
this.scaleY = height | ||
} | ||
get height() { | ||
return this.scaleY | ||
} | ||
|
||
set depth(width) { | ||
this.scaleZ = width | ||
} | ||
get depth() { | ||
return this.scaleZ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { MeshFacade } from './MeshFacade.js' | ||
import { getBoxGeometry } from './BoxFacade.js' | ||
|
||
/** | ||
* A simple cube, centered on the origin. | ||
* The `size` property sets the uniform edge length. For non-uniform boxes, use {@link BoxFacade.js#Box} | ||
* To control the material, see {@link MeshFacade}. | ||
*/ | ||
export class CubeFacade extends MeshFacade { | ||
get geometry() { | ||
return getBoxGeometry() | ||
} | ||
|
||
set size(size) { | ||
this.scale = size | ||
} | ||
get size() { | ||
return this.scale | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import Object3DFacade from '../Object3DFacade.js' | ||
import { | ||
BufferGeometry, | ||
Mesh, | ||
MeshBasicMaterial, MeshDepthMaterial, MeshDistanceMaterial, | ||
MeshLambertMaterial, MeshMatcapMaterial, MeshNormalMaterial, | ||
MeshPhongMaterial, MeshPhysicalMaterial, | ||
MeshStandardMaterial, MeshToonMaterial | ||
} from 'three' | ||
|
||
const dummyGeometry = new BufferGeometry() | ||
const dummyMaterial = new MeshBasicMaterial() | ||
|
||
export const MESH_MATERIALS = { | ||
'basic': MeshBasicMaterial, | ||
'depth': MeshDepthMaterial, | ||
'distance': MeshDistanceMaterial, | ||
'lambert': MeshLambertMaterial, | ||
'matcap': MeshMatcapMaterial, | ||
'normal': MeshNormalMaterial, | ||
'phong': MeshPhongMaterial, | ||
'physical': MeshPhysicalMaterial, | ||
'standard': MeshStandardMaterial, | ||
'toon': MeshToonMaterial, | ||
} | ||
|
||
|
||
|
||
/** | ||
* A facade for rendering a Mesh. The following properties are supported: | ||
* | ||
* @member {Geometry|BufferGeometry} geometry - The geometry instance to be used for this | ||
* mesh. It's recommended to use a shared geometry instance between meshes when possible. | ||
* @member {string|class|Material} material - The type of the material to be used for this mesh. Can either | ||
* be a reference to a Material class, a Material instance, or one of the strings in the `MESH_MATERIALS` | ||
* enum. Defaults to 'standard'. | ||
* @member {boolean} autoDisposeGeometry - Whether the geometry should be automatically disposed when this | ||
* mesh is removed from the scene. Defaults to `false`. You can set it to `true` as a memory optimization | ||
* if the geometry is not expected to return to the scene later, but this is not generally needed. | ||
* @member {boolean} autoDisposeMaterial - Whether the material's shader program should be automatically disposed | ||
* when this mesh is removed from the scene. Defaults to `false`. You can set it to `true` as a memory | ||
* optimization if the material uses a custom shader that is not expected to be used again, but this is | ||
* not generally needed. | ||
* | ||
* Also, for convenience, properties of the material can be set via `material.*` shortcut properties. For example, | ||
* passing `{"material.transparent": true, "material.opacity": 0.5}` will set the material to half-opaque | ||
* transparency. Colors will call `set` on the Color object for that material property. | ||
*/ | ||
export class MeshFacade extends Object3DFacade { | ||
constructor (parent) { | ||
super(parent, new Mesh(dummyGeometry, dummyMaterial)) | ||
this.material = 'standard' | ||
this.autoDisposeGeometry = false | ||
this.autoDisposeMaterial = false | ||
this._dirtyMtlProps = null | ||
} | ||
|
||
afterUpdate() { | ||
let {geometry, material, threeObject} = this | ||
|
||
threeObject.geometry = geometry || dummyGeometry | ||
|
||
// Resolve `material` prop to a Material instance | ||
if (material !== this._lastMtl) { | ||
this._lastMtl = material | ||
if (typeof material === 'string') { | ||
material = new (MESH_MATERIALS[material] || MeshStandardMaterial)() | ||
} | ||
else if (material && material.isMaterial) { | ||
// material = material | ||
} | ||
else if (typeof material === 'function') { | ||
material = new material() | ||
} | ||
else { | ||
material = new MeshStandardMaterial() | ||
} | ||
threeObject.material = material | ||
} | ||
|
||
// If any of the material setters were called, sync the dirty values to the material | ||
const dirties = this._dirtyMtlProps | ||
if (dirties) { | ||
threeObject.material.setValues(dirties) | ||
this._dirtyMtlProps = null | ||
} | ||
|
||
super.afterUpdate() | ||
} | ||
|
||
destructor () { | ||
if (this.autoDisposeGeometry) { | ||
this.threeObject.geometry.dispose() | ||
} | ||
if (this.autoDisposeMaterial) { | ||
this.threeObject.material.dispose() | ||
} | ||
super.destructor() | ||
} | ||
} | ||
|
||
// For all of the known mesh materials, add `material.*` setters for all of their | ||
// supported properties. The setters will update a "dirty" object which will then be | ||
// applied to the material during afterUpdate; this lets us only deal with the specific | ||
// material props that have been set rather than having to iterate over all props. | ||
const ignoreMaterialProps = {type:1, id:1, uuid:1, version:1} | ||
Object.keys(MESH_MATERIALS).forEach(key => { | ||
let material = new MESH_MATERIALS[key]() | ||
for (let mtlProp in material) { | ||
if (material.hasOwnProperty(mtlProp) && !ignoreMaterialProps.hasOwnProperty(mtlProp)) { | ||
Object.defineProperty(MeshFacade.prototype, `material.${mtlProp}`, { | ||
enumerable: true, | ||
configurable: true, | ||
get() { | ||
const dirties = this._dirtyMtlProps | ||
return (dirties && mtlProp in dirties) ? dirties[mtlProp] : this.threeObject.material[mtlProp] | ||
}, | ||
set(value) { | ||
const dirties = this._dirtyMtlProps || (this._dirtyMtlProps = Object.create(null)) | ||
dirties[mtlProp] = value | ||
} | ||
}) | ||
} | ||
} | ||
}) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { utils } from 'troika-core' | ||
import { PlaneBufferGeometry } from 'three' | ||
import { MeshFacade } from './MeshFacade.js' | ||
|
||
const getGeometry = utils.memoize(() => { | ||
return new PlaneBufferGeometry(1, 1, 1, 1).rotateX(Math.PI / 2) | ||
}) | ||
|
||
/** | ||
* A simple rectangular plane that lays along the x-z plane, facing up, centered on the origin. | ||
* The `width` property controls x scale and the `depth` property controls z scale. | ||
* To control the material, see {@link MeshFacade}. | ||
*/ | ||
export class PlaneFacade extends MeshFacade { | ||
get geometry() { | ||
return getGeometry() | ||
} | ||
|
||
set width(width) { | ||
this.scaleX = width | ||
} | ||
get width() { | ||
return this.scaleX | ||
} | ||
|
||
set depth(width) { | ||
this.scaleZ = width | ||
} | ||
get depth() { | ||
return this.scaleZ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { utils } from 'troika-core' | ||
import { SphereBufferGeometry } from 'three' | ||
import { MeshFacade } from './MeshFacade.js' | ||
|
||
function createGeometry(widthSegments, heightSegments) { | ||
return new SphereBufferGeometry(1, widthSegments, heightSegments) | ||
} | ||
|
||
const geometries = Object.create(null, { | ||
low: { | ||
get: utils.memoize(() => createGeometry(16, 12)) | ||
}, | ||
medium: { | ||
get: utils.memoize(() => createGeometry(32, 24)) | ||
}, | ||
high: { | ||
get: utils.memoize(() => createGeometry(64, 48)) | ||
} | ||
}) | ||
|
||
export function getSphereGeometry(detail) { | ||
return geometries[detail] || geometries.medium | ||
} | ||
|
||
/** | ||
* A simple sphere, centered on the origin. | ||
* The `radius` property is an alias to the uniform `scale`. Set `scaleX/Y/Z` individually if | ||
* you need non-uniform scaling. | ||
* The `detail` property allows selecting a LOD; its values can be 'low', 'medium', or 'high'. | ||
* To control the material, see {@link MeshFacade}. | ||
*/ | ||
export class SphereFacade extends MeshFacade { | ||
get geometry() { | ||
return getSphereGeometry(this.detail) | ||
} | ||
|
||
set radius(r) { | ||
this.scale = r | ||
} | ||
get radius() { | ||
return this.scale | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters