Created
June 20, 2024 07:43
-
-
Save xeolabs/81d002007a7e3a2690dda920ed3dcb87 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>xeokit Example</title> | |
<link href="../css/pageStyle.css" rel="stylesheet"/> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script> | |
<style> | |
#storeys { | |
overflow: hidden; | |
background-color: white; | |
position: absolute; | |
left: 10px; | |
bottom: 10px; | |
width: 400px; | |
height: 409px; | |
/* margin-top: 20px; */ | |
opacity: 0.8 !important; | |
border-radius: 15px; | |
/* margin-bottom: 10px; */ | |
pointer-events: all; | |
opacity: 1.0; | |
} | |
.marker { | |
position: absolute; | |
width: 15px; | |
height: 15px; | |
background-color: red; | |
border-radius: 50%; | |
top: 0px; | |
left: 0px; | |
/*transform: translate(-50%, -50%);*/ | |
z-index: 1000; | |
} | |
</style> | |
</head> | |
<body> | |
<input type="checkbox" id="info-button"/> | |
<label for="info-button" class="info-button"><i class="far fa-3x fa-question-circle"></i></label> | |
<canvas id="myCanvas"></canvas> | |
<div id="storeys"> | |
<div id="marker" class="marker"></div> | |
</div> | |
<div class="slideout-sidebar"> | |
<img class="info-icon" src="../../assets/images/storey_views_icon.png"/> | |
<h1>StoreyViewsPlugin</h1> | |
<h2>Minimap</h2> | |
<p>Click a room in the plan images to go there in <b>first-person mode</b>. </p> | |
<h3>Components Used</h3> | |
<ul> | |
<li> | |
<a href="../../docs/class/src/viewer/Viewer.js~Viewer.html" target="_other">Viewer</a> | |
</li> | |
<li> | |
<a href="../../docs/class/src/plugins/StoreyViewsPlugin/StoreyViewsPlugin.js~StoreyViewsPlugin.html" | |
target="_other">StoreyViewsPlugin</a> | |
</li> | |
<li> | |
<a href="../../docs/class/src/plugins/XKTLoaderPlugin/XKTLoaderPlugin.js~XKTLoaderPlugin.html" | |
target="_other">XKTLoaderPlugin</a> | |
</li> | |
<li> | |
<a href="../../docs/class/src/viewer/scene/camera/CameraFlightAnimation.js~CameraFlightAnimation.html" | |
target="_other">CameraFlightAnimation</a> | |
</li> | |
</ul> | |
<h3>Resources</h3> | |
<ul> | |
<li> | |
<a href="https://github.com/openBIMstandards/DataSetSchependomlaan" target="_other">Model source</a> | |
</li> | |
</ul> | |
</div> | |
</body> | |
<script type="module"> | |
//------------------------------------------------------------------------------------------------------------------ | |
// Import the modules we need for this example | |
//------------------------------------------------------------------------------------------------------------------ | |
import { | |
Viewer, /*StoreyViewsPlugin, math,*/ | |
XKTLoaderPlugin, | |
CameraMemento, | |
Skybox | |
} from "../../dist/xeokit-sdk.min.es.js"; | |
import {StoreyViewsPlugin} from '../../src/plugins/StoreyViewsPlugin/StoreyViewsPlugin.js'; | |
import {math} from '../../src/viewer/scene/math/math.js'; | |
//------------------------------------------------------------------------------------------------------------------ | |
// Helping Variables | |
//------------------------------------------------------------------------------------------------------------------ | |
let animationInProgress = false; | |
//------------------------------------------------------------------------------------------------------------------ | |
// Create a Viewer, arrange the camera | |
//------------------------------------------------------------------------------------------------------------------ | |
const viewer = new Viewer({ | |
canvasId: "myCanvas", | |
transparent: true, | |
edges: true | |
}); | |
viewer.camera.eye = [-2.56, 8.38, 8.27]; | |
viewer.camera.look = [13.44, 3.31, -14.83]; | |
viewer.camera.up = [0.10, 0.98, -0.14]; | |
viewer.camera.project.fovy = 70; | |
new Skybox(viewer.scene, { | |
src: "../../assets/textures/skybox/cloudySkyBox.jpg", | |
size: 1000 | |
}); | |
//------------------------------------------------------------------------------------------------------------------ | |
// Load a model | |
//------------------------------------------------------------------------------------------------------------------ | |
const xktLoader = new XKTLoaderPlugin(viewer); | |
const sceneModel = xktLoader.load({ | |
id: "myModel", | |
src: "../../assets/models/xkt/v8/ifc/Schependomlaan.ifc.xkt", | |
edges: true, | |
objectDefaults: { // This model has opaque windows / spaces; make them transparent | |
"IfcPlate": { | |
opacity: 0.3 // These are used as windows in this model - make transparent | |
}, | |
"IfcWindow": { | |
opacity: 0.4 | |
}, | |
"IfcSpace": { | |
opacity: 0.4 | |
} | |
} | |
}); | |
//------------------------------------------------------------------------------------------------------------------ | |
// Add a StoreyViewsPlugin | |
//------------------------------------------------------------------------------------------------------------------ | |
const storeyViewsPlugin = new StoreyViewsPlugin(viewer); | |
const allStoreyMaps = []; | |
//------------------------------------------------------------------------------------------------------------------ | |
// When model loaded, build a clickable set of storey plan images from the StoreyViewsPlugin, bind mouse | |
// events to fly the Camera to a first-person view at whatever location we click within each plan view image | |
//------------------------------------------------------------------------------------------------------------------ | |
sceneModel.on("loaded", function () { | |
viewer.cameraFlight.jumpTo(sceneModel); | |
// Make all doors transparent | |
viewer.scene.setObjectsOpacity(viewer.metaScene.getObjectIDsByType("IfcDoor"), 0.3); | |
buildStoreyMapsMenu(); | |
}); | |
function buildStoreyMapsMenu() { | |
const cameraMemento = new CameraMemento(); // Saves 3D perspective camera to restore | |
cameraMemento.saveCamera(viewer.scene); | |
const storeyDiv = document.getElementById("storeys"); | |
const storeyIds = Object.keys(storeyViewsPlugin.storeys); | |
for (var i = 0, len = storeyIds.length; i < len; i++) { | |
const storeyId = storeyIds[i]; | |
const storeyMap = storeyViewsPlugin.createStoreyMap(storeyId, { | |
format: "png", | |
width: 400 | |
}); | |
console.log('storeyMap: ', storeyMap); | |
allStoreyMaps.push(storeyMap); | |
const img = document.createElement("img"); | |
img.className = 'storeyMap'; | |
img.src = storeyMap.imageData; | |
img.id = `${storeyId}`; | |
// img.style.border = "1px solid #000000"; | |
img.style.borderRadius = "15px"; | |
// img.style.background = "lightblue"; | |
// img.style.width = storeyMap.width + "px"; | |
// img.style.height = storeyMap.height + "px"; | |
img.style.width = "100%"; | |
img.style.height = "100%"; | |
img.style.opacity = 0.8; | |
img.style.display = 'none'; | |
storeyDiv.appendChild(img); | |
img.onmouseenter = () => { | |
img.style.cursor = "default"; | |
}; | |
img.onmousemove = (e) => { | |
img.style.cursor = "default"; | |
const imagePos = [e.offsetX, e.offsetY]; | |
const pickResult = storeyViewsPlugin.pickStoreyMap(storeyMap, imagePos, {}); | |
if (pickResult) { | |
const entity = pickResult.entity; | |
const metaObject = viewer.metaScene.metaObjects[entity.id]; | |
if (metaObject) { | |
img.style.cursor = "pointer"; | |
} | |
} | |
}; | |
img.onmouseleave = (e) => { | |
img.style.cursor = "default"; | |
}; | |
const worldPos = math.vec3(); | |
img.onclick = (e) => { | |
const imagePos = [e.offsetX, e.offsetY]; | |
const pickResult = storeyViewsPlugin.pickStoreyMap(storeyMap, imagePos, { | |
pickSurface: true | |
}); | |
if (pickResult) { | |
worldPos.set(pickResult.worldPos); | |
// Set camera vertical position at the mid point of the storey's vertical | |
// extents - note how this is adapts to whichever of the X, Y or Z axis is | |
// designated the World's "up" axis | |
const camera = viewer.scene.camera; | |
const idx = camera.xUp ? 0 : (camera.yUp ? 1 : 2); // Find the right axis for "up" | |
const storey = storeyViewsPlugin.storeys[storeyMap.storeyId]; | |
worldPos[idx] = (storey.aabb[idx] + storey.aabb[3 + idx]) / 2; | |
animationInProgress = true; | |
viewer.cameraFlight.flyTo({ | |
eye: worldPos, | |
up: viewer.camera.worldUp, | |
look: math.addVec3(worldPos, viewer.camera.worldForward, []), | |
projection: "perspective", | |
duration: 1.5 | |
}, () => { | |
animationInProgress = false; | |
getStorey(viewer.camera.viewMatrix) | |
viewer.cameraControl.navMode = "firstPerson"; | |
}); | |
} | |
}; | |
} | |
viewer.camera.on('viewMatrix', function (matrix) { | |
if (!animationInProgress) getStorey(matrix); | |
}) | |
// console.log('viewer.camera.position:', viewer.camera); | |
// const cameraPos = math.vec3(); | |
// math.decomposeMat4(math.inverseMat4(viewer.camera.viewMatrix, math.mat4()), cameraPos, math.vec4(), math.vec3()); | |
// console.log('cameraPos: ', cameraPos) | |
// const storey = storeyViewsPlugin.getStoreyContainingWorldPos(cameraPos); | |
// console.log('storey: ', storey); | |
// document.getElementById(`${storey}`).style.display = "block"; | |
} | |
const marker = document.getElementById("marker"); | |
function getStorey(matrix) { | |
console.log('getStoreyCalled'); | |
const invert = math.mat4(); | |
math.inverseMat4(matrix, invert); | |
// const cameraPos = [invert[12], invert[13], invert[14]]; | |
const cameraPos = viewer.camera.eye; | |
console.log(cameraPos); | |
let storeyId = getStoreyId(cameraPos); | |
hideStoreyMaps(); | |
const el = document.getElementById(`${storeyId}`) | |
el.style.display = "block"; | |
const cameraDir = viewer.camera.worldForward; | |
const storeyMap = allStoreyMaps.filter(storey => storey.storeyId === storeyId)[0]; | |
const imageDir = math.vec2(), imagePos = math.vec2(); | |
storeyViewsPlugin.worldDirToStoreyMap(storeyMap, cameraDir, imageDir); | |
storeyViewsPlugin.worldPosToStoreyMap(storeyMap, cameraPos, imagePos); | |
// storeyViewsPlugin.worldPosToStoreyMap(storeyMap, cameraPos, imagePos); | |
console.log('imagePos: ', imagePos); | |
const centerX = 400 / 2; | |
const centerY = -400 / 2; | |
// const translateX = centerX - (imagePos[0]); | |
const translateY = centerY - (imagePos[1]); | |
const translateX = (imagePos[0]) - centerX; | |
// const translateY = (imagePos[1]) - centerY; | |
console.log('translateX: ', translateX) | |
console.log('translateY: ', translateY) | |
// console.log('imagePos: ', imagePos); | |
const angle = calculateAngleFromDirection(imageDir); | |
marker.style.left = `${Math.round(imagePos[0])}px`; | |
marker.style.top = `${Math.round(imagePos[1])}px`; | |
// el.style.transform = `translate(0px, 0px) rotate(0deg)`; | |
// el.style.transform = `rotate(${angle}deg)`; | |
} | |
function hideStoreyMaps() { | |
const elements = document.querySelectorAll('.storeyMap'); | |
elements.forEach(element => { | |
element.style.display = 'none'; | |
}); | |
} | |
function getStoreyId(cameraPos) { | |
let storey = null; | |
storey = storeyViewsPlugin.getStoreyContainingWorldPos(cameraPos); | |
if (storey === null) { | |
storey = storeyViewsPlugin.getStoreyInVerticalRange(cameraPos); | |
if (storey === null) { | |
storey = storeyViewsPlugin.isPositionAboveOrBelowBuilding(cameraPos); | |
} | |
} | |
return storey; | |
} | |
function calculateAngleFromDirection(direction) { | |
const angleInRad = Math.atan2(direction[0], direction[1]); | |
const angleInDeg = angleInRad * (180 / Math.PI); | |
return angleInDeg; | |
} | |
function radToDeg(rad) { | |
return rad * (180 / Math.PI); | |
} | |
function calculateTranslation(cameraPos) { | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment