Skip to content

Instantly share code, notes, and snippets.

@xeolabs
Created June 20, 2024 07:43
Show Gist options
  • Save xeolabs/81d002007a7e3a2690dda920ed3dcb87 to your computer and use it in GitHub Desktop.
Save xeolabs/81d002007a7e3a2690dda920ed3dcb87 to your computer and use it in GitHub Desktop.
<!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