SRP
This commit is contained in:
205
js/buildings.js
205
js/buildings.js
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
59
js/controls.js
vendored
@@ -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();
|
||||
}
|
||||
71
js/main.js
71
js/main.js
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}());
|
||||
263
js/panels.js
263
js/panels.js
@@ -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
106
js/sun.js
@@ -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);
|
||||
}
|
||||
93
js/time.js
93
js/time.js
@@ -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;
|
||||
}
|
||||
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it is too large.
|
3858
web-app/dist/assets/index-Bso7APm4.js
vendored
3858
web-app/dist/assets/index-Bso7APm4.js
vendored
File diff suppressed because one or more lines are too long
1
web-app/dist/assets/index-D9Xb8bfs.css
vendored
1
web-app/dist/assets/index-D9Xb8bfs.css
vendored
File diff suppressed because one or more lines are too long
14
web-app/dist/index.html
vendored
14
web-app/dist/index.html
vendored
@@ -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>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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
Reference in New Issue
Block a user