|
<!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> |