From 0739f4d6ebcdd13be46b6371c95504b290c86359 Mon Sep 17 00:00:00 2001 From: Jason Johnston Date: Thu, 26 Mar 2020 12:26:13 -0600 Subject: [PATCH] feat(examples): beziers: add instanceable version of the beziers, and add point light option --- .../bezier-3d/Bezier3DExample.jsx | 25 +++- .../bezier-3d/Bezier3DFacade.js | 112 +++++++++++++++++- .../globe-connections/ConnectionsFacade.js | 4 +- packages/troika-three-utils/src/BezierMesh.js | 10 +- 4 files changed, 138 insertions(+), 13 deletions(-) diff --git a/packages/troika-examples/bezier-3d/Bezier3DExample.jsx b/packages/troika-examples/bezier-3d/Bezier3DExample.jsx index c527fed0..9d596646 100644 --- a/packages/troika-examples/bezier-3d/Bezier3DExample.jsx +++ b/packages/troika-examples/bezier-3d/Bezier3DExample.jsx @@ -1,6 +1,6 @@ import React from 'react' import { Canvas3D, Group3DFacade } from 'troika-3d' -import Bezier3DFacade from './Bezier3DFacade' +import { Bezier3DFacade, Bezier3DInstanceableFacade } from './Bezier3DFacade' import ShadowSurface from './ShadowSurfaceFacade' import DatGui, {DatBoolean, DatNumber} from 'react-dat-gui' @@ -18,6 +18,9 @@ class Bezier3DExample extends React.Component { dashed: 0, randomize: true, shadows: true, + instancing: false, + pointLight: false, + directionalLight: true, p1x: -1, p1y: 0, p1z: 0, @@ -41,7 +44,7 @@ class Bezier3DExample extends React.Component { for (let i = 0; i < (state.randomize ? state.count : 1); i++) { const obj = { key: 'bezier' + i, - facade: Bezier3DFacade, + facade: state.instancing ? Bezier3DInstanceableFacade : Bezier3DFacade, radius: state.radius, color: state.randomize ? (colors[i] || (colors[i] = randomColor())) : 0x66ccff, castShadow: state.shadows, @@ -66,16 +69,24 @@ class Bezier3DExample extends React.Component { z: vr ? 1 : 3 } } lights={[ - { + state.directionalLight && { type: 'directional', z: 2, y: 1, castShadow: state.shadows, shadow: { - //mapSize: {width: 1024, height: 1024}, camera: {far: 10, near: 0.1, left: -2, right: 2, top: 2, bottom: -2} } }, + state.pointLight && { + type: 'point', + z: 0, + y: 0, + castShadow: state.shadows, + shadow: { + camera: {far: 10, near: 0.001} + } + }, ]} objects={[ { @@ -105,8 +116,12 @@ class Bezier3DExample extends React.Component { + + + + - {state.randomize ? : null } + {state.randomize ? : null } {state.randomize ? null : pointProps.map(prop => diff --git a/packages/troika-examples/bezier-3d/Bezier3DFacade.js b/packages/troika-examples/bezier-3d/Bezier3DFacade.js index 73793106..f3470117 100644 --- a/packages/troika-examples/bezier-3d/Bezier3DFacade.js +++ b/packages/troika-examples/bezier-3d/Bezier3DFacade.js @@ -1,12 +1,15 @@ -import { Object3DFacade } from 'troika-3d' +import { Object3DFacade, Instanceable3DFacade } from 'troika-3d' import { BezierMesh } from 'troika-three-utils' +import { Color, MeshStandardMaterial, Vector3 } from 'three' const noDash = [0, 0] +const tempColor = new Color() +const defaultMaterial = new MeshStandardMaterial({transparent: true}) /** * Facade wrapper around BezierMesh from three-troika-utils */ -class Bezier3DFacade extends Object3DFacade { +export class Bezier3DFacade extends Object3DFacade { constructor(parent) { super(parent, new BezierMesh()) this.radius = 0.01 @@ -14,6 +17,7 @@ class Bezier3DFacade extends Object3DFacade { this.color = 0xffffff this.dashArray = [0, 0] this.dashOffset = 0 + this.material = defaultMaterial } afterUpdate() { @@ -47,4 +51,106 @@ class Bezier3DFacade extends Object3DFacade { } } -export default Bezier3DFacade + + +const instancingMeshes = new WeakMap() //cache of singleton meshes for each material + +/** + * Instanceable version + */ +export class Bezier3DInstanceableFacade extends Instanceable3DFacade { + constructor (parent) { + super(parent) + this.radius = 0.01 + this.opacity = 1 + this.color = this._color = new Color(0xffffff) + this.dashArray = [0, 0] + this.dashOffset = 0 + this.material = defaultMaterial + } + + afterUpdate () { + let { + p1x, p1y, p1z, + c1x, c1y, c1z, + c2x, c2y, c2z, + p2x, p2y, p2z, + radius, + dashArray, + dashOffset, + opacity, + color + } = this + + /* + pointA: {value: new Vector3()}, + controlA: {value: new Vector3()}, + controlB: {value: new Vector3()}, + pointB: {value: new Vector3()}, + radius: {value: 0.01}, + dashing: {value: new Vector3()} //on, off, offset + */ + if (p1x !== this._p1x || p1y !== this._p1y || p1z !== this._p1z) { + this.setInstanceUniform('pointA', new Vector3(this._p1x = p1x, this._p1y = p1y, this._p1z = p1z)) + } + if (c1x !== this._c1x || c1y !== this._c1y || c1z !== this._c1z) { + this.setInstanceUniform('controlA', new Vector3(this._c1x = c1x, this._c1y = c1y, this._c1z = c1z)) + } + if (c2x !== this._c2x || c2y !== this._c2y || c2z !== this._c2z) { + this.setInstanceUniform('controlB', new Vector3(this._c2x = c2x, this._c2y = c2y, this._c2z = c2z)) + } + if (p2x !== this._p2x || p2y !== this._p2y || p2z !== this._p2z) { + this.setInstanceUniform('pointB', new Vector3(this._p2x = p2x, this._p2y = p2y, this._p2z = p2z)) + } + if (radius !== this._radius) { + this.setInstanceUniform('radius', this._radius = radius) + } + dashArray = dashArray || noDash + let lastDashArray = this._dashArray || noDash + if (dashArray[0] !== lastDashArray[0] || dashArray[1] !== lastDashArray[1] || dashOffset !== this._dashOffset) { + this._dashArray = dashArray + this.setInstanceUniform('dashing', new Vector3(dashArray[0], dashArray[1], this._dashOffset = dashOffset)) + } + + // Material color and opacity: + if (!tempColor.set(color).equals(this._color)) { + this.setInstanceUniform('diffuse', this._color = new Color(color)) + } + if (opacity !== this._opacity) { + this.setInstanceUniform('opacity', this._opacity = opacity) + } + + super.afterUpdate() + } + + set material(material) { + if (material !== this._material) { + this._material = material + let mesh = instancingMeshes.get(material) + if (!mesh) { + mesh = new BezierMesh() + mesh.castShadow = true //TODO - figure out how to control shadow casting + mesh.material = material + mesh.material.instanceUniforms = this.getInstanceUniforms() + instancingMeshes.set(material, mesh) + } + this.instancedThreeObject = mesh + } + } + + getInstanceUniforms() { + return [ + 'pointA', + 'controlA', + 'controlB', + 'pointB', + 'radius', + 'dashing', + + // Set up instancing for material color and opacity by default; users can specify additional + // material-specific uniforms by extending and overriding. TODO - find a nicer way + 'diffuse', + 'opacity' + ] + } +} diff --git a/packages/troika-examples/globe-connections/ConnectionsFacade.js b/packages/troika-examples/globe-connections/ConnectionsFacade.js index 89ce7530..17ab6a19 100644 --- a/packages/troika-examples/globe-connections/ConnectionsFacade.js +++ b/packages/troika-examples/globe-connections/ConnectionsFacade.js @@ -1,5 +1,5 @@ import {Group3DFacade} from 'troika-3d' -import Bezier3DFacade from '../bezier-3d/Bezier3DFacade' +import { Bezier3DFacade, Bezier3DInstanceableFacade } from '../bezier-3d/Bezier3DFacade' import {Vector3} from 'three' @@ -46,7 +46,7 @@ class ConnectionsFacade extends Group3DFacade { // globeCtrl.applyMatrix4(globe.threeObject.matrixWorld) cxns.push({ key: cityLabel.$facadeId, - facade: Bezier3DFacade, + facade: Bezier3DInstanceableFacade, radius: hovering ? 0.0015 : 0.001, color: hovering ? 0xffffff : colors[i % colors.length], opacity: hovering ? 1.0 : 0.6, diff --git a/packages/troika-three-utils/src/BezierMesh.js b/packages/troika-three-utils/src/BezierMesh.js index 1f616711..f9bce8b9 100644 --- a/packages/troika-three-utils/src/BezierMesh.js +++ b/packages/troika-three-utils/src/BezierMesh.js @@ -34,11 +34,15 @@ const defaultBaseMaterial = new MeshStandardMaterial({color: 0xffffff, side: Dou * TODO: allow control of the geometry's segment counts */ class BezierMesh extends Mesh { + static getGeometry() { + return geometry || (geometry = + new CylinderBufferGeometry(1, 1, 1, 6, 64).translate(0, 0.5, 0) + ) + } + constructor() { super( - geometry || (geometry = - new CylinderBufferGeometry(1, 1, 1, 6, 64).translate(0, 0.5, 0) - ), + BezierMesh.getGeometry(), defaultBaseMaterial )