let buildings = []; // --- Building Data--- const buildingTypes = { house: { geometry: new THREE.BoxGeometry(4, 6, 4), material: new THREE.MeshLambertMaterial({ color: 0x87CEEB}), description: "Residential House", consumption: "Low Energy, Water", generation: "None", iot: "Smart Thermostat, Smart Lights" }, factory: { geometry: (() => { const shape = new THREE.Shape().moveTo(0, 0).lineTo(0, 6).lineTo(8, 6).lineTo(8, 8).lineTo(10, 8).lineTo(10, 0).lineTo(0, 0); return new THREE.ExtrudeGeometry(shape, { depth: 12, bevelEnabled: false }); })(), material: new THREE.MeshLambertMaterial({ color: 0xB22222}), description: "Manufacturing Plant", consumption: "High Energy, Industrial Resources", generation: "Goods, Waste Heat", iot: "Process Sensors, Automated Controls" }, office: { geometry: new THREE.BoxGeometry(6, 10, 6), material: new THREE.MeshLambertMaterial({ color: 0xFFFFFF }), description: "Office Building", consumption: "Medium Energy, Data", generation: "Services, Data", iot: "BMS, Occupancy Sensors" }, shop: { geometry: new THREE.BoxGeometry(8, 4, 6), material: new THREE.MeshLambertMaterial({ color: 0xd8d400 }), description: "Retail Shop", consumption: "Medium Energy, Goods", generation: "Retail Sales", iot: "POS System, Smart Shelves" } }; function initBuildings() { createBuilding('house', new THREE.Vector3(-9, 0, 24), 'H01', 'data/H01.csv', 'assets/models/building1.glb'); createBuilding('factory', new THREE.Vector3(48, 0, -49), 'H02', 'data/H02.csv', 'assets/models/factory.glb'); // ExtrudeGeometry origin is at bottom createBuilding('office', new THREE.Vector3(-38, 0, -12), 'H03', 'data/H03.csv', 'assets/models/predio1.glb'); createBuilding('shop', new THREE.Vector3(-5, 0, -5), 'H04', 'data/H04.csv', 'assets/models/store1.glb', Math.PI / 2); createBuilding('house', new THREE.Vector3(-5, 0, 40), 'H05', 'data/H05.csv', 'assets/models/building2.glb'); createBuilding('house', new THREE.Vector3(6, 0, 24), 'H06', 'data/H06.csv', 'assets/models/building1.glb'); createBuilding('house', new THREE.Vector3(-37, 0, 30), 'H07', 'data/H07.csv', 'assets/models/house1.glb'); createBuilding('house', new THREE.Vector3(-34, 0, 9), 'H08', 'data/H08.csv', 'assets/models/store4.glb', -Math.PI / 2); createBuilding('house', new THREE.Vector3(19, 0, -20), 'H09', 'data/H09.csv', 'assets/models/store2.glb', Math.PI / 2); createBuilding('house', new THREE.Vector3(36, 0, 6), 'H10', 'data/H10.csv', 'assets/models/store5.glb'); createBuilding('house', new THREE.Vector3(34, 0, -19), 'H11', 'data/H11.csv', 'assets/models/store3.glb', Math.PI); createBuilding('house', new THREE.Vector3(-21, 0, -18), 'H12', 'data/H12.csv', 'assets/models/predio2.glb'); createBuilding('house', new THREE.Vector3(-5, 0, -20), 'H13', 'data/H13.csv', 'assets/models/predio3.glb'); createBuilding('house', new THREE.Vector3(-7, 0, -48), 'H14', 'data/H14.csv', 'assets/models/predio4.glb'); renderer.domElement.addEventListener('pointerdown', function(event) { // Calculate mouse position in normalized device coordinates const rect = renderer.domElement.getBoundingClientRect(); const mouse = new THREE.Vector2( ((event.clientX - rect.left) / rect.width) * 2 - 1, -((event.clientY - rect.top) / rect.height) * 2 + 1 ); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(interactableObjects); if (intersects.length > 0) { const mesh = intersects[0].object; if (mesh.material && mesh.material.name && mesh.material.name.toLowerCase().includes("vetro")) { enableWindowLight(mesh); } } }); } function enableWindowLight(windowMesh) { // If light is already on, turn it off if (windowMesh.userData.lightOn) { if (windowMesh.userData.lightOn.parent) { windowMesh.userData.lightOn.parent.remove(windowMesh.userData.lightOn); } windowMesh.userData.lightOn = null; // Optionally, reset window glow if (windowMesh.material) { windowMesh.material.emissive = new THREE.Color(0x000000); windowMesh.material.emissiveIntensity = 0; } return; } // Create a warm point light just inside the window const light = new THREE.PointLight(0xdbb108, 2, 20); // Place the light slightly inside the window (adjust as needed) const bbox = new THREE.Box3().setFromObject(windowMesh); const center = bbox.getCenter(new THREE.Vector3()); light.position.copy(center); // Optionally, offset the light a bit inward light.position.y += 1; // Attach the light to the window's parent group (the building) if (windowMesh.parent) { scene.add(light); } windowMesh.userData.lightOn = light; if (windowMesh.material) { windowMesh.material.color.set(0x66aaff) windowMesh.material.emissive = new THREE.Color(0xebb104); windowMesh.material.emissiveIntensity = 1; } } // --- Building Creation Function --- function createBuilding(type, position, id, csvPath, modelPath, rotation) { const buildingData = buildingTypes[type]; if (!buildingData) { console.warn(`Building type "${type}" not found.`); return null; } if (modelPath) { const loader = new THREE.GLTFLoader(); loader.load(modelPath, function (gltf) { const group = gltf.scene; group.position.copy(position); if (rotation){ group.rotation.y = rotation; } group.userData = { id, type, ...buildingData, csvPath }; group.traverse(function (child) { if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; child.userData.parentGroup = group; interactableObjects.push(child); // Make windows less transparent if (child.material && child.material.name && child.material.name.toLowerCase().includes("vetro")) { child.material.thickness = 1; child.material.opacity = 1; // or 1 for fully opaque child.material.transparent = true; // or false if you want no transparency child.material.transmission = 0.01; } } }); buildings.push(group); scene.add(group); if (csvPath) loadBuildingCSV(group, csvPath); }, undefined, function (error) { console.error('Error loading model:', error); }); } else { const mesh = new THREE.Mesh(buildingData.geometry, buildingData.material); mesh.position.copy(position); mesh.castShadow = true; mesh.receiveShadow = true; mesh.userData = { id, type, ...buildingData, csvPath }; // Store csvPath scene.add(mesh); buildings.push(mesh); interactableObjects.push(mesh); if (csvPath) loadBuildingCSV(mesh, csvPath); return mesh; } } function loadBuildingCSV(building, csvPath) { Papa.parse(csvPath, { download: true, header: true, dynamicTyping: true, complete: function (results) { building.userData.energyData = results.data; // Array of rows // Optionally, set initial values if (results.data.length > 0) { updateBuildingEnergy(building, 0); // Set to first row } } }); } // Helper to update building's current energy values by row index (e.g., time step) function updateBuildingEnergy(building, rowIndex) { const data = building.userData.energyData; if (!data || !data[rowIndex]) return; const row = data[rowIndex]; building.userData.generation = row.Generation; building.userData.consumption = row.Consumption; building.userData.devices = { Time: row.Time, AC1: row.AC1, AC2: row.AC2, AC3: row.AC3, AC4: row.AC4, WaterHeater: row['Water heater'], TV: row.TV, Microwave: row.Microwave, Kettle: row.Kettle, Lighting: row.Lighting, Refrigerator: row.Refrigerator, FlexibleLoad: row['Flexible Load'], RigidLoad: row['Rigid Load'] }; } function updateAllBuildingsForTimeStep(rowIndex) { buildings.forEach(obj => { if (obj.userData.energyData) { updateBuildingEnergy(obj, rowIndex); } }); } function updateBuildings() { const rowIndex = getCurrentRowIndex(); if (rowIndex !== lastUpdateRowIndex) { //updateAllBuildingsForTimeStep(rowIndex); lastUpdateRowIndex = rowIndex; } }