mirror of
https://github.com/beestat/app.git
synced 2026-04-06 01:02:13 -04:00
Sun & moon
This commit is contained in:
parent
8d3666edf9
commit
b0924f4577
@ -390,6 +390,21 @@ beestat.component.card.three_d.prototype.decorate_drawing_pane_ = function(paren
|
||||
this.scene_.set_auto_rotate(beestat.setting('visualize.three_d.auto_rotate'));
|
||||
|
||||
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
|
||||
|
||||
// Set location for celestial light calculations if address is available
|
||||
if (
|
||||
floor_plan.address_id !== undefined &&
|
||||
floor_plan.address_id !== null
|
||||
) {
|
||||
const address = beestat.cache.address[floor_plan.address_id];
|
||||
if (address !== undefined) {
|
||||
this.scene_.set_location(
|
||||
address.normalized.metadata.latitude,
|
||||
address.normalized.metadata.longitude
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const groups = Object.values(floor_plan.data.groups);
|
||||
groups.forEach(function(group) {
|
||||
const setting_key = 'visualize.three_d.show_group.' + group.group_id;
|
||||
@ -397,6 +412,7 @@ beestat.component.card.three_d.prototype.decorate_drawing_pane_ = function(paren
|
||||
});
|
||||
|
||||
this.scene_.set_layer_visible('walls', beestat.setting('visualize.three_d.show_walls'));
|
||||
this.scene_.set_layer_visible('environment', beestat.setting('visualize.three_d.show_environment'));
|
||||
|
||||
// Manage width of the scene.
|
||||
if (this.state_.width === undefined) {
|
||||
|
||||
@ -67,8 +67,9 @@ beestat.component.scene.prototype.decorate_ = function(parent) {
|
||||
parent.style('background', '#202a30');
|
||||
|
||||
this.debug_ = {
|
||||
'axes': false,
|
||||
'axes': true,
|
||||
'directional_light_top_helper': false,
|
||||
'sun_light_helper': true,
|
||||
// 'grid': false,
|
||||
'watcher': false
|
||||
};
|
||||
@ -145,6 +146,10 @@ beestat.component.scene.prototype.add_renderer_ = function(parent) {
|
||||
this.renderer_.setPixelRatio(window.devicePixelRatio);
|
||||
this.renderer_.setSize(this.width_, this.height_);
|
||||
|
||||
// Enable shadow maps
|
||||
this.renderer_.shadowMap.enabled = true;
|
||||
this.renderer_.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
|
||||
parent[0].appendChild(this.renderer_.domElement);
|
||||
};
|
||||
|
||||
@ -356,6 +361,181 @@ beestat.component.scene.prototype.add_ambient_light_ = function() {
|
||||
));
|
||||
};
|
||||
|
||||
/**
|
||||
* Directional sun and moon lights that provide natural lighting. Only
|
||||
* visible when the environment layer is enabled. Positions are calculated based
|
||||
* on time of day and location.
|
||||
*/
|
||||
beestat.component.scene.prototype.add_celestial_lights_ = function() {
|
||||
// Create celestial group if it doesn't exist
|
||||
if (this.celestial_group_ === undefined) {
|
||||
this.celestial_group_ = new THREE.Group();
|
||||
this.scene_.add(this.celestial_group_);
|
||||
this.layers_['celestial'] = this.celestial_group_;
|
||||
}
|
||||
|
||||
// Sun light
|
||||
this.sun_light_ = new THREE.DirectionalLight(
|
||||
0xffffdd, // Slightly warm color for sunlight
|
||||
0.6
|
||||
);
|
||||
|
||||
// Initial position (will be updated by update_celestial_lights_)
|
||||
this.sun_light_.position.set(500, 500, -500);
|
||||
|
||||
// Enable shadow casting
|
||||
this.sun_light_.castShadow = true;
|
||||
|
||||
// Configure shadow properties
|
||||
this.sun_light_.shadow.mapSize.width = 2048;
|
||||
this.sun_light_.shadow.mapSize.height = 2048;
|
||||
this.sun_light_.shadow.camera.left = -500;
|
||||
this.sun_light_.shadow.camera.right = 500;
|
||||
this.sun_light_.shadow.camera.top = 500;
|
||||
this.sun_light_.shadow.camera.bottom = -500;
|
||||
this.sun_light_.shadow.camera.near = 0.5;
|
||||
this.sun_light_.shadow.camera.far = 2000;
|
||||
this.sun_light_.shadow.bias = -0.001; // Prevent shadow acne
|
||||
|
||||
// Set target to world origin (0,0,0) so light always points there
|
||||
this.sun_light_.target.position.set(0, 0, 0);
|
||||
this.scene_.add(this.sun_light_.target);
|
||||
|
||||
this.celestial_group_.add(this.sun_light_);
|
||||
|
||||
if (this.debug_.sun_light_helper === true) {
|
||||
this.sun_light_helper_ = new THREE.DirectionalLightHelper(
|
||||
this.sun_light_,
|
||||
100
|
||||
);
|
||||
this.celestial_group_.add(this.sun_light_helper_);
|
||||
}
|
||||
|
||||
// Moon light
|
||||
this.moon_light_ = new THREE.DirectionalLight(
|
||||
0xaaccff, // Cool bluish color for moonlight
|
||||
0.15
|
||||
);
|
||||
|
||||
// Initial position (will be updated by update_celestial_lights_)
|
||||
this.moon_light_.position.set(-500, 500, 500);
|
||||
|
||||
// Moon casts shadows too
|
||||
this.moon_light_.castShadow = true;
|
||||
|
||||
// Configure shadow properties (same as sun)
|
||||
this.moon_light_.shadow.mapSize.width = 2048;
|
||||
this.moon_light_.shadow.mapSize.height = 2048;
|
||||
this.moon_light_.shadow.camera.left = -500;
|
||||
this.moon_light_.shadow.camera.right = 500;
|
||||
this.moon_light_.shadow.camera.top = 500;
|
||||
this.moon_light_.shadow.camera.bottom = -500;
|
||||
this.moon_light_.shadow.camera.near = 0.5;
|
||||
this.moon_light_.shadow.camera.far = 2000;
|
||||
this.moon_light_.shadow.bias = -0.001;
|
||||
|
||||
// Set target to world origin
|
||||
this.moon_light_.target.position.set(0, 0, 0);
|
||||
this.scene_.add(this.moon_light_.target);
|
||||
|
||||
this.celestial_group_.add(this.moon_light_);
|
||||
|
||||
if (this.debug_.sun_light_helper === true) {
|
||||
this.moon_light_helper_ = new THREE.DirectionalLightHelper(
|
||||
this.moon_light_,
|
||||
100
|
||||
);
|
||||
this.celestial_group_.add(this.moon_light_helper_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update sun and moon light positions based on date and location using SunCalc.
|
||||
* Adjusts light intensities based on altitude and moon phase.
|
||||
*
|
||||
* @param {moment} date The date/time to calculate positions for
|
||||
* @param {number} latitude Location latitude
|
||||
* @param {number} longitude Location longitude
|
||||
*/
|
||||
beestat.component.scene.prototype.update_celestial_lights_ = function(date, latitude, longitude) {
|
||||
if (
|
||||
this.sun_light_ === undefined ||
|
||||
this.moon_light_ === undefined ||
|
||||
date === undefined ||
|
||||
latitude === undefined ||
|
||||
longitude === undefined
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const distance = 1000; // Distance from origin for light positioning
|
||||
|
||||
const js_date = date.toDate();
|
||||
|
||||
// === SUN ===
|
||||
const sun_position = SunCalc.getPosition(js_date, latitude, longitude);
|
||||
const sun_altitude = sun_position.altitude;
|
||||
const sun_azimuth = sun_position.azimuth;
|
||||
|
||||
// Convert spherical coordinates to Cartesian
|
||||
// SunCalc: azimuth 0=south, π/2=west, π/-π=north, -π/2=east
|
||||
// World coords: +X=east, +Y=up, +Z=south, -Z=north
|
||||
// DirectionalLight shines FROM position TOWARD origin (0,0,0)
|
||||
const sun_x = distance * Math.cos(sun_altitude) * Math.sin(sun_azimuth);
|
||||
const sun_y = distance * Math.cos(sun_altitude) * Math.cos(sun_azimuth);
|
||||
const sun_z = distance * Math.sin(sun_altitude);
|
||||
|
||||
this.sun_light_.position.set(sun_x, sun_y, sun_z);
|
||||
|
||||
// Adjust sun intensity based on altitude (fade when below horizon)
|
||||
let sun_intensity = 0.6;
|
||||
if (sun_altitude < 0) {
|
||||
// Sun is below horizon, fade out
|
||||
sun_intensity = Math.max(0, 0.6 * (1 + sun_altitude / (Math.PI / 6)));
|
||||
}
|
||||
|
||||
this.sun_light_.intensity = sun_intensity;
|
||||
this.sun_light_.castShadow = sun_intensity > 0.05;
|
||||
|
||||
// === MOON ===
|
||||
const moon_position = SunCalc.getMoonPosition(js_date, latitude, longitude);
|
||||
const moon_altitude = moon_position.altitude;
|
||||
const moon_azimuth = moon_position.azimuth;
|
||||
|
||||
// Get moon illumination (phase)
|
||||
const moon_illumination = SunCalc.getMoonIllumination(js_date);
|
||||
const moon_fraction = moon_illumination.fraction; // 0 = new moon, 1 = full moon
|
||||
|
||||
// Convert spherical coordinates to Cartesian (same as sun)
|
||||
const moon_x = distance * Math.cos(moon_altitude) * Math.sin(moon_azimuth);
|
||||
const moon_y = distance * Math.cos(moon_altitude) * Math.cos(moon_azimuth);
|
||||
const moon_z = distance * Math.sin(moon_altitude);
|
||||
|
||||
this.moon_light_.position.set(moon_x, moon_y, moon_z);
|
||||
|
||||
// Adjust moon intensity based on altitude and illumination
|
||||
let moon_intensity = 0.15 * moon_fraction; // Scaled by moon phase
|
||||
if (moon_altitude < 0) {
|
||||
// Moon is below horizon, fade out
|
||||
moon_intensity = Math.max(0, moon_intensity * (1 + moon_altitude / (Math.PI / 6)));
|
||||
}
|
||||
|
||||
this.moon_light_.intensity = moon_intensity;
|
||||
this.moon_light_.castShadow = moon_intensity > 0.02;
|
||||
|
||||
// Update debug helpers if enabled
|
||||
if (this.debug_.sun_light_helper === true) {
|
||||
// Force world matrix update before updating helpers
|
||||
this.sun_light_.updateMatrixWorld();
|
||||
this.sun_light_.target.updateMatrixWorld();
|
||||
this.sun_light_helper_.update();
|
||||
|
||||
this.moon_light_.updateMatrixWorld();
|
||||
this.moon_light_.target.updateMatrixWorld();
|
||||
this.moon_light_helper_.update();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the scene based on the currently set date.
|
||||
*/
|
||||
@ -509,6 +689,11 @@ beestat.component.scene.prototype.update_ = function() {
|
||||
this.directional_light_top_helper_.update();
|
||||
}
|
||||
|
||||
// Update celestial lights (sun and moon) based on date and location
|
||||
if (this.date_ !== undefined && this.latitude_ !== undefined && this.longitude_ !== undefined) {
|
||||
this.update_celestial_lights_(this.date_, this.latitude_, this.longitude_);
|
||||
}
|
||||
|
||||
// Update debug watcher
|
||||
if (this.debug_.watcher === true) {
|
||||
this.update_debug_();
|
||||
@ -810,6 +995,8 @@ beestat.component.scene.prototype.add_walls_ = function(layer, group) {
|
||||
mesh.position.z = -wall_height - elevation;
|
||||
mesh.userData.is_wall = true;
|
||||
mesh.layers.set(beestat.component.scene.layer_visible);
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
|
||||
layer.add(mesh);
|
||||
}
|
||||
@ -913,7 +1100,6 @@ beestat.component.scene.prototype.add_environment_ = function() {
|
||||
// Position the ground flush with the base of the house (hides any below-ground structures).
|
||||
let current_z = 0;
|
||||
|
||||
// Ground strata from top to bottom: grass, topsoil, clay, sand, bedrock.
|
||||
const padding = 60;
|
||||
const strata = [
|
||||
{'color': 0x4a7c3f, 'thickness': 8},
|
||||
@ -939,10 +1125,14 @@ beestat.component.scene.prototype.add_environment_ = function() {
|
||||
mesh.position.y = center_y;
|
||||
mesh.position.z = current_z + stratum.thickness / 2;
|
||||
mesh.userData.is_environment = true;
|
||||
mesh.receiveShadow = true;
|
||||
|
||||
environment_layer.add(mesh);
|
||||
current_z += stratum.thickness;
|
||||
}, this);
|
||||
|
||||
// Add celestial lights (sun and moon) to the environment layer
|
||||
this.add_celestial_lights_();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -962,6 +1152,25 @@ beestat.component.scene.prototype.set_date = function(date) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the location for celestial light calculations.
|
||||
*
|
||||
* @param {number} latitude
|
||||
* @param {number} longitude
|
||||
*
|
||||
* @return {beestat.component.scene}
|
||||
*/
|
||||
beestat.component.scene.prototype.set_location = function(latitude, longitude) {
|
||||
this.latitude_ = latitude;
|
||||
this.longitude_ = longitude;
|
||||
|
||||
if (this.rendered_ === true) {
|
||||
this.update_();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the type of data this scene is visualizing.
|
||||
*
|
||||
@ -1030,6 +1239,17 @@ beestat.component.scene.prototype.set_layer_visible = function(layer_name, visib
|
||||
);
|
||||
});
|
||||
|
||||
// When toggling environment, also toggle celestial lights
|
||||
if (layer_name === 'environment' && this.layers_['celestial'] !== undefined) {
|
||||
this.layers_['celestial'].traverse(function(child) {
|
||||
child.layers.set(
|
||||
visible === true
|
||||
? beestat.component.scene.layer_visible
|
||||
: beestat.component.scene.layer_hidden
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user