Does anyone know how to create interactive walls in Three.js that automatically adjust when a connected wall is manipulated? I have a room modeled as a GLB file, comprising four walls and a floor. For example, if I stretch one wall, the adjacent wall, the opposite wall, and the floor should adjust accordingly to maintain the room’s shape. Is it feasible to implement this functionality in Three.js?
Hard coded the side but I am trying to do this automatically and the adjacent side do not move correctly when the wall is stretched
import "./style.css";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { Pane } from "tweakpane";
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
var scene, camera, renderer, controls, panel, cubeTexture, raycaster, mouse;
var model, selectedObject;
setupThreeScene();
loadModel();
function loadModel() {
var loader = new GLTFLoader();
loader.load('simpleBox.glb', function (gltf) {
model = gltf.scene;
console.log("Model Loaded:", model);
// Iterate through the children of the model and set materials
model.traverse(function (child) {
if (child.isMesh) {
child.material = new THREE.MeshStandardMaterial({
color: "white",
map: getTexture(),
envMap: cubeTexture,
roughness: 0.2,
metalness: 0.7,
side: THREE.DoubleSide // Ensure both sides of the material are rendered
});
child.name = child.name || 'Unnamed Mesh'; // Ensure each mesh has a name
}
});
// Add the model to the scene
scene.add(model);
// Adjust model position and scale if necessary
model.position.set(0, 0, 0);
model.scale.set(1, 1, 1);
console.log("Model Position:", model.position);
console.log("Model Scale:", model.scale);
}, undefined, function (error) {
console.error('An error happened:', error);
});
}
function setupThreeScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color("gainsboro");
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
camera.position.y = 2;
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(update);
document.body.appendChild(renderer.domElement);
// Add a grid helper
var gridHelper = new THREE.GridHelper(10, 20);
gridHelper.position.y = -1;
scene.add(gridHelper);
// Controls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
var light = new THREE.DirectionalLight(0xFFFFFF);
light.position.set(0, 2.0, 1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.9));
// Reflection map
var loader = new THREE.CubeTextureLoader();
loader.setCrossOrigin("");
loader.setPath('https://threejs.org/examples/textures/cube/pisa/');
cubeTexture = loader.load([
'px.png', 'nx.png',
'py.png', 'ny.png',
'pz.png', 'nz.png'
]);
window.addEventListener("resize", (event) => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
window.addEventListener('click', onClick, false);
panel = new Pane();
}
function onClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
selectedObject = intersects[0].object;
console.log('Clicked on:', selectedObject.name);
// Get connected sides
const connectedSides = getConnectedSides(selectedObject.name);
console.log('Connected sides:', connectedSides);
// Clear previous panel contents
panel.children.forEach(child => panel.remove(child));
// Add controls for the selected object
panel.addBinding(selectedObject.scale, 'x', { min: 0, max: 10, step: 0.1 }).on('change', (value) => {
extrudeGeometry(selectedObject, 'x', value.value, connectedSides);
});
panel.addBinding(selectedObject.scale, 'y', { min: 0, max: 10, step: 0.1 }).on('change', (value) => {
extrudeGeometry(selectedObject, 'y', value.value, connectedSides);
});
panel.addBinding(selectedObject.scale, 'z', { min: 0, max: 10, step: 0.1 }).on('change', (value) => {
extrudeGeometry(selectedObject, 'z', value.value, connectedSides);
});
panel.addBinding(selectedObject.material, 'wireframe');
}
}
function extrudeGeometry(object, axis, value, connectedSides) {
const previousValue = object.scale[axis];
const delta = value - previousValue;
console.log(object)
// Update the selected object
updateObjectGeometry(object, axis, value);
// Calculate the new length and divide it by 2
const newLength = object.scale[axis];
const halfLength = newLength / 2;
// Update the parallel side
const parallelSideName = connectedSides.parallel;
const parallelSide = model.getObjectByName(parallelSideName);
if (parallelSide) {
updateObjectGeometry(parallelSide, axis, value);
}
const floorSide = model.getObjectByName('Cube005');
if (floorSide && object.name !== 'Cube005' && object.type!=='GridHelper') {
updateObjectGeometry(floorSide, axis, value);
}
// Update the other sides that need to move
connectedSides.moving.forEach((sideName, index) => {
const side = model.getObjectByName(sideName);
if (side) {
side.position[axis] = (index % 2 === 0) ? halfLength : -halfLength;
}
});
}
function updateObjectGeometry(object, axis, value) {
object.scale[axis] = value;
object.geometry.attributes.position.needsUpdate = true;
object.geometry.computeBoundingBox();
object.geometry.computeBoundingSphere();
}
function getConnectedSides(name) {
// Define the connections for each side of the box
const connections = {
Cube001: { parallel: 'Cube003', moving: ['Cube004', 'Cube002'] },
Cube002: { parallel: 'Cube004', moving: ['Cube001', 'Cube003'] },
Cube003: { parallel: 'Cube001', moving: ['Cube002', 'Cube004'] },
Cube004: { parallel: 'Cube002', moving: ['Cube001', 'Cube003'] },
Cube005: { parallel: '', moving: ['Cube001', 'Cube002', 'Cube003', 'Cube004'] },
};
return connections[name] || { parallel: '', moving: [] };
}
function update() {
controls.update();
renderer.render(scene, camera);
}
function getTexture() {
var texSize = 32;
var canvas = document.createElement("CANVAS");
canvas.width = texSize;
canvas.height = texSize;
// Procedurally defining of a black square
var context = canvas.getContext("2d");
context.fillStyle = "linen";
context.fillRect(0, 0, texSize, texSize);
context.strokeStyle = "black";
context.lineWidth = 1;
context.strokeRect(3, 3, 27, 27);
var texture = new THREE.CanvasTexture(canvas);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.x = texture.repeat.y = 5;
return texture;
}