Building a 3D Smart Archive Room with Three.js: Path Planning and Automated Inspection
3D Scene Construction
Facility Visualization
The virtual environment includes the external campus, distinct floors, and the interior archive storage rooms. Floor are displayed using an exploded view technique to allow users to select specific levels. Double-clicking a floor navigates the camera into the specific storage area.
Equipment Management
Users can populate the scene with various monitoring devices. The system supports dragging, rotating, and deleting models within the 3D space. Device types are pre-loaded, allowing customization of the environment and binding specific data payloads to each object for later diagnostics.
function persistConfiguration() {
const payload = JSON.stringify(collectDeviceData());
const sysLabel = $("#sysLabel").val();
const roomLabel = $("#roomLabel").val();
api.saveLayout(payload, sysLabel, roomLabel, (res) => {
if (res && res.status === "success") {
$("#displayRoom").html(roomLabel);
$("#displaySystem").html(sysLabel);
}
}, (err) => console.error(err));
scene3D.viewMode = 1;
editorState = "display";
controls.enabled = false;
controls.visible = false;
closePopups();
}
$("#btnManage").on("click", () => {
closePopups();
showManagerPanel();
});
$("#btnTranslate").on("click", () => {
if (controls.object && controls.object.uuid.includes("equip_")) {
controls.setMode("translate");
} else {
showTip("Select a device first.");
}
});
$("#btnRotate").on("click", () => {
if (controls.object && controls.object.uuid.includes("equip_")) {
controls.setMode("rotate");
} else {
showTip("Select a device first.");
}
});
$("#btnRemove").on("click", () => {
if (controls.object && controls.object.uuid.includes("equip_")) {
scene3D.removeObject(controls.object.uuid);
} else {
showTip("Select a device first.");
}
});
Inspection Path Editing
Drawing Routes
The core challenge involves drawing lines that follow mouse movement and adjusting existing routes. The system allows modifying paths and binding specific inspection nodes to the line segments.
function handlePathSegmentInteraction(target, intersects) {
if (!intersects || intersects.length === 0) return;
const hitName = intersects[0].object.uuid;
if (!hitName.startsWith("Route_")) return;
const segmentId = parseInt(hitName.split("SEGMENT")[0].replace("Route_", ""));
pathManager.activeSegment = segmentId;
if (hitName.includes("SEGMENT1")) {
if (segmentId === 0) {
const pStart = pathManager.nodes[1].coords;
const pEnd = pathManager.nodes[0].coords;
pathManager.drawTempLine("preview_A", pStart, pEnd, true);
} else {
scene3D.removeObject(`Route_${segmentId - 1}`);
scene3D.removeObject(`Route_${segmentId}`);
const prevStart = pathManager.nodes[segmentId - 1].coords;
const currEnd = pathManager.nodes[segmentId].coords;
pathManager.drawTempLine("preview_A", prevStart, currEnd, true);
const nextStart = pathManager.nodes[segmentId + 1].coords;
pathManager.drawTempLine("preview_B", nextStart, currEnd, true);
}
} else if (hitName.includes("SEGMENT2")) {
scene3D.removeObject(`Route_${segmentId}`);
pathManager.activeSegment = segmentId + 1;
const penultimate = pathManager.nodes[pathManager.nodes.length - 2].coords;
const last = pathManager.nodes[pathManager.nodes.length - 1].coords;
pathManager.drawTempLine("preview_A", penultimate, last, true);
}
}
Executing the Inspection
Runtime Simulation
The system supports both first-person and third-person perspectives. A daily schedule can be configured to trigger automatic inspection along the predefined path, stopping at nodes too check device telemetry.
SceneController.prototype.walkSpeed = 40;
SceneController.prototype.cameraView = 1; // 1: First, 3: Third
SceneController.prototype.patrolQueue = [];
SceneController.prototype.startThirdPersonView = function() {
if (this.active) {
showTip("Inspection already running.");
return;
}
$("#logPanel").html("").show();
this.spawnAvatar();
this.active = 1;
this.cameraView = 3;
this.patrolQueue = [];
this.processRoute(0, this.routeNodes);
};
SceneController.prototype.startFirstPersonView = function() {
if (this.active) {
showTip("Inspection already running.");
return;
}
$("#logPanel").html("").show();
this.spawnAvatar();
this.cameraView = 1;
this.patrolQueue = [];
this.active = 1;
this.processRoute(0, this.routeNodes, true);
};
SceneController.prototype.haltPatrol = function() {
$("#logPanel").hide().html("");
this.active = 0;
if (this.timerRef) clearTimeout(this.timerRef);
if (this.avatar && this.avatar.tween) {
this.avatar.tween.stop();
this.avatar.position.y = -9999; // Move out of view
this.avatar.visible = false;
const anims = this.avatar.asset.animations;
this.avatar.mixer.clipAction(anims[1]).stop();
this.avatar.mixer.clipAction(anims[0]).play();
}
resetCameraPosition(this.defaultView.cam, this.defaultView.lookAt, 100, () => {});
};