Skip to content

Commit

Permalink
Add floorplan demo
Browse files Browse the repository at this point in the history
  • Loading branch information
gkjohnson committed Sep 30, 2023
1 parent 5f4a286 commit 0b1f83f
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 4 deletions.
51 changes: 51 additions & 0 deletions example/floorProjection.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>three-edge-projection - Projected Edge Generation</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: #111;
}

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

#output {
color: white;
position: absolute;
left: 10px;
bottom: 10px;
white-space: pre;
}

#info {
position: absolute;
top: 0;
width: 100%;
color: white;
font-family: monospace;
text-align: center;
padding: 5px 0;
}

a {
color: white;
}
</style>
</head>
<body>
<div id="info">
Floor plan silhouette and edge projection from <a href="https://sketchfab.com/3d-models/3d-floor-plan-of-home-apartment-office-layout-506ed4379b5940a793e652c1b7c0d836">Sketchfab model</a>.
</div>
<div id="output">loading...</div>
<script type="module" src="./floorProjection.js"></script>
</body>
</html>
221 changes: 221 additions & 0 deletions example/floorProjection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import {
Box3,
WebGLRenderer,
Scene,
DirectionalLight,
AmbientLight,
Group,
BufferGeometry,
LineSegments,
LineBasicMaterial,
PerspectiveCamera,
MeshBasicMaterial,
Mesh,
DoubleSide,
} from 'three';
import { MapControls } from 'three/examples/jsm/controls/MapControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
import { ProjectionGenerator, SilhouetteGenerator } from '../src';

const ANGLE_THRESHOLD = 50;
let renderer, camera, scene, gui, controls;

Check warning on line 23 in example/floorProjection.js

View workflow job for this annotation

GitHub Actions / build (16.x)

'gui' is defined but never used
let model, outlines, group, silhouette;
let outputContainer;
let task = null;

init();

async function init() {

outputContainer = document.getElementById( 'output' );

const bgColor = 0x111111;

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

// scene setup
scene = new Scene();

// lights
const light = new DirectionalLight( 0xffffff, 3.5 );
light.position.set( 1, 2, 3 );
scene.add( light );

const ambientLight = new AmbientLight( 0xb0bec5, 0.5 );
scene.add( ambientLight );

// load model
group = new Group();
scene.add( group );

const gltf = await new GLTFLoader()
.setMeshoptDecoder( MeshoptDecoder )
.loadAsync( 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/main/models/3d-home-layout/scene.glb' );
model = gltf.scene;
group.updateMatrixWorld( true );

// center model
const box = new Box3();
box.setFromObject( model, true );
box.getCenter( group.position ).multiplyScalar( - 1 );
group.position.y = Math.max( 0, - box.min.y ) + 1;
group.add( model );
model.visible = false;

// create projection display mesh
silhouette = new Mesh( new BufferGeometry(), new MeshBasicMaterial( {
color: '#eee',
polygonOffset: true,
polygonOffsetFactor: 3,
polygonOffsetUnits: 3,
side: DoubleSide,
} ) );
outlines = new LineSegments( new BufferGeometry(), new LineBasicMaterial( { color: 0x030303 } ) );
scene.add( outlines, silhouette );

// camera setup
camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.01, 50 );
camera.position.setScalar( 5.5 );
camera.updateProjectionMatrix();

// controls
controls = new MapControls( camera, renderer.domElement );
controls.zoomToCursor = true;
controls.maxPolarAngle = Math.PI / 3;

task = updateProjection();

render();

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

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

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

}, false );

}

function* updateProjection() {

outputContainer.innerText = 'processing: --';
silhouette.visible = false;
outlines.visible = false;

// transform and merge geometries to project into a single model
const geometries = [];
model.updateWorldMatrix( true, true );
model.traverse( c => {

if ( c.geometry ) {

const clone = c.geometry.clone();
clone.applyMatrix4( c.matrixWorld );
for ( const key in clone.attributes ) {

if ( key !== 'position' ) {

clone.deleteAttribute( key );

}

}

geometries.push( clone );

}

} );
const mergedGeometry = mergeGeometries( geometries, false );

yield;

// generate the silhouette
let task, result, generator;
generator = new SilhouetteGenerator();
generator.sortTriangles = true;
task = generator.generate( mergedGeometry, {

onProgress: ( p, data ) => {

outputContainer.innerText = `processing: ${ parseFloat( ( p * 100 ).toFixed( 2 ) ) }%`;
silhouette.geometry.dispose();
silhouette.geometry = data.getGeometry();
silhouette.visible = true;

},

} );

result = task.next();
while ( ! result.done ) {

result = task.next();
yield;

}

silhouette.geometry.dispose();
silhouette.geometry = result.value;
silhouette.visible = true;
outputContainer.innerText = 'generating intersection edges...';

// generate the edges
generator = new ProjectionGenerator();
generator.angleThreshold = ANGLE_THRESHOLD;
task = generator.generate( mergedGeometry, {

onProgress: ( p, data ) => {

outputContainer.innerText = `processing: ${ parseFloat( ( p * 100 ).toFixed( 2 ) ) }%`;
outlines.geometry.dispose();
outlines.geometry = data.getLineGeometry();
outlines.visible = true;

},

} );

result = task.next();
while ( ! result.done ) {

result = task.next();
yield;

}

outlines.geometry.dispose();
outlines.geometry = result.value;
outlines.visible = true;
outputContainer.innerText = '';

}


function render() {

requestAnimationFrame( render );

if ( task ) {

const res = task.next();
if ( res.done ) {

task = null;

}

}

renderer.render( scene, camera );

}
5 changes: 3 additions & 2 deletions src/ProjectionGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class ProjectionGenerator {
this.sortEdges = true;
this.iterationTime = 30;
this.angleThreshold = 50;
this.includeIntersectionEdges = false;
this.includeIntersectionEdges = true;

}

Expand Down Expand Up @@ -121,7 +121,8 @@ export class ProjectionGenerator {
let edges = generateEdges( geometry, UP_VECTOR, angleThreshold );
if ( includeIntersectionEdges ) {

edges = edges.concat( generateIntersectionEdges( bvh ) );
const results = yield* generateIntersectionEdges( bvh, iterationTime );
edges = edges.concat( results );

}

Expand Down
12 changes: 10 additions & 2 deletions src/utils/generateIntersectionEdges.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { isLineTriangleEdge } from './triangleLineUtils.js';
const OFFSET_EPSILON = 1e-6;
const _tri = new ExtendedTriangle();
const _line = new Line3();
export function generateIntersectionEdges( bvh ) {
export function* generateIntersectionEdges( bvh, iterationTime = 30 ) {

const edges = [];
const geometry = bvh.geometry;
const index = geometry.index;
const posAttr = geometry.attributes.position;
const vertCount = index ? index.count : posAttr;


let time = performance.now();
for ( let i = 0; i < vertCount; i += 3 ) {

let i0 = i + 0;
Expand Down Expand Up @@ -78,6 +78,14 @@ export function generateIntersectionEdges( bvh ) {

} );

const delta = performance.now() - time;
if ( delta > iterationTime ) {

yield;
time = performance.now();

}

}

return edges;
Expand Down

0 comments on commit 0b1f83f

Please sign in to comment.