Building Interactive 3D Archives Rooms with WebGL and Three.js

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);
}

Tags: WebGL Three.js 3D Visualization Interactive Archives Scene Animation

Posted on Thu, 07 May 2026 10:15:56 +0000 by LDusan