Skip to content

Instantly share code, notes, and snippets.

@theo-armour
Last active December 14, 2017 01:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theo-armour/163685de4d1fdacd70b2ffd446e8c874 to your computer and use it in GitHub Desktop.
Save theo-armour/163685de4d1fdacd70b2ffd446e8c874 to your computer and use it in GitHub Desktop.
gbXML Viewer
license: MIT
<!doctype html>
<html lang="en" >
<head>
<meta charset="utf-8" >
<meta name="viewport" content = "width=device-width,user-scalable=no,minimum-scale=1.0,maximum-scale=1.0" >
<meta name=description content="View gbXML files in 3D in your browser. Open files using File Reader or by URL in location.hash.Base Script used by gbXML Viewer modules." >
<meta name=keywords content="gbXML,Three.js,WebGL,JavaScript,GitHub,FOSS,3D,STEM" >
<meta name = "date" content = "2017-12-03" >
<title>gbXML Viewer8 Core R3</title>
<style>
body { font: 11pt monospace; margin: 0; overflow: hidden; }
a { color: crimson; text-decoration: none; }
a:hover, a:focus { background-color: yellow; color: #aaa; text-decoration: underline }
button, input[type=button] { background-color: #ddd; border: none; color: #322; cursor: pointer; padding: 3px 5px; }
button:hover { background: #ccc; color: #fff }
.dragDropArea { border: 1px dashed gray; }
#divMenu { left: 0; margin: auto; position: absolute; right: 0; text-align: center; width: 50%; }
</style>
</head>
<body>
<script src = "https://cdn.rawgit.com/mrdoob/three.js/r88/build/three.min.js" ></script>
<script src = "https://cdn.rawgit.com/mrdoob/three.js/r88/examples/js/controls/OrbitControls.js" ></script>
<div id = "divMenu" >
<div id = "divHeader" >
</div>
<p id = "divContents" >
<button onclick=controls.autoRotate=!controls.autoRotate; >rotation</button>
<button onclick=surfaceMeshes.visible=!surfaceMeshes.visible; >surfaces</button>
<button onclick=surfaceEdges.visible=!surfaceEdges.visible; >edges</button>
<button onclick=setAllVisible();zoomObjectBoundingSphere(surfaceMeshes); >reset view</button>
</p>
<div id=divLog ></div>
<script>
const uriGbxmlDefault = 'https://rawgit.com/ladybug-tools/spider/master/gbxml-viewer/gbxml-sample-files/bristol-clifton-down-road.xml';
var gbjson;
var surfaceMeshes;
var surfaceEdges;
var colors = {
InteriorWall: 0x008000,
ExteriorWall: 0xFFB400,
Roof: 0x800000,
InteriorFloor: 0x80FFFF,
ExposedFloor: 0x40B4FF,
Shade: 0xFFCE9D,
UndergroundWall: 0xA55200,
UndergroundSlab: 0x804000,
Ceiling: 0xFF8080,
Air: 0xFFFF00,
UndergroundCeiling: 0x408080,
RaisedFloor: 0x4B417D,
SlabOnGrade: 0x804000,
FreestandingColumn: 0x808080,
EmbeddedColumn: 0x80806E
}
var renderer, camera, controls, scene;
let lightAmbient, lightDirectional, lightPoint;
var cameraHelper, axesHelper, gridHelper, groundHelper;
init();
animate();
function init() {
divHeader.innerHTML =
'<h1 id=divTitle ><a href="" >' + document.title + '</a></h1>' +
'<p>' +
'<input type=file id=inpFile onchange=openFile(this); >' +
'<p>' +
'';
renderer = new THREE.WebGLRenderer( { alpha: 1, antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.shadowMap.renderReverseSided = false;
renderer.shadowMap.renderSingleSided = false;
document.body.appendChild( renderer.domElement );
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.1, 10000 );
camera.up.set( 0, 0, 1 );
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.autoRotate = true;
scene = new THREE.Scene();
lightAmbient = new THREE.AmbientLight( 0x444444 );
scene.add( lightAmbient );
lightDirectional = new THREE.DirectionalLight( 0xffffff, 1 );
lightDirectional.shadow.mapSize.width = 2048; // default 512
lightDirectional.shadow.mapSize.height = 2048;
lightDirectional.castShadow = true;
scene.add( lightDirectional );
lightPoint = new THREE.PointLight( 0xffffff, 0.5 );
lightPoint.position = new THREE.Vector3( 0, 0, 1 );
camera.add( lightPoint );
scene.add( camera );
axesHelper = new THREE.AxesHelper( 1 );
scene.add( axesHelper );
window.addEventListener( 'resize', onWindowResize, false );
window.addEventListener( 'orientationchange', onWindowResize, false );
window.addEventListener( 'keyup', function() { controls.autoRotate = false; }, false );
window.addEventListener ( 'hashchange', onHashChange, false );
renderer.domElement.addEventListener( 'click', function() { controls.autoRotate = false; }, false );
parent.addEventListener( 'hashchange', onHashChange, false );
console.log( 'location.hash', parent.location.hash );
onHashChange();
}
function onHashChange() {
const url = !parent.location.hash ? uriGbxmlDefault : parent.location.hash.slice( 1 );
requestFile( url, callbackGbXML );
}
function requestFile( url, callback ) {
const xhr = new XMLHttpRequest();
xhr.crossOrigin = 'anonymous';
xhr.open( 'GET', url, true );
xhr.onerror = function( xhr ) { console.log( 'error:', xhr ); };
xhr.onprogress = onRequestFileProgress;
xhr.onload = callback;
xhr.send( null );
function onRequestFileProgress( xhr ) {
divLog.innerHTML = 'bytes loaded: ' + xhr.loaded.toLocaleString() + ' of ' + xhr.total.toLocaleString() ;
}
}
function callbackGbXML( xhr ){
const response = xhr.target.responseXML.documentElement;
parseFileXML( response );
}
function openFile( files ) {
const reader = new FileReader();
reader.onprogress = onRequestFileProgress;
reader.onload = function( event ) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString( reader.result, "text/xml" );
gbjson = parseFileXML( xmlDoc.children[ 0 ] );
if ( files.files[ 0 ] ) { gbjson.name = files.files[ 0 ].name; }
}
reader.readAsText( files.files[ 0 ] );
function onRequestFileProgress( event ) {
divLog.innerHTML =
'bytes loaded: ' + event.loaded.toLocaleString() +
( event.lengthComputable ? ' of ' + event.total.toLocaleString() : '' ) +
'';
}
}
// loads any text file - from file reader or location hash or wherever
function parseFileXML( xmlNode ) {
gbjson = XML2jsobj( xmlNode );
//console.log( 'gbjson', gbjson );
parseGbJson( gbjson );
// onWindowLoad();
return gbjson;
}
// https://www.sitepoint.com/how-to-convert-xml-to-a-javascript-object/
// http://blogs.sitepointstatic.com/examples/tech/xml2json/index.html
function XML2jsobj( node ) {
let data = {};
function Add( name, value ) {
if ( data[ name ] ) {
if ( data[ name ].constructor !== Array ) {
data[ name ] = [ data[ name ] ];
}
data[ name ][ data[ name ].length ] = value;
} else {
data[ name ] = value;
}
}
let child, childNode;
for ( child = 0; childNode = node.attributes[ child ]; child++ ) {
Add( childNode.name, childNode.value );
}
for ( child = 0; childNode = node.childNodes[ child ]; child++ ) {
if ( childNode.nodeType === 1 ) {
if ( childNode.childNodes.length === 1 && childNode.firstChild.nodeType === 3 ) { // text value
Add( childNode.nodeName, childNode.firstChild.nodeValue );
} else { // sub-object
Add( childNode.nodeName, XML2jsobj( childNode ) );
}
}
}
return data;
}
function parseGbJson( gbjson ) {
//console.log( 'surfaces', gbjson.Campus.Surface );
const surfaces = gbjson.Campus.Surface;
let polyloops = [];
let openings = [];
for ( let surface of surfaces ) {
if ( surface.Opening ) {
if ( surface.Opening.PlanarGeometry ) {
const polyloop = surface.Opening.PlanarGeometry.PolyLoop;
const points = getPoints( polyloop );
openings.push( [ points ] );
} else {
let arr = [];
for ( let opening of surface.Opening ) {
polyloop = opening.PlanarGeometry.PolyLoop;
points = getPoints( polyloop );
arr.push( points );
}
openings.push( arr );
}
} else {
openings.push( [] );
}
polyloop = surface.PlanarGeometry.PolyLoop;
points = getPoints( polyloop );
polyloops.push( points );
}
scene.remove( surfaceMeshes, surfaceEdges );
if ( surfaceMeshes ) {
surfaceMeshes.traverse( function ( child ) {
if ( child.geometry ) {
child.geometry.dispose();
child.material.dispose();
}
if ( child.texture ) { child.texture.dispose(); }
} );
}
if ( surfaceEdges ) {
surfaceEdges.traverse( function ( child ) {
if ( child.geometry ) {
child.geometry.dispose();
child.material.dispose();
}
} );
}
surfaceMeshes = new THREE.Object3D();
surfaceMeshes.name = 'surfaceMeshes';
surfaceEdges = new THREE.Object3D();
surfaceEdges.name = 'surfaceEdges';
for ( let i = 0; i < polyloops.length; i++ ) {
const material = new THREE.MeshPhongMaterial( { color: colors[ surfaces[ i ].surfaceType ], side: 2, opacity: 0.85, transparent: true } );
const shape = drawShapeSinglePassObjects( polyloops[ i ], material, openings[ i ] );
shape.userData.data = surfaces[ i ];
shape.castShadow = shape.receiveShadow = true;
surfaceMeshes.add( shape );
const edgesGeometry = new THREE.EdgesGeometry( shape.geometry );
const surfaceEdge = new THREE.LineSegments( edgesGeometry, new THREE.LineBasicMaterial( { color: 0x888888 } ) );
surfaceEdge.rotation.copy( shape.rotation );
surfaceEdge.position.copy( shape.position );
surfaceEdges.add( surfaceEdge );
}
scene.add( surfaceMeshes, surfaceEdges );
zoomObjectBoundingSphere( surfaceMeshes );
}
function getPoints( polyloop ) {
const points = [];
for ( let CartesianPoint of polyloop.CartesianPoint ) {
// const p = CartesianPoint.Coordinate;
// const point = new THREE.Vector3( parseFloat( p[ 0 ] ), parseFloat( p[ 1 ] ), parseFloat( p[ 2 ] ) );
const point = new THREE.Vector3().fromArray( CartesianPoint.Coordinate );
points.push( point );
}
return points;
}
function drawShapeSinglePassObjects( vertices, material, holes ) {
// let there be simpler ways to do this
const plane = new THREE.Plane().setFromCoplanarPoints ( vertices[ 0 ], vertices[ 1 ], vertices[ 2 ] );
const obj = new THREE.Object3D();
obj.lookAt( plane.normal );
const obj2 = new THREE.Object3D();
obj2.quaternion.copy( obj.clone().quaternion.conjugate() );
obj2.updateMatrixWorld( true );
for ( let vertex of vertices ) {
obj2.localToWorld( vertex );
}
const shape = new THREE.Shape( vertices );
shape.autoClose = true;
for ( let verticesHoles of holes ) {
for ( let vertex of verticesHoles ) {
obj2.localToWorld( vertex );
}
const hole = new THREE.Path();
hole.setFromPoints( verticesHoles );
shape.holes.push( hole );
}
const geometryShape = new THREE.ShapeGeometry( shape );
let shapeMesh = new THREE.Mesh( geometryShape, material );
shapeMesh.quaternion.copy( obj.quaternion );
shapeMesh.position.copy( plane.normal.multiplyScalar( - plane.constant ) );
return shapeMesh;
}
function zoomObjectBoundingSphere( obj ) {
if ( obj.geometry ) {
// might not be necessary
obj.geometry.computeBoundingSphere();
const center = obj.geometry.boundingSphere.center;
const radius = obj.geometry.boundingSphere.radius;
} else {
const bbox = new THREE.Box3().setFromObject( obj );
const sphere = bbox.getBoundingSphere();
center = sphere.center;
radius = sphere.radius;
}
obj.userData.center = center;
obj.userData.radius = radius;
controls.target.copy( center );
controls.maxDistance = 5 * radius;
camera.position.copy( center.clone().add( new THREE.Vector3( 1.0 * radius, - 1.0 * radius, 1.0 * radius ) ) );
axesHelper.scale.set( radius, radius, radius );
axesHelper.position.copy( center );
camera.far = 10 * radius; //2 * camera.position.length();
camera.updateProjectionMatrix();
lightDirectional.position.copy( center.clone().add( new THREE.Vector3( 1.5 * radius, 1.5 * radius, 1.5 * radius ) ) );
lightDirectional.shadow.camera.scale.set( 0.2 * radius, 0.2 * radius, 0.01 * radius );
lightDirectional.target = axesHelper;
// scene.remove( cameraHelper );
// cameraHelper = new THREE.CameraHelper( lightDirectional.shadow.camera );
// scene.add( cameraHelper );
}
function setAllVisible() {
surfaceMeshes.visible = true;
document.body.style.backgroundImage = '';
for ( let child of surfaceMeshes.children ) {
child.material = new THREE.MeshPhongMaterial( {
color: colors[ child.userData.data.surfaceType ], side: 2, opacity: 0.85, transparent: true }
);
child.material.wireframe = false;
child.visible = true;
};
surfaceEdges.visible = true;
for ( let child of surfaceEdges.children ) {
// child.material.opacity = 0.85;
child.material.wireframe = false;
};
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.1, 10000 );
camera.up.set( 0, 0, 1 );
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.autoRotate = true;
camera.add( lightPoint );
scene.add( camera );
if ( parent.createReport ) { parent.createReport(); }
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
//console.log( 'onWindowResize window.innerWidth', window.innerWidth );
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
controls.update();
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment