This commit is contained in:
rafaeldpsilva
2025-12-10 14:57:34 +00:00
parent 84f5286126
commit b1ddf0c85e
102 changed files with 0 additions and 497175 deletions

View File

@@ -1,205 +0,0 @@
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;
}
}

View File

@@ -1,88 +0,0 @@
let moveAnimating = false;
let moveStart = null;
let moveEnd = null;
let contStart = null;
let contEnd = null;
let moveProgress = 0;
const moveDuration = 0.2; // seconds
let moveStartTime = 0;
let fixedY = 45;
function initCameras(renderer) {
// Camera (Fixed Diagonal View)
const aspect = window.innerWidth / window.innerHeight;
perspCamera = new THREE.PerspectiveCamera(40, aspect, 1, 1000);
camera = perspCamera;
camera.position.set(25, 45, 25);
camera.lookAt(0, 0, 0);
}
function initMotions(){
window.addEventListener('keydown', function(event) {
});
// Restrict panning to X and Z only
controls.addEventListener('change', function() {
if (moveAnimating) return; // Don't update target while animating
controls.target.y = 10;
camera.position.y = fixedY;
camera.position.y = Math.max(camera.position.y, 20);
camera.position.y = Math.min(camera.position.y, 45);
});
// --- Scroll wheel pans the camera forward/backward (along view direction) ---
renderer.domElement.addEventListener('wheel', function(event) {
event.preventDefault(); // Prevent default scrolling behavior
if (!moveAnimating){
if (event.deltaY < 0 && camera.position.y <= 35) {
// Scrolling up
moveStart = camera.position.clone();
moveEnd = camera.position.clone().add(new THREE.Vector3(2, 10, 2));
contStart = controls.target.clone();
contEnd = controls.target.clone().add(new THREE.Vector3(2, 0, 2));
moveProgress = 0;
moveStartTime = performance.now() / 1000;
moveAnimating = true;
} else if (event.deltaY > 0 && camera.position.y >= 30) {
// Scrolling down
moveStart = camera.position.clone();
moveEnd = camera.position.clone().add(new THREE.Vector3(-2, -10, -2));
contStart = controls.target.clone();
contEnd = controls.target.clone().add(new THREE.Vector3(-2, 0, -2));
moveProgress = 0;
moveStartTime = performance.now() / 1000;
moveAnimating = true;
}
}
}, { passive: false });
}
// --- Event Handlers ---
function onWindowResize() {
const aspect = window.innerWidth / window.innerHeight;
perspCamera.aspect = aspect;
perspCamera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animateCamera() {
if (moveAnimating) {
const now = performance.now() / 1000;
moveProgress = Math.min((now - moveStartTime) / moveDuration, 1);
camera.position.lerpVectors(moveStart, moveEnd, moveProgress);
controls.target.lerpVectors(contStart, contEnd, moveProgress); // Optional: move target too
if (moveProgress >= 1) {
moveAnimating = false;
fixedY = camera.position.y;
}
controls.update();
}
}

59
js/controls.js vendored
View File

@@ -1,59 +0,0 @@
let mouse;
let raycaster;
function initControls(scene, camera, renderer){
// --- Add OrbitControls ---
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // Smooth movement
controls.dampingFactor = 0.05;
controls.enableRotate = false;
controls.enablePan = true;
controls.enableZoom = false;
controls.mouseButtons = {
LEFT: THREE.MOUSE.PAN
}
controls.target.set(0, 0, 0); // Focus on scene center
controls.update();
const transformControls = new THREE.TransformControls(camera, renderer.domElement);
transformControls.setMode('translate'); // Move mode
transformControls.showX = true;
transformControls.showY = false;
transformControls.showZ = true;
transformControls.enabled = false;
transformControls.addEventListener('dragging-changed', function (event) {
controls.enabled = !event.value; // Disable OrbitControls while dragging
});
scene.add(transformControls);
window.addEventListener('keydown', function(event) {
if (event.key === 'm') {
transformControls.enabled = !transformControls.enabled;
}
});
// Add event listener for selecting Building To Move
renderer.domElement.addEventListener('pointerdown', function(event) {
// Calculate mouse position in normalized device coordinates
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactableObjects);
if (transformControls.enabled) {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactableObjects);
if (intersects.length > 0) {
const selected = intersects[0].object.userData.parentGroup || intersects[0].object;
transformControls.attach(selected);
} else {
transformControls.detach();
}
}
});
mouse = new THREE.Vector2();
raycaster = new THREE.Raycaster();
}

View File

@@ -1,71 +0,0 @@
// --- Global Variables ---
let scene, camera, renderer, controls;
// --- Initialization ---
function init() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x9ee3f9);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
//renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
initCameras(renderer);
initSun(scene);
initControls(scene, camera, renderer);
initMotions();
initPanels();
// Event Listeners
window.addEventListener('resize', onWindowResize, false);
// Load the city model
// Ground Plane (Unchanged)
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x91ca49, side: THREE.DoubleSide });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
const loader = new THREE.GLTFLoader();
loader.load('assets/models/ground.glb', function (gltf) {
const group = gltf.scene;
group.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.userData.parentGroup = group;
}
});
buildings.push(group);
scene.add(group);
}, undefined, function (error) {s
console.error('Error loading model:', error);
});
initBuildings();
initTime();
}
// --- Animation Loop ---
function animate(timestamp) {
requestAnimationFrame(animate);
totalGameSecondsElapsed = animateUpdateTime(timestamp);
updateBuildings();
updatePanels();
updateSun(scene);
updateFps();
animateCamera()
controls.update();
renderer.render(scene, camera);
}
// --- Start ---
window.onload = function () {
init();
animate(0); // Start animation loop, pass initial timestamp 0
}

View File

@@ -1,317 +0,0 @@
/*
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
https://github.com/mourner/suncalc
*/
(function () { 'use strict';
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
// date/time constants and conversions
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
function toDays(date) { return toJulian(date) - J2000; }
// general calculations for position
var e = rad * 23.4397; // obliquity of the Earth
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
function astroRefraction(h) {
if (h < 0) // the following formula works for positive altitudes only.
h = 0; // if h = -0.08901179 a div/0 would occur.
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
}
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + PI;
}
function sunCoords(d) {
var M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
return {
dec: declination(L, 0),
ra: rightAscension(L, 0)
};
}
var SunCalc = {};
// calculates sun position for a given date and latitude/longitude
SunCalc.getPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = sunCoords(d),
H = siderealTime(d, lw) - c.ra;
return {
azimuth: azimuth(H, phi, c.dec),
altitude: altitude(H, phi, c.dec)
};
};
// sun times configuration (angle, morning name, evening name)
var times = SunCalc.times = [
[-0.833, 'sunrise', 'sunset' ],
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
[ -6, 'dawn', 'dusk' ],
[ -12, 'nauticalDawn', 'nauticalDusk'],
[ -18, 'nightEnd', 'night' ],
[ 6, 'goldenHourEnd', 'goldenHour' ]
];
// adds a custom time to the times config
SunCalc.addTime = function (angle, riseName, setName) {
times.push([angle, riseName, setName]);
};
// calculations for sun times
var J0 = 0.0009;
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {
var w = hourAngle(h, phi, dec),
a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}
// calculates sun times for a given date, latitude/longitude, and, optionally,
// the observer height (in meters) relative to the horizon
SunCalc.getTimes = function (date, lat, lng, height) {
height = height || 0;
var lw = rad * -lng,
phi = rad * lat,
dh = observerAngle(height),
d = toDays(date),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),
M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),
Jnoon = solarTransitJ(ds, M, L),
i, len, time, h0, Jset, Jrise;
var result = {
solarNoon: fromJulian(Jnoon),
nadir: fromJulian(Jnoon - 0.5)
};
for (i = 0, len = times.length; i < len; i += 1) {
time = times[i];
h0 = (time[0] + dh) * rad;
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
result[time[1]] = fromJulian(Jrise);
result[time[2]] = fromJulian(Jset);
}
return result;
};
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
M = rad * (134.963 + 13.064993 * d), // mean anomaly
F = rad * (93.272 + 13.229350 * d), // mean distance
l = L + rad * 6.289 * sin(M), // longitude
b = rad * 5.128 * sin(F), // latitude
dt = 385001 - 20905 * cos(M); // distance to the moon in km
return {
ra: rightAscension(l, b),
dec: declination(l, b),
dist: dt
};
}
SunCalc.getMoonPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = moonCoords(d),
H = siderealTime(d, lw) - c.ra,
h = altitude(H, phi, c.dec),
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
h = h + astroRefraction(h); // altitude correction for refraction
return {
azimuth: azimuth(H, phi, c.dec),
altitude: h,
distance: c.dist,
parallacticAngle: pa
};
};
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
SunCalc.getMoonIllumination = function (date) {
var d = toDays(date || new Date()),
s = sunCoords(d),
m = moonCoords(d),
sdist = 149598000, // distance from Earth to Sun in km
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
return {
fraction: (1 + cos(inc)) / 2,
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
angle: angle
};
};
function hoursLater(date, h) {
return new Date(date.valueOf() + h * dayMs / 24);
}
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
var t = new Date(date);
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
}
var result = {};
if (rise) result.rise = hoursLater(t, rise);
if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
return result;
};
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
else if (typeof define === 'function' && define.amd) define(SunCalc);
else window.SunCalc = SunCalc;
}());

View File

@@ -1,263 +0,0 @@
const debugPanel = document.getElementById('debug-panel');
const infoPanel = document.getElementById('info-panel');
const infoButton = document.getElementById('info-button');
const moveButton = document.getElementById('move-button');
const toolPalette = document.getElementById('tool-palette');
let infoPanelPinned = false;
let pinnedBuildingData = null;
let debugOn = true;
let interactableObjects = [];
let INTERSECTED;
// Add these variables at the top of your file (outside any function)
let lastFpsUpdate = performance.now();
let frameCount = 0;
let currentFps = 0;
// Call this in your animation loop (e.g., in animate() in main.js)
function updateFps() {
frameCount++;
const now = performance.now();
if (now - lastFpsUpdate > 500) { // Update every 0.5s
currentFps = (frameCount * 1000) / (now - lastFpsUpdate);
lastFpsUpdate = now;
frameCount = 0;
}
}
function initPanels(){
window.addEventListener('keydown', function(event) {
if (event.key === 'h') {
console.log('H');
hideDebugPanel()
debugOn = !debugOn;
}
});
/* renderer.domElement.addEventListener('pointerdown', function(event) {
// Calculate mouse position in normalized device coordinates
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactableObjects);
if (intersects.length > 0) {
console.log(intersects[0].object);
// Pin the info panel to this building
infoPanelPinned = true;
const selected = intersects[0].object.userData.parentGroup || intersects[0].object;
pinnedBuildingData = selected.userData;
displayInfo(pinnedBuildingData);
} else {
// Unpin if clicking empty space
infoPanelPinned = false;
pinnedBuildingData = null;
hideInfo();
}
}); */
document.addEventListener('mousemove', onMouseMove, false);
toolPalette.addEventListener('mouseup', (event) => event.stopPropagation(), false);
// Tool Palette Button Listeners
infoButton.addEventListener('click', showInfoPanelForSelected);
moveButton.addEventListener('click', () => {
if (selectedObject) {
console.log("Move tool clicked for:", selectedObject.userData.description);
// Add move logic here later
hideToolPalette(); // Hide palette after action (optional)
}
});
}
function onMouseMove(event) {
if (infoPanelPinned) return;
// Calculate mouse position (Unchanged)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// --- Info Panel Positioning (Adjusted for bottom-left) ---
const panelRect = infoPanel.getBoundingClientRect();
let panelX = event.clientX + 15;
let panelY = event.clientY - panelRect.height - 15; // Position above cursor
// Basic boundary checks (adjust as needed)
if (panelX + panelRect.width > window.innerWidth - 10) {
panelX = event.clientX - panelRect.width - 15;
}
if (panelY < 10) { // Check top boundary
panelY = event.clientY + 15; // Move below cursor if too high
}
if (panelX < 10) panelX = 10; // Prevent going off left
infoPanel.style.left = `${panelX}px`;
infoPanel.style.top = `${panelY}px`;
}
// --- Debug Panel ---
function displaySunDebug() {
const pos = directionalLight.position;
debugPanel.innerHTML = `<h3>Debug Info</h3>
<p><strong>Position:</strong> (${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)})</p>`;
debugPanel.style.display = 'block';
}
function displayDebugPanel(object) {
if (!object || !object.position) {
debugPanel.style.display = 'none';
return;
}
const pos = object.position;
debugPanel.innerHTML = `<h3>Debug Info</h3>
<p><strong>ID:</strong> ${object.userData?.id ?? 'N/A'}</p>
<p><strong>Position:</strong> (${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)})</p>`;
debugPanel.style.display = 'block';
}
function hideDebugPanel() {
debugPanel.style.display = 'none';
}
function showToolPalette(targetObject) {
if (!targetObject) return;
// Calculate screen position of the object
const screenPos = toScreenPosition(targetObject, camera);
// Position palette slightly above and to the right of the object's center
toolPalette.style.left = `${screenPos.x + 10}px`;
toolPalette.style.top = `${screenPos.y - toolPalette.offsetHeight - 10}px`; // Offset by palette height
// Basic boundary check (prevent going off screen) - needs improvement
const paletteRect = toolPalette.getBoundingClientRect(); // Get actual size after potential content change
if (parseInt(toolPalette.style.left) + paletteRect.width > window.innerWidth - 10) {
toolPalette.style.left = `${window.innerWidth - paletteRect.width - 10}px`;
}
if (parseInt(toolPalette.style.top) < 10) {
toolPalette.style.top = '10px';
}
if (parseInt(toolPalette.style.left) < 10) {
toolPalette.style.left = '10px';
}
toolPalette.style.display = 'flex'; // Use flex to show it
}
function hideToolPalette() {
toolPalette.style.display = 'none';
}
function showInfoPanelForSelected() {
if (selectedObject && selectedObject.userData) {
displayInfo(selectedObject.userData);
} else {
hideInfoPanel();
}
// Optionally hide palette after clicking info
// hideToolPalette();
}
function displayInfo(buildingData) {
document.getElementById('info-title').textContent = buildingData.description;
document.getElementById('info-consumption').textContent = buildingData.consumption;
document.getElementById('info-generation').textContent = buildingData.generation;
document.getElementById('info-iot').textContent = buildingData.iot;
infoPanel.style.display = 'block'; // Show the panel
}
function hideInfoPanel() {
infoPanel.style.display = 'none'; // Hide the panel
}
// --- Info Panel ---
function displayInfo(buildingData) {
if (infoPanelPinned && pinnedBuildingData && pinnedBuildingData !== buildingData) return;
if (infoPanelPinned && !pinnedBuildingData) pinnedBuildingData = buildingData;
document.getElementById('info-title').textContent = buildingData.id;
document.getElementById('info-consumption').textContent = buildingData.consumption + ' kWh';
document.getElementById('info-generation').textContent = buildingData.generation + ' kWh';
// Dynamically show IoT/device consumption
let iotHtml = '';
if (buildingData.devices) {
for (const [key, value] of Object.entries(buildingData.devices)) {
iotHtml += `<strong>${key}:</strong> ${value ?? 'N/A'}<br>`;
}
} else {
iotHtml = 'N/A';
}
document.getElementById('info-iot').innerHTML = iotHtml;
infoPanel.style.display = 'block';
}
function hideInfo() {
if (!infoPanelPinned) {
infoPanel.style.display = 'none';
pinnedBuildingData = null;
}
}
function displayDebugPanelWithCamera(object) {
let html = `<h3>Debug Info</h3>`;
if (object && object.position) {
html += `<p><strong>ID:</strong> ${object.userData?.id ?? 'N/A'}</p>
<p><strong>Object Position:</strong> (${object.position.x.toFixed(2)}, ${object.position.y.toFixed(2)}, ${object.position.z.toFixed(2)})</p>`;
}
html += `<p><strong>FPS:</strong> ${currentFps.toFixed(1)}</p>`;
html += `<p><strong>Camera Position:</strong> (${camera.position.x.toFixed(2)}, ${camera.position.y.toFixed(2)}, ${camera.position.z.toFixed(2)})</p>`;
html += `<p><strong>Controls Target:</strong> (${controls.target.x.toFixed(2)}, ${controls.target.y.toFixed(2)}, ${controls.target.z.toFixed(2)})</p>`;
html += `<p><strong>Sun Position:</strong> (${directionalLight.position.x.toFixed(2)}, ${directionalLight.position.y.toFixed(2)}, ${directionalLight.position.z.toFixed(2)})</p>`;
debugPanel.innerHTML = html;
debugPanel.style.display = 'block';
}
function updatePanels(){
if (infoPanelPinned && pinnedBuildingData) {
displayInfo(pinnedBuildingData);
}
// --- Raycasting Logic ---
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactableObjects);
let debugTarget = null;
if (infoPanelPinned && pinnedBuildingData) {
debugTarget = interactableObjects.find(obj => obj.userData === pinnedBuildingData);
} else if (intersects.length > 0) {
debugTarget = intersects[0].object.userData.parentGroup || intersects[0].object;
}
if (debugOn){
displayDebugPanelWithCamera(debugTarget);
//displaySunDebug();
}
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
if (INTERSECTED) { /* Optional: Revert highlight */ }
//INTERSECTED = intersects[0].object.userData.parentGroup || intersects[0].object;
INTERSECTED = intersects[0].object;
if (INTERSECTED.userData) {
//console.log('Pinned building data:', INTERSECTED.userData);
if (INTERSECTED.userData.id){
displayInfo(INTERSECTED.userData);
} else{
displayInfo(INTERSECTED.userData.parentGroup.userData);
}
} else {
hideInfo();
}
}
} else {
if (INTERSECTED) {
hideInfo();
}
INTERSECTED = null;
}
}

106
js/sun.js
View File

@@ -1,106 +0,0 @@
let directionalLight;
let ambientLight;
function initSun(scene) {
// Lighting
ambientLight = new THREE.AmbientLight(0x90bbbd, 0.9);
scene.add(ambientLight);
directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(20, 30, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 4096;
directionalLight.shadow.mapSize.height = 4096;
directionalLight.shadow.camera.near = 25;
directionalLight.shadow.camera.far = 250;
directionalLight.shadow.bias = -0.005
directionalLight.shadow.camera.left = -100;
directionalLight.shadow.camera.right = 100;
directionalLight.shadow.camera.top = 100;
directionalLight.shadow.camera.bottom = -100;
scene.add(directionalLight);
}
function sunPosition(){
// --- Use SunCalc to get sun position ---
// Set your latitude and longitude
const latitude = 41.17873;
const longitude = -8.60835; // Example: adjust as needed
// Calculate the current date and time based on your simulation hour
const now = new Date();
now.setHours(Math.floor(currentHour));
now.setMinutes(Math.floor((currentHour % 1) * 60));
now.setSeconds(0);
// Get sun position from SunCalc
const sunPos = SunCalc.getPosition(now, latitude, longitude);
// Convert to your scene's coordinates
const sunRadius = 100;
const azimuth = sunPos.azimuth; // Convert from south-based to x/z-plane (east = 0)
const elevation = sunPos.altitude;
console.log(azimuth, elevation)
const sunX = sunRadius * Math.cos(elevation) * Math.cos(azimuth);
const sunY = sunRadius * Math.sin(elevation);
const sunZ = sunRadius * Math.cos(elevation) * Math.sin(azimuth);
directionalLight.position.set(sunX, sunY, sunZ);
directionalLight.target.position.set(0, 0, 0);
directionalLight.target.updateMatrixWorld();
}
function updateSun(scene) {
sunPosition();
// --- Dynamic Sky Color ---
const dayColor = new THREE.Color(0xa9d3fd); // Light blue
const nightColor = new THREE.Color(0x0a0a23); // Dark blue/black
const sunriseSunColor = new THREE.Color(0xa3d1ff); // Bright white/yellow
const daySunColor = new THREE.Color(0xffffa9); // Bright white/yellow
const sunsetSunColor = new THREE.Color(0xffcc25); // Warm orange
const nightSunColor = new THREE.Color(0x222244); // Dim blue
const dayAmbientColor = new THREE.Color(0xa9d3fd); // Light blue
const nightAmbientColor = new THREE.Color(0x222244); // Dim blue
let sunColor;
if (currentHour < 5 || currentHour >= 21){
sunColor = nightSunColor;
ambientLight.color.copy(nightAmbientColor);
directionalLight.castShadow = false;
directionalLight.intensity = 0
scene.background = nightColor;
} else if (5 < currentHour && currentHour <= 8) {
// Night to sunrise
const t = (currentHour - 5) / 3;
sunColor = nightSunColor.clone().lerp(sunriseSunColor, t);
ambientLight.color.copy(nightAmbientColor.clone().lerp(dayAmbientColor, t));
directionalLight.castShadow = true;
directionalLight.intensity = t *0.5;
scene.background = nightColor.clone().lerp(dayColor, t);
} else if (currentHour < 11) {
// Sunrise to day
const t = (currentHour - 8) / 3;
sunColor = sunriseSunColor.clone().lerp(daySunColor, t);
directionalLight.castShadow = true;
scene.background = dayColor;
} else if (currentHour < 16) {
// Day
sunColor = daySunColor;
scene.background = dayColor;
} else if (currentHour < 20) {
// Day to sunset
const t = (currentHour - 16) / 2;
directionalLight.intensity = (1-t) * 0.5
sunColor = daySunColor.clone().lerp(sunsetSunColor, t);
scene.background = dayColor;
} else if (currentHour < 21) {
// Sunset to night
const t = (currentHour - 20);
sunColor = sunsetSunColor.clone().lerp(nightSunColor, t);
ambientLight.color.copy(dayAmbientColor.clone().lerp(nightAmbientColor, t));
scene.background = dayColor.clone().lerp(nightColor, t);
}
directionalLight.color.copy(sunColor);
}

View File

@@ -1,93 +0,0 @@
const pauseButton = document.getElementById('pause-button');
const playButton = document.getElementById('play-button');
const fastButton = document.getElementById('fast-button');
const timeDisplay = document.getElementById('time-display');
// --- Time System Variables ---
const NORMAL_SPEED = 60; // 1 real sec = 1 game min
const FAST_SPEED = 1000; // 1 real sec = 10 game min
let timeScaleMultiplier = NORMAL_SPEED; // Start at normal speed
let totalGameSecondsElapsed = 0;
let currentDay = 80;
let currentHour = 7.0; // Start at 8:00 AM
let lastTimestamp = 0; // For delta time calculation
let lastUpdateRowIndex = 0;
function initTime(){
// Time Control Button Listeners
pauseButton.addEventListener('click', () => setTimeScale(0));
playButton.addEventListener('click', () => setTimeScale(NORMAL_SPEED));
fastButton.addEventListener('click', () => setTimeScale(FAST_SPEED));
// Initialize time
totalGameSecondsElapsed = (currentDay - 1) * 86400 + currentHour * 3600;
updateTimeDisplay(); // Initial display update
updateButtonStates(); // Set initial button active state
}
// --- Time System Functions ---
function updateTime(deltaTime) {
if (timeScaleMultiplier <= 0) return; // Don't update if paused
totalGameSecondsElapsed += deltaTime * timeScaleMultiplier;
const SECONDS_IN_DAY = 86400;
const SECONDS_IN_HOUR = 3600;
let previousDay = currentDay;
currentDay = Math.floor(totalGameSecondsElapsed / SECONDS_IN_DAY) + 1;
currentHour = (totalGameSecondsElapsed % SECONDS_IN_DAY) / SECONDS_IN_HOUR;
if (currentDay !== previousDay) {
console.log(`Day ${currentDay} has begun!`);
// Add any daily logic here
}
updateTimeDisplay();
}
function formatTime(hour) {
const hours = Math.floor(hour);
const minutes = Math.floor((hour - hours) * 60);
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
}
function updateTimeDisplay() {
timeDisplay.textContent = `Day ${currentDay}, ${formatTime(currentHour)}`;
}
function setTimeScale(newScale) {
timeScaleMultiplier = newScale;
console.log(`Time scale set to: ${timeScaleMultiplier}`);
updateButtonStates();
}
function updateButtonStates() {
pauseButton.classList.toggle('active', timeScaleMultiplier === 0);
playButton.classList.toggle('active', timeScaleMultiplier === NORMAL_SPEED);
fastButton.classList.toggle('active', timeScaleMultiplier === FAST_SPEED);
}
function animateUpdateTime(timestamp){
// Calculate Delta Time
if (lastTimestamp === 0) {
lastTimestamp = timestamp; // Initialize on first frame
}
const deltaTime = (timestamp - lastTimestamp) / 1000; // Delta time in seconds
lastTimestamp = timestamp;
// --- Update Time ---
updateTime(deltaTime);
return totalGameSecondsElapsed;
}
function getCurrentRowIndex(){
// --- Update buildings every 15 game minutes ---
const SECONDS_IN_DAY = 86400;
const SECONDS_IN_HOUR = 3600;
const MINUTES_PER_ROW = 15;
const SECONDS_PER_ROW = MINUTES_PER_ROW * 60;
// Calculate which row should be active based on totalGameSecondsElapsed
const rowIndex = Math.floor((totalGameSecondsElapsed % SECONDS_IN_DAY) / SECONDS_PER_ROW);
return rowIndex;
}

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

View File

Can't render this file because it is too large.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Community Digital Twin</title>
<script type="module" crossorigin src="/assets/index-Bso7APm4.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D9Xb8bfs.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More