Skip to content

Commit

Permalink
Merge pull request #49 from gkjohnson/halfedge
Browse files Browse the repository at this point in the history
Add a half edge structure optimization
  • Loading branch information
gkjohnson committed Jun 22, 2022
2 parents 9fee10c + 42a9e22 commit 6acd111
Show file tree
Hide file tree
Showing 12 changed files with 847 additions and 169 deletions.
15 changes: 13 additions & 2 deletions examples/geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const params = {
wireframe: false,
displayBrushes: false,
shadows: true,
useGroups: true,

randomize: () => {

Expand All @@ -34,6 +35,7 @@ let material, surfaceSampler;
let resultObject, wireframeResult, light;
let csgEvaluator = new Evaluator();
csgEvaluator.attributes = [ 'position', 'normal' ];
csgEvaluator.useGroups = false;

const materialMap = new Map();

Expand Down Expand Up @@ -189,6 +191,7 @@ async function init() {
gui.add( params, 'displayBrushes' );
gui.add( params, 'shadows' );
gui.add( params, 'wireframe' );
gui.add( params, 'useGroups' ).onChange( updateCSG );
gui.add( params, 'randomize' );

window.addEventListener( 'resize', function () {
Expand Down Expand Up @@ -219,9 +222,17 @@ function updateCSG() {

}

csgEvaluator.useGroups = true;
csgEvaluator.useGroups = params.useGroups;
csgEvaluator.evaluate( bunnyBrush, finalBrush, params.operation, resultObject );
resultObject.material = resultObject.material.map( m => materialMap.get( m ) );
if ( params.useGroups ) {

resultObject.material = resultObject.material.map( m => materialMap.get( m ) );

} else {

resultObject.material = materialMap.get( bunnyBrush.material );

}

const deltaTime = window.performance.now() - startTime;
outputContainer.innerText = `${ deltaTime.toFixed( 3 ) }ms`;
Expand Down
26 changes: 26 additions & 0 deletions examples/multimaterial.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>three-bvh-csg - Multi Material three-bvh-csg</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<style type="text/css">
html, body {
padding: 0;
margin: 0;
overflow: hidden;
font-family: monospace;
background-color: #111111;
}

canvas {
width: 100%;
height: 100%;
}

</style>
</head>
<body>
<script type="module" src="./multimaterial.js"></script>
</body>
</html>
138 changes: 138 additions & 0 deletions examples/multimaterial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as THREE from 'three';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import {
Brush,
Evaluator,
ADDITION,
SUBTRACTION,
INTERSECTION,
} from '..';

let renderer, camera, scene, controls, gui, light, wireframeResult;
init();
render();

async function init() {

const bgColor = 0x111111;

// renderer setup
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( bgColor, 1 );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
document.body.appendChild( renderer.domElement );

// scene setup
scene = new THREE.Scene();

// lights
light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 1, 2, 1 );
scene.add( light, new THREE.AmbientLight( 0xb0bec5, 0.1 ) );

// shadows
const shadowCam = light.shadow.camera;
light.castShadow = true;
light.shadow.mapSize.setScalar( 4096 );
light.shadow.bias = 1e-5;
light.shadow.normalBias = 1e-2;

shadowCam.left = shadowCam.bottom = - 2.5;
shadowCam.right = shadowCam.top = 2.5;
shadowCam.updateProjectionMatrix();

// camera setup
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
camera.position.set( - 2, 1.5, 2 );
camera.far = 100;
camera.updateProjectionMatrix();

// controls
controls = new OrbitControls( camera, renderer.domElement );

// floor
const floor = new THREE.Mesh( new THREE.PlaneBufferGeometry(), new THREE.ShadowMaterial( { opacity: 0.05 } ) );
floor.material.color.set( 0xE0F7FA ).convertSRGBToLinear();
floor.rotation.x = - Math.PI / 2;
floor.scale.setScalar( 10 );
floor.position.y = - 0.75;
floor.receiveShadow = true;
scene.add( floor );

// materials
const redMaterial = new THREE.MeshStandardMaterial( { roughness: 0.25 } );
const greenMaterial = new THREE.MeshStandardMaterial( { roughness: 0.25 } );
const blueMaterial = new THREE.MeshStandardMaterial( { roughness: 0.25 } );

redMaterial.color.set( 0xFF1744 ).convertSRGBToLinear();
greenMaterial.color.set( 0x76FF03 ).convertSRGBToLinear();
blueMaterial.color.set( 0x2979FF ).convertSRGBToLinear();

// basic pieces
const cylinder1 = new Brush( new THREE.CylinderBufferGeometry( 0.5, 0.5, 6, 45 ), blueMaterial );
cylinder1.updateMatrixWorld();

const cylinder2 = new Brush( new THREE.CylinderBufferGeometry( 0.5, 0.5, 6, 45 ), blueMaterial );
cylinder2.rotation.x = Math.PI / 2;
cylinder2.updateMatrixWorld();

const cylinder3 = new Brush( new THREE.CylinderBufferGeometry( 0.5, 0.5, 6, 45 ), blueMaterial );
cylinder3.rotation.z = Math.PI / 2;
cylinder3.updateMatrixWorld();

const sphere = new Brush( new THREE.SphereBufferGeometry( 1, 50, 50 ), greenMaterial );
sphere.updateMatrixWorld();

const box = new Brush( new THREE.BoxBufferGeometry( 1.5, 1.5, 1.5 ), redMaterial );
box.updateMatrixWorld();

// processing
const evaluator = new Evaluator();
let result;
result = evaluator.evaluate( cylinder1, cylinder2, ADDITION );
result = evaluator.evaluate( result, cylinder3, ADDITION );
result = evaluator.evaluate( sphere, result, SUBTRACTION );
result = evaluator.evaluate( box, result, INTERSECTION );

result.castShadow = true;
result.receiveShadow = true;
scene.add( result );

// add wireframe representation
wireframeResult = new THREE.Mesh( result.geometry, new THREE.MeshBasicMaterial( {
wireframe: true,
color: 0,
opacity: 0.15,
transparent: true,
} ) );
wireframeResult.material.color.set( 0x001516 ).convertSRGBToLinear();
wireframeResult.visible = false;
scene.add( wireframeResult );

// gui
gui = new GUI();
gui.add( wireframeResult, 'visible' ).name( 'wireframe' );

window.addEventListener( 'resize', function () {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}, false );


}

function render() {

requestAnimationFrame( render );
renderer.render( scene, camera );

}
34 changes: 27 additions & 7 deletions examples/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as THREE from 'three';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
import { MeshBVHVisualizer } from 'three-mesh-bvh';
import {
Brush,
Expand Down Expand Up @@ -50,14 +52,15 @@ let brush1, brush2;
let resultObject, wireframeResult, light, originalMaterial;
let edgesHelper, trisHelper;
let bvhHelper1, bvhHelper2;
let bunnyGeom;
let needsUpdate = true;
let csgEvaluator = new Evaluator();
let csgEvaluator;

const materialMap = new Map();

init();
render();

function init() {
async function init() {

const bgColor = 0x111111;

Expand Down Expand Up @@ -116,6 +119,10 @@ function init() {
} );
scene.add( transformControls );

// bunny mesh has no UVs so skip that attribute
csgEvaluator = new Evaluator();
csgEvaluator.attributes = [ 'position', 'normal' ];

// initialize brushes
brush1 = new Brush( new THREE.BoxBufferGeometry(), new THREE.MeshStandardMaterial() );
brush2 = new Brush( new THREE.BoxBufferGeometry(), new THREE.MeshStandardMaterial() );
Expand Down Expand Up @@ -202,6 +209,14 @@ function init() {
bvhHelper2 = new MeshBVHVisualizer( brush2, 20 );
scene.add( bvhHelper1, bvhHelper2 );

// load bunny geometry
const gltf = await new GLTFLoader()
.setMeshoptDecoder( MeshoptDecoder )
.loadAsync( 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/main/models/stanford-bunny/bunny.glb' );

bunnyGeom = gltf.scene.children[ 0 ].geometry;
bunnyGeom.computeVertexNormals();

// gui
gui = new GUI();
gui.add( params, 'operation', { ADDITION, SUBTRACTION, INTERSECTION, DIFFERENCE } ).onChange( () => {
Expand Down Expand Up @@ -229,8 +244,8 @@ function init() {
} );

csgEvaluator.attributes = v ?
[ 'color', 'position', 'uv', 'normal' ] :
[ 'position', 'uv', 'normal' ];
[ 'color', 'position', 'normal' ] :
[ 'position', 'normal' ];

needsUpdate = true;

Expand All @@ -254,7 +269,7 @@ function init() {
} );

const brush1Folder = gui.addFolder( 'brush 1' );
brush1Folder.add( params, 'brush1Shape', [ 'sphere', 'box', 'cylinder', 'torus', 'torus knot' ] ).name( 'shape' ).onChange( v => {
brush1Folder.add( params, 'brush1Shape', [ 'sphere', 'box', 'cylinder', 'torus', 'torus knot', 'mesh' ] ).name( 'shape' ).onChange( v => {

updateBrush( brush1, v, params.brush1Complexity );

Expand All @@ -272,7 +287,7 @@ function init() {
} );

const brush2Folder = gui.addFolder( 'brush 2' );
brush2Folder.add( params, 'brush2Shape', [ 'sphere', 'box', 'cylinder', 'torus', 'torus knot' ] ).name( 'shape' ).onChange( v => {
brush2Folder.add( params, 'brush2Shape', [ 'sphere', 'box', 'cylinder', 'torus', 'torus knot', 'mesh' ] ).name( 'shape' ).onChange( v => {

updateBrush( brush2, v, params.brush2Complexity );

Expand Down Expand Up @@ -324,6 +339,8 @@ function init() {

} );

render();

}

function updateBrush( brush, type, complexity ) {
Expand Down Expand Up @@ -363,6 +380,9 @@ function updateBrush( brush, type, complexity ) {
Math.round( THREE.MathUtils.lerp( 4, 16, complexity ) ),
);
break;
case 'mesh':
brush.geometry = bunnyGeom.clone();
break;

}

Expand Down
42 changes: 37 additions & 5 deletions src/core/Brush.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Mesh, Matrix4 } from 'three';
import { MeshBVH } from 'three-mesh-bvh';
import { HalfEdgeMap } from './HalfEdgeMap.js';
import { areSharedArrayBuffersSupported, convertToSharedArrayBuffer } from './utils.js';

export class Brush extends Mesh {
Expand Down Expand Up @@ -35,8 +36,6 @@ export class Brush extends Mesh {

prepareGeometry() {

// - half edges

// generate shared array buffers
const geometry = this.geometry;
const attributes = geometry.attributes;
Expand All @@ -61,16 +60,49 @@ export class Brush extends Mesh {
if ( ! geometry.boundsTree ) {

geometry.boundsTree = new MeshBVH( geometry, { maxLeafTris: 3 } );
if ( geometry.halfEdges ) {

geometry.halfEdges.updateFrom( geometry );

}

}

// generate half edges
if ( ! geometry.halfEdges ) {

geometry.halfEdges = new HalfEdgeMap( geometry );

}

// save group indices for materials
if ( ! geometry.groupIndices ) {

const triCount = geometry.index.count / 3;
const array = new Uint16Array( triCount );
const groups = geometry.groups;
for ( let i = 0, l = groups.length; i < l; i ++ ) {

const { start, count } = groups[ i ];
for ( let g = start / 3, lg = ( start + count ) / 3; g < lg; g ++ ) {

array[ g ] = i;

}

}

geometry.groupIndices = array;

}

}

disposeCacheData() {

// - half edges

this.geometry.boundsTree = null;
const { geometry } = this;
geometry.halfEdges = null;
geometry.boundsTree = null;

}

Expand Down

0 comments on commit 6acd111

Please sign in to comment.