Seamless Scene Transitions
Use animated transitions between views to enhance navigation within the 3D space. A helper module manages CSS-based scene swaps with varied effects.
const SceneAnimator = (() => {
const container = document.querySelector('#scene-container');
const panels = Array.from(container.querySelectorAll('.scene-panel'));
let activeIdx = 0;
let transitioning = false;
const effectCycle = [
['rotateCubeLeftOut', 'rotateCubeLeftIn'],
['rotateCubeRightOut', 'rotateCubeRightIn'],
['rotateCubeTopOut', 'rotateCubeTopIn'],
['rotateSlideOut', 'rotateSlideIn']
];
let effectIdx = 0;
const saveOriginal = panel => panel.dataset.originalClass = panel.className;
panels.forEach(saveOriginal);
panels[activeIdx].classList.add('scene-active');
const trigger = btn => {
if (transitioning) return;
const target = parseInt(btn.dataset.target, 10);
if (target === activeIdx) return;
transitioning = true;
const prevPanel = panels[activeIdx];
const nextPanel = panels[target];
activeIdx = target;
const [exitCls, enterCls] = effectCycle[effectIdx % effectCycle.length];
effectIdx++;
prevPanel.classList.add(exitCls);
nextPanel.classList.add(enterCls, 'scene-active');
const onEnd = () => {
prevPanel.className = prevPanel.dataset.originalClass;
nextPanel.className = `${nextPanel.dataset.originalClass} scene-active`;
transitioning = false;
};
nextPanel.addEventListener('animationend', onEnd, { once: true });
};
document.querySelectorAll('.nav-btn').forEach(btn => btn.addEventListener('click', () => trigger(btn)));
return { init: () => {} };
})();
Animated Compact Shelf Opening
Render an auxiliary geometry to preview motion when opening a shelf unit. Fade the shelf faces, reveal a proxy block, then animate it into place.
function revealCompactShelf(shelfEntity) {
shelfEntity.material.materials.forEach(mat => mat.opacity = 0.1);
const proxyGeo = {
id: shelfEntity.name + '_proxy',
shape: 'box',
size: { x: 78, y: 1, z: 319 },
position: {
x: shelfEntity.position.x,
y: shelfEntity.position.y - 99,
z: shelfEntity.position.z
},
style: { color: 0x6E6E6E, opacity: 0.9 }
};
const proxy = core3D.createFromSpec(proxyGeo);
core3D.addToScene(proxy);
new TWEEN.Tween(proxy.position).to({ y: shelfEntity.position.y }, 2000).start();
new TWEEN.Tween(proxy.scale).to({ y: 199 }, 2000)
.onComplete(() => {
shelfEntity.material.materials.forEach(mat => mat.opacity = 1);
core3D.remove(proxy);
})
.start();
buildShelfLayout(shelfEntity);
}
File Placement and Retrieval on Shelves
Generate a detailed internal grid representing storage slots. Populate with file representations based on occupancy data.
function buildShelfLayout(shelfEntity) {
const [_, grpIdx, colIdx] = shelfEntity.name.split('_').map(Number);
let layout = shelfStore[grpIdx - 1]?.[colIdx - 1];
if (!layout) {
const rows = 12, cols = 4;
const cellH = 200 / rows, cellW = 320 / cols;
layout = core3D.createGroup({ name: `shelf_detail_${grpIdx}_${colIdx}` });
for (let r = 0; r < rows - 1; r++) {
const slab = core3D.makeBox({
size: { x: 79, y: 1, z: 319 },
position: { x: 0, y: 100 - (r + 1) * cellH, z: 0 },
texture: '../../img/3dImg/outside_lightmap.jpg'
});
layout.add(slab);
}
for (let c = 0; c < cols - 1; c++) {
[-35, 35].forEach(side => {
const bar = core3D.makeBox({
size: { x: 10, y: 199, z: 2 },
position: { x: side, y: 0, z: 160 - (c + 1) * cellW },
texture: '../../img/3dImg/card_panel.png'
});
layout.add(bar);
});
}
if (!shelfStore[grpIdx - 1]) shelfStore[grpIdx - 1] = [];
shelfStore[grpIdx - 1][colIdx - 1] = layout;
core3D.addToScene(layout);
}
const mockData = [];
for (let r = 1; r <= rows; r++) {
for (let c = 1; c <= cols; c++) {
mockData.push({ row: r, col: c, count: Math.floor(Math.random() * 5) });
}
}
layout.clearChildren();
mockData.forEach(({ row, col, count }) => {
if (count > 0) {
for (let k = 0; k < Math.ceil(Math.random() * ((cellW - 2) / 22)); k++) {
const fileBox = core3D.makeBox({
size: { x: 70, y: cellH - 5, z: 20 },
position: {
x: shelfEntity.position.x,
y: (row - 1) * cellH + (cellH - 5) / 2 + 5,
z: shelfEntity.position.z - 160 + (col - 1) * cellW + 22 * (k + 1)
},
color: 0x9E9E9E
});
layout.add(fileBox);
}
}
});
layout.visible = false;
setTimeout(() => layout.visible = true, 2000);
}
Rapid File Search and Camera Focus
Locate a file entry and move the camera to focus on its shelf location, highlighting the target.
function locateFile() {
const result = {
group: Math.floor(Math.random() * 6) + 1,
number: Math.floor(Math.random() * 7) + 1,
face: Math.random() > 0.5 ? 'A' : 'B',
row: Math.floor(Math.random() * 10) + 1,
col: Math.floor(Math.random() * 8) + 1,
index: Math.floor(Math.random() * 20) + 1
};
const target = core3D.find(`mjj_${result.group}_${result.number}`);
highlight(target);
const camPos = core3D.camera.position;
const tgtPos = target.position;
const dist = Math.hypot(camPos.x - tgtPos.x, camPos.y - tgtPos.y, camPos.z - tgtPos.z);
const mid = dist < 150 ** 3 ? camPos : {
x: (camPos.x + tgtPos.x) / 2,
y: 600,
z: (camPos.z + tgtPos.z) / 2
};
moveCamera(mid, tgtPos, 500);
}
Real-Time Camera View Integration
Visualize camera coverage volumes and launch live feeds in overlay layers.
const camVolumes = [];
function showCameraRanges() {
if (camVolumes.length === 0) {
const defs = [
{ pos: { x: -129, y: 171, z: -314 }, rot: { x: 1.84, y: -0.019, z: -0.101 } },
{ pos: { x: 45, y: 247, z: 534 }, rot: { x: 1.54, y: -0.086, z: -3.04 } }
];
defs.forEach(({ pos, rot }) => {
const cyl = core3D.makeCylinder({
radiusTop: 500, radiusBottom: 5, height: 500,
position: pos, rotation: rot,
material: { color: 0xFFF200, opacity: 0.3, map: '../../img/3dImg/camarerange.png' }
});
camVolumes.push(cyl);
core3D.addToScene(cyl);
});
}
camVolumes.forEach(v => { v.visible = true; pulse(v, 0xB2FB07); });
moveCamera({ x: -1284, y: 1787, z: -378 }, { x: -237, y: 308, z: 145 }, 1000);
}
Fire Extinguisher Location Highlighting
Temporari reveal extinguisher positions with markers and animated camera fly-throughs.
function spotlightExtinguishers() {
hideAllShelves();
hideCameraRanges();
removeMarkers();
[[940,200,-660],[660,200,-70],[660,200,400],[-960,200,-70],[-960,200,400]]
.forEach(pos => addMarker({ x: pos[0], y: pos[1], z: pos[2] }));
animateMarkersBounce();
runCameraTour([
{ eye: { x: -270, y: 1274, z: -714 }, target: { x: 209, y: 705, z: -750 } },
{ eye: { x: -574, y: 1430, z: -161 }, target: { x: 486, y: 134, z: -90 } },
{ eye: { x: 196, y: 1262, z: 340 }, target: { x: -639, y: 277, z: 216 } }
]);
}
Environmental Controls with Animation
Animate thermometers and hygrometers into view for adjusting temperature and humidity parameters.
function adjustTemperature() {
hideAllShelves();
moveCamera({ x: 540, y: 388, z: -524 }, { x: 0, y: 260, z: 130 }, 1000);
if (thermoGauges.length === 0) {
[[300,-100],[300,500],[-500,-100],[-500,500]].forEach(([px,pz]) => {
const gauge = core3D.makeThermometer({ position: { x: px, y: 400, z: pz }, value: 28.5 });
thermoGauges.push(gauge);
core3D.addToScene(gauge);
});
}
thermoGauges.forEach(g => { g.visible = true; g.position.y = 400; });
new TWEEN.Tween(thermoGauges[0].position).to({ y: 200 }, 2000)
.onUpdate(() => thermoGauges.slice(1).forEach(g => g.position.y = thermoGauges[0].position.y))
.easing(TWEEN.Easing.Elastic.Out).start();
}
Similar patterns apply for humidity adjustment using humidity gauges.
Large Screen Management
Switch displayed media on wall-mounted screens and navigate cameras to predefined viewpoints.
function configureScreens() {
const walls = core3D.findAll(obj => obj.name.includes('wall_bigScreen_'));
walls.forEach(wall => {
// assign stream or image based on UI selection
});
}
HVAC and Ventilation Animations
Trigger animated flow tubes and enclosure visibility for ventilation and air conditioning units.
function toggleVentilation(open) {
ventUnits.forEach(unit => {
if (unit.flowTube) unit.flowTube.visible = open;
unit.mainBody.visible = open;
});
}
Alarm Monitoring with Visual Cues
Flash objects red on alarm conditions and provide real-time status overlays.
const activeAlarms = new Map();
function raiseAlert(objRef, color, id) {
if (activeAlarms.has(id)) return;
const blink = setInterval(() => {
objRef.material.color.setHex(objRef.currentColor === color ? 0x000000 : color);
objRef.currentColor = objRef.material.color.getHex();
}, 400);
activeAlarms.set(id, blink);
}