mirror of
https://github.com/beestat/app.git
synced 2026-02-26 13:10:23 -05:00
Lots of changes
This commit is contained in:
parent
df8e916b34
commit
f05fe3c049
@ -404,15 +404,16 @@ beestat.component.card.three_d.prototype.decorate_drawing_pane_ = function(paren
|
||||
}
|
||||
}
|
||||
|
||||
const show_exterior = beestat.setting('visualize.three_d.show_exterior') !== false;
|
||||
|
||||
const groups = Object.values(floor_plan.data.groups);
|
||||
groups.forEach(function(group) {
|
||||
const setting_key = 'visualize.three_d.show_group.' + group.group_id;
|
||||
self.scene_.set_layer_visible(group.group_id, beestat.setting(setting_key) !== false);
|
||||
self.scene_.set_layer_visible(group.group_id, !show_exterior);
|
||||
});
|
||||
|
||||
this.scene_.set_layer_visible('walls', beestat.setting('visualize.three_d.show_walls'));
|
||||
this.scene_.set_layer_visible('roof', beestat.setting('visualize.three_d.show_roof'));
|
||||
this.scene_.set_layer_visible('environment', beestat.setting('visualize.three_d.show_environment'));
|
||||
this.scene_.set_layer_visible('walls', show_exterior);
|
||||
this.scene_.set_layer_visible('roof', show_exterior);
|
||||
this.scene_.set_layer_visible('environment', show_exterior);
|
||||
|
||||
// Manage width of the scene.
|
||||
if (this.state_.width === undefined) {
|
||||
@ -772,63 +773,30 @@ beestat.component.card.three_d.prototype.decorate_toolbar_ = function(parent) {
|
||||
})
|
||||
);
|
||||
|
||||
// Toggle walls
|
||||
// Toggle exterior (walls, roof, environment) and interior (floor plan)
|
||||
tile_group.add_tile(new beestat.component.tile()
|
||||
.set_icon(beestat.setting('visualize.three_d.show_walls') === false ? 'border_none_variant' : 'wall')
|
||||
.set_title('Toggle Walls')
|
||||
.set_icon(beestat.setting('visualize.three_d.show_exterior') === false ? 'floor_plan' : 'home')
|
||||
.set_title('Toggle View')
|
||||
.set_text_color(beestat.style.color.gray.light)
|
||||
.set_background_color(beestat.style.color.bluegray.base)
|
||||
.set_background_hover_color(beestat.style.color.bluegray.light)
|
||||
.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
beestat.setting(
|
||||
'visualize.three_d.show_walls',
|
||||
!beestat.setting('visualize.three_d.show_walls')
|
||||
);
|
||||
this.set_icon(
|
||||
beestat.setting('visualize.three_d.show_walls') === false ? 'border_none_variant' : 'wall'
|
||||
);
|
||||
self.scene_.set_layer_visible('walls', beestat.setting('visualize.three_d.show_walls'));
|
||||
})
|
||||
);
|
||||
const new_value = !beestat.setting('visualize.three_d.show_exterior');
|
||||
beestat.setting('visualize.three_d.show_exterior', new_value);
|
||||
|
||||
// Toggle roof
|
||||
tile_group.add_tile(new beestat.component.tile()
|
||||
.set_icon(beestat.setting('visualize.three_d.show_roof') === false ? 'border_none_variant' : 'wall')
|
||||
.set_title('Toggle Roof')
|
||||
.set_text_color(beestat.style.color.gray.light)
|
||||
.set_background_color(beestat.style.color.bluegray.base)
|
||||
.set_background_hover_color(beestat.style.color.bluegray.light)
|
||||
.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
beestat.setting(
|
||||
'visualize.three_d.show_roof',
|
||||
!beestat.setting('visualize.three_d.show_roof')
|
||||
);
|
||||
this.set_icon(
|
||||
beestat.setting('visualize.three_d.show_roof') === false ? 'border_none_variant' : 'wall'
|
||||
);
|
||||
self.scene_.set_layer_visible('roof', beestat.setting('visualize.three_d.show_roof'));
|
||||
})
|
||||
);
|
||||
this.set_icon(new_value ? 'home' : 'floor_plan');
|
||||
|
||||
// Toggle environment
|
||||
tile_group.add_tile(new beestat.component.tile()
|
||||
.set_icon(beestat.setting('visualize.three_d.show_environment') === false ? 'border_none_variant' : 'wall')
|
||||
.set_title('Toggle Environment')
|
||||
.set_text_color(beestat.style.color.gray.light)
|
||||
.set_background_color(beestat.style.color.bluegray.base)
|
||||
.set_background_hover_color(beestat.style.color.bluegray.light)
|
||||
.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
beestat.setting(
|
||||
'visualize.three_d.show_environment',
|
||||
!beestat.setting('visualize.three_d.show_environment')
|
||||
);
|
||||
this.set_icon(
|
||||
beestat.setting('visualize.three_d.show_environment') === false ? 'border_none_variant' : 'wall'
|
||||
);
|
||||
self.scene_.set_layer_visible('environment', beestat.setting('visualize.three_d.show_environment'));
|
||||
// Toggle walls, roof, and environment
|
||||
self.scene_.set_layer_visible('walls', new_value);
|
||||
self.scene_.set_layer_visible('roof', new_value);
|
||||
self.scene_.set_layer_visible('environment', new_value);
|
||||
|
||||
// Floor plan groups are opposite of exterior (show interior when exterior is hidden)
|
||||
const floor_plan = beestat.cache.floor_plan[self.floor_plan_id_];
|
||||
Object.values(floor_plan.data.groups).forEach(function(group) {
|
||||
self.scene_.set_layer_visible(group.group_id, !new_value);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@ -18,13 +18,10 @@ beestat.component.scene.layer_visible = 0;
|
||||
beestat.component.scene.layer_hidden = 1;
|
||||
beestat.component.scene.layer_outline = 2;
|
||||
|
||||
/**
|
||||
* 3D Scene configuration constants
|
||||
*/
|
||||
beestat.component.scene.roof_pitch = 0.5; // Rise over run (0.5 = 6:12 pitch)
|
||||
beestat.component.scene.roof_overhang = 12; // Roof overhang beyond walls
|
||||
beestat.component.scene.wall_thickness = 4;
|
||||
beestat.component.scene.environment_padding = 100; // Padding around floor plan
|
||||
beestat.component.scene.environment_padding = 200; // Padding around floor plan
|
||||
beestat.component.scene.room_floor_thickness = 6;
|
||||
beestat.component.scene.room_wall_inset = 1.5;
|
||||
|
||||
@ -40,10 +37,12 @@ beestat.component.scene.default_appearance = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Brightness of the ambient light. Works with the directional lights to provide
|
||||
* a base level of light to the scene.
|
||||
* Light intensity constants
|
||||
*/
|
||||
beestat.component.scene.ambient_light_intensity = 0.25;
|
||||
beestat.component.scene.directional_light_intensity = 0.1;
|
||||
beestat.component.scene.sun_light_intensity = 0.6;
|
||||
beestat.component.scene.moon_light_intensity = 0.35;
|
||||
|
||||
/**
|
||||
* Rerender the scene by removing the primary group, then re-adding it and the
|
||||
@ -102,12 +101,11 @@ beestat.component.scene.prototype.decorate_ = function(parent) {
|
||||
parent.style('background', '#202a30');
|
||||
|
||||
this.debug_ = {
|
||||
'axes': false,
|
||||
'directional_light_top_helper': false,
|
||||
'axes': true,
|
||||
'directional_light_helpers': true,
|
||||
'sun_light_helper': true,
|
||||
'moon_light_helper': true,
|
||||
// 'grid': false,
|
||||
'watcher': false,
|
||||
'watcher': true,
|
||||
'roof_edges': false,
|
||||
'straight_skeleton': false
|
||||
};
|
||||
@ -121,8 +119,7 @@ beestat.component.scene.prototype.decorate_ = function(parent) {
|
||||
this.add_controls_(parent);
|
||||
this.add_raycaster_(parent);
|
||||
this.add_skybox_(parent);
|
||||
this.add_ambient_light_();
|
||||
this.add_directional_lights_();
|
||||
this.add_static_lights_();
|
||||
|
||||
this.add_main_group_();
|
||||
this.add_floor_plan_();
|
||||
@ -131,6 +128,7 @@ beestat.component.scene.prototype.decorate_ = function(parent) {
|
||||
self.animation_frame_ = window.requestAnimationFrame(animate);
|
||||
self.controls_.update();
|
||||
self.update_raycaster_();
|
||||
self.update_celestial_light_intensities_();
|
||||
self.renderer_.render(self.scene_, self.camera_);
|
||||
};
|
||||
animate();
|
||||
@ -237,7 +235,7 @@ beestat.component.scene.prototype.add_controls_ = function(parent) {
|
||||
this.controls_ = new THREE.OrbitControls(this.camera_, parent[0]);
|
||||
this.controls_.enableDamping = true;
|
||||
this.controls_.enablePan = false;
|
||||
this.controls_.maxDistance = 10000;
|
||||
this.controls_.maxDistance = 2000;
|
||||
this.controls_.minDistance = 400;
|
||||
this.controls_.maxPolarAngle = Math.PI / 2.5;
|
||||
};
|
||||
@ -396,44 +394,68 @@ beestat.component.scene.prototype.add_directional_lights_ = function() {
|
||||
this.directional_lights_ = [];
|
||||
|
||||
// Key light: Main light from upper front-right (strongest)
|
||||
const key_light = new THREE.DirectionalLight(0xffffff, 0.1);
|
||||
key_light.position.set(500, 800, 500);
|
||||
this.scene_.add(key_light);
|
||||
const key_light = new THREE.DirectionalLight(0xffffff, beestat.component.scene.directional_light_intensity);
|
||||
key_light.position.set(2000, 3200, 2000);
|
||||
this.static_light_group_.add(key_light);
|
||||
this.directional_lights_.push(key_light);
|
||||
|
||||
// Fill light: Softer light from upper front-left (balances key light)
|
||||
const fill_light = new THREE.DirectionalLight(0xffffff, 0.1);
|
||||
fill_light.position.set(-500, 600, 500);
|
||||
this.scene_.add(fill_light);
|
||||
const fill_light = new THREE.DirectionalLight(0xffffff, beestat.component.scene.directional_light_intensity);
|
||||
fill_light.position.set(-2000, 2400, 2000);
|
||||
this.static_light_group_.add(fill_light);
|
||||
this.directional_lights_.push(fill_light);
|
||||
|
||||
// Back light: Mild light from behind (creates rim lighting on edges)
|
||||
const back_light = new THREE.DirectionalLight(0xffffff, 0.1);
|
||||
back_light.position.set(0, 500, -500);
|
||||
this.scene_.add(back_light);
|
||||
const back_light = new THREE.DirectionalLight(0xffffff, beestat.component.scene.directional_light_intensity);
|
||||
back_light.position.set(0, 2000, -2000);
|
||||
this.static_light_group_.add(back_light);
|
||||
this.directional_lights_.push(back_light);
|
||||
|
||||
// Top light: Gentle overhead light for roof definition
|
||||
const top_light = new THREE.DirectionalLight(0xffffff, 0.1);
|
||||
top_light.position.set(0, 1000, 0);
|
||||
this.scene_.add(top_light);
|
||||
const top_light = new THREE.DirectionalLight(0xffffff, beestat.component.scene.directional_light_intensity);
|
||||
top_light.position.set(0, 4000, 0);
|
||||
this.static_light_group_.add(top_light);
|
||||
this.directional_lights_.push(top_light);
|
||||
|
||||
// Add helpers for debugging
|
||||
if (this.debug_.directional_light_helpers === true) {
|
||||
this.directional_light_helpers_ = [];
|
||||
this.directional_lights_.forEach((light) => {
|
||||
const helper = new THREE.DirectionalLightHelper(light, 100);
|
||||
this.static_light_group_.add(helper);
|
||||
this.directional_light_helpers_.push(helper);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ambient lighting so nothing is shrouded in darkness.
|
||||
* Create static lights group containing ambient and directional fill lights.
|
||||
* These lights are always on and provide base illumination.
|
||||
*/
|
||||
beestat.component.scene.prototype.add_ambient_light_ = function() {
|
||||
// Prevent re-initialization if light already exists
|
||||
if (this.ambient_light_ !== undefined) {
|
||||
beestat.component.scene.prototype.add_static_lights_ = function() {
|
||||
// Prevent re-initialization
|
||||
if (this.static_light_group_ !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize layers object if not already done
|
||||
if (this.layers_ === undefined) {
|
||||
this.layers_ = {};
|
||||
}
|
||||
|
||||
this.static_light_group_ = new THREE.Group();
|
||||
this.scene_.add(this.static_light_group_);
|
||||
this.layers_['static_lights'] = this.static_light_group_;
|
||||
|
||||
// Add ambient light
|
||||
this.ambient_light_ = new THREE.AmbientLight(
|
||||
0xffffff,
|
||||
beestat.component.scene.ambient_light_intensity
|
||||
);
|
||||
this.scene_.add(this.ambient_light_);
|
||||
this.static_light_group_.add(this.ambient_light_);
|
||||
|
||||
// Add directional fill lights
|
||||
this.add_directional_lights_();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -448,16 +470,16 @@ 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_;
|
||||
if (this.celestial_light_group_ === undefined) {
|
||||
this.celestial_light_group_ = new THREE.Group();
|
||||
this.scene_.add(this.celestial_light_group_);
|
||||
this.layers_['celestial'] = this.celestial_light_group_;
|
||||
}
|
||||
|
||||
// Sun light
|
||||
this.sun_light_ = new THREE.DirectionalLight(
|
||||
0xffffdd, // Slightly warm color for sunlight
|
||||
0.6
|
||||
beestat.component.scene.sun_light_intensity
|
||||
);
|
||||
|
||||
// Initial position (will be updated by update_celestial_lights_)
|
||||
@ -465,67 +487,67 @@ beestat.component.scene.prototype.add_celestial_lights_ = function() {
|
||||
|
||||
// Enable shadow casting
|
||||
this.sun_light_.castShadow = true;
|
||||
this.sun_light_.shadow.mapSize.set(2048, 2048);
|
||||
this.sun_light_.shadow.bias = -0.001;
|
||||
|
||||
// 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;
|
||||
// Configure shadow camera frustum
|
||||
this.sun_light_.shadow.camera.left = -1000;
|
||||
this.sun_light_.shadow.camera.right = 1000;
|
||||
this.sun_light_.shadow.camera.top = 1000;
|
||||
this.sun_light_.shadow.camera.bottom = -1000;
|
||||
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
|
||||
this.sun_light_.shadow.camera.far = 5000;
|
||||
this.sun_light_.shadow.camera.updateProjectionMatrix();
|
||||
|
||||
// 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_);
|
||||
this.celestial_light_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_);
|
||||
this.celestial_light_group_.add(this.sun_light_helper_);
|
||||
}
|
||||
|
||||
// Moon light
|
||||
this.moon_light_ = new THREE.DirectionalLight(
|
||||
0xaaccff, // Cool bluish color for moonlight
|
||||
0.15
|
||||
beestat.component.scene.moon_light_intensity
|
||||
);
|
||||
|
||||
// Initial position (will be updated by update_celestial_lights_)
|
||||
this.moon_light_.position.set(-500, 500, 500);
|
||||
|
||||
// Moon casts shadows too
|
||||
// Enable shadow casting
|
||||
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.mapSize.set(2048, 2048);
|
||||
this.moon_light_.shadow.bias = -0.001;
|
||||
|
||||
// Configure shadow camera frustum
|
||||
this.moon_light_.shadow.camera.left = -1000;
|
||||
this.moon_light_.shadow.camera.right = 1000;
|
||||
this.moon_light_.shadow.camera.top = 1000;
|
||||
this.moon_light_.shadow.camera.bottom = -1000;
|
||||
this.moon_light_.shadow.camera.near = 0.5;
|
||||
this.moon_light_.shadow.camera.far = 5000;
|
||||
this.moon_light_.shadow.camera.updateProjectionMatrix();
|
||||
|
||||
// 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_);
|
||||
this.celestial_light_group_.add(this.moon_light_);
|
||||
|
||||
if (this.debug_.moon_light_helper === true) {
|
||||
this.moon_light_helper_ = new THREE.DirectionalLightHelper(
|
||||
this.moon_light_,
|
||||
100
|
||||
);
|
||||
this.celestial_group_.add(this.moon_light_helper_);
|
||||
this.celestial_light_group_.add(this.moon_light_helper_);
|
||||
}
|
||||
};
|
||||
|
||||
@ -536,88 +558,86 @@ beestat.component.scene.prototype.add_celestial_lights_ = function() {
|
||||
* @param {moment} date The date/time to calculate positions for
|
||||
* @param {number} latitude Location latitude
|
||||
* @param {number} longitude Location longitude
|
||||
*
|
||||
* @link https://www.earthspacelab.com/app/solar-time/
|
||||
*/
|
||||
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 distance = 2000;
|
||||
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;
|
||||
// Sun
|
||||
const sun_pos = SunCalc.getPosition(js_date, latitude, longitude);
|
||||
this.sun_light_.position.set(
|
||||
distance * Math.cos(sun_pos.altitude) * Math.sin(sun_pos.azimuth), // East-West
|
||||
distance * Math.sin(sun_pos.altitude), // Up-Down (altitude)
|
||||
-distance * Math.cos(sun_pos.altitude) * Math.cos(sun_pos.azimuth) // North-South
|
||||
);
|
||||
|
||||
// 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);
|
||||
// Calculate target intensity for smooth transitions
|
||||
this.target_sun_intensity_ = sun_pos.altitude < 0
|
||||
? Math.max(0, beestat.component.scene.sun_light_intensity * (1 + sun_pos.altitude / (Math.PI / 6)))
|
||||
: beestat.component.scene.sun_light_intensity;
|
||||
|
||||
this.sun_light_.position.set(sun_x, sun_y, sun_z);
|
||||
// Moon
|
||||
const moon_pos = SunCalc.getMoonPosition(js_date, latitude, longitude);
|
||||
const moon_fraction = SunCalc.getMoonIllumination(js_date).fraction;
|
||||
this.moon_light_.position.set(
|
||||
distance * Math.cos(moon_pos.altitude) * Math.sin(moon_pos.azimuth), // East-West
|
||||
distance * Math.sin(moon_pos.altitude), // Up-Down (altitude)
|
||||
-distance * Math.cos(moon_pos.altitude) * Math.cos(moon_pos.azimuth) // North-South
|
||||
);
|
||||
const moon_intensity = beestat.component.scene.moon_light_intensity * moon_fraction;
|
||||
|
||||
// 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)));
|
||||
// Calculate target intensity for smooth transitions
|
||||
// Moon is only visible when sun is below horizon
|
||||
if (sun_pos.altitude >= 0) {
|
||||
this.target_moon_intensity_ = 0;
|
||||
} else {
|
||||
this.target_moon_intensity_ = moon_pos.altitude < 0
|
||||
? Math.max(0, moon_intensity * (1 + moon_pos.altitude / (Math.PI / 6)))
|
||||
: moon_intensity;
|
||||
}
|
||||
|
||||
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
|
||||
// Update helpers
|
||||
if (this.debug_.sun_light_helper) {
|
||||
this.sun_light_.updateMatrixWorld();
|
||||
this.sun_light_.target.updateMatrixWorld();
|
||||
this.sun_light_helper_.update();
|
||||
}
|
||||
|
||||
if (this.debug_.moon_light_helper === true) {
|
||||
if (this.debug_.moon_light_helper) {
|
||||
this.moon_light_.updateMatrixWorld();
|
||||
this.moon_light_.target.updateMatrixWorld();
|
||||
this.moon_light_helper_.update();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Smoothly interpolate celestial light intensities towards their targets.
|
||||
* Called every frame to create smooth transitions instead of instant jumps.
|
||||
*/
|
||||
beestat.component.scene.prototype.update_celestial_light_intensities_ = function() {
|
||||
if (this.sun_light_ === undefined || this.moon_light_ === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize current intensities if not set
|
||||
if (this.target_sun_intensity_ === undefined) {
|
||||
this.target_sun_intensity_ = 0;
|
||||
}
|
||||
if (this.target_moon_intensity_ === undefined) {
|
||||
this.target_moon_intensity_ = 0;
|
||||
}
|
||||
|
||||
// Lerp factor - lower = smoother but slower, higher = faster but jumpier
|
||||
const lerp_factor = 0.05;
|
||||
|
||||
// Lerp sun intensity
|
||||
this.sun_light_.intensity += (this.target_sun_intensity_ - this.sun_light_.intensity) * lerp_factor;
|
||||
|
||||
// Lerp moon intensity
|
||||
this.moon_light_.intensity += (this.target_moon_intensity_ - this.moon_light_.intensity) * lerp_factor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the scene based on the currently set date.
|
||||
*/
|
||||
@ -774,6 +794,8 @@ beestat.component.scene.prototype.update_ = function() {
|
||||
|
||||
// Update debug watcher
|
||||
if (this.debug_.watcher === true) {
|
||||
this.debug_info_.sun_light_intensity = this.sun_light_ !== undefined ? this.sun_light_.intensity.toFixed(3) : 'N/A';
|
||||
this.debug_info_.moon_light_intensity = this.moon_light_ !== undefined ? this.moon_light_.intensity.toFixed(3) : 'N/A';
|
||||
this.update_debug_();
|
||||
}
|
||||
};
|
||||
@ -1126,7 +1148,7 @@ beestat.component.scene.prototype.update_debug_ = function() {
|
||||
beestat.component.scene.prototype.add_main_group_ = function() {
|
||||
const bounding_box = beestat.floor_plan.get_bounding_box(this.floor_plan_id_);
|
||||
|
||||
// Main group handles rotation and orientation
|
||||
// Main group handles rotation, orientation, and centering
|
||||
this.main_group_ = new THREE.Group();
|
||||
|
||||
// Apply X rotation to orient the floor plan
|
||||
@ -1136,12 +1158,10 @@ beestat.component.scene.prototype.add_main_group_ = function() {
|
||||
const rotation_degrees = this.get_appearance_value_('rotation');
|
||||
this.main_group_.rotation.z = (rotation_degrees * Math.PI) / 180;
|
||||
|
||||
// Content group is offset to center the geometry at the rotation point
|
||||
this.content_group_ = new THREE.Group();
|
||||
this.content_group_.translateX((bounding_box.right + bounding_box.left) / -2);
|
||||
this.content_group_.translateZ((bounding_box.bottom + bounding_box.top) / -2);
|
||||
// Apply translation to center the geometry at the rotation point
|
||||
this.main_group_.translateX((bounding_box.right + bounding_box.left) / -2);
|
||||
this.main_group_.translateZ((bounding_box.bottom + bounding_box.top) / -2);
|
||||
|
||||
this.main_group_.add(this.content_group_);
|
||||
this.scene_.add(this.main_group_);
|
||||
};
|
||||
|
||||
@ -1152,15 +1172,23 @@ beestat.component.scene.prototype.add_floor_plan_ = function() {
|
||||
const self = this;
|
||||
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
|
||||
|
||||
this.layers_ = {};
|
||||
// Initialize layers if not already done
|
||||
if (this.layers_ === undefined) {
|
||||
this.layers_ = {};
|
||||
}
|
||||
|
||||
// Create floor plan group for walls, rooms, and roofs
|
||||
this.floor_plan_group_ = new THREE.Group();
|
||||
this.main_group_.add(this.floor_plan_group_);
|
||||
this.layers_['floor_plan'] = this.floor_plan_group_;
|
||||
|
||||
const walls_layer = new THREE.Group();
|
||||
self.content_group_.add(walls_layer);
|
||||
self.floor_plan_group_.add(walls_layer);
|
||||
self.layers_['walls'] = walls_layer;
|
||||
|
||||
floor_plan.data.groups.forEach(function(group) {
|
||||
const layer = new THREE.Group();
|
||||
self.content_group_.add(layer);
|
||||
self.floor_plan_group_.add(layer);
|
||||
self.layers_[group.group_id] = layer;
|
||||
group.rooms.forEach(function(room) {
|
||||
self.add_room_(layer, group, room);
|
||||
@ -1172,7 +1200,7 @@ beestat.component.scene.prototype.add_floor_plan_ = function() {
|
||||
this.add_roofs_();
|
||||
|
||||
if (this.debug_.roof_edges) {
|
||||
this.add_roof_outlines_();
|
||||
this.add_roof_outline_debug_();
|
||||
}
|
||||
|
||||
if (this.debug_.straight_skeleton) {
|
||||
@ -1358,18 +1386,12 @@ beestat.component.scene.prototype.add_roofs_ = function() {
|
||||
*/
|
||||
beestat.component.scene.prototype.add_hip_roofs_ = function() {
|
||||
const self = this;
|
||||
|
||||
if (typeof SkeletonBuilder === 'undefined') {
|
||||
console.warn('SkeletonBuilder not yet loaded - skipping roof generation');
|
||||
return;
|
||||
}
|
||||
|
||||
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
|
||||
const exposed_areas = this.compute_exposed_ceiling_areas_(floor_plan);
|
||||
|
||||
// Create layer for roofs
|
||||
const roofs_layer = new THREE.Group();
|
||||
this.content_group_.add(roofs_layer);
|
||||
this.floor_plan_group_.add(roofs_layer);
|
||||
this.layers_['roof'] = roofs_layer;
|
||||
|
||||
const roof_pitch = beestat.component.scene.roof_pitch;
|
||||
@ -1544,7 +1566,7 @@ beestat.component.scene.prototype.add_flat_roofs_ = function() {
|
||||
|
||||
// Create layer for roofs
|
||||
const roofs_layer = new THREE.Group();
|
||||
this.content_group_.add(roofs_layer);
|
||||
this.floor_plan_group_.add(roofs_layer);
|
||||
this.layers_['roof'] = roofs_layer;
|
||||
|
||||
// Process each exposed area
|
||||
@ -1624,14 +1646,14 @@ beestat.component.scene.prototype.add_flat_roofs_ = function() {
|
||||
/**
|
||||
* Add red outline visualization for exposed ceiling areas (future roof locations).
|
||||
*/
|
||||
beestat.component.scene.prototype.add_roof_outlines_ = function() {
|
||||
beestat.component.scene.prototype.add_roof_outline_debug_ = function() {
|
||||
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
|
||||
|
||||
const exposed_areas = this.compute_exposed_ceiling_areas_(floor_plan);
|
||||
|
||||
// Create layer for roof outlines
|
||||
const roof_outlines_layer = new THREE.Group();
|
||||
this.content_group_.add(roof_outlines_layer);
|
||||
this.floor_plan_group_.add(roof_outlines_layer);
|
||||
this.layers_['roof_outlines'] = roof_outlines_layer;
|
||||
|
||||
// Render each exposed area as red outline
|
||||
@ -1667,17 +1689,12 @@ beestat.component.scene.prototype.add_roof_outlines_ = function() {
|
||||
* Visualize the straight skeleton for each roof polygon with debug lines.
|
||||
*/
|
||||
beestat.component.scene.prototype.add_roof_skeleton_debug_ = function() {
|
||||
if (typeof SkeletonBuilder === 'undefined') {
|
||||
console.warn('SkeletonBuilder not yet loaded - skipping skeleton debug visualization');
|
||||
return;
|
||||
}
|
||||
|
||||
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
|
||||
const exposed_areas = this.compute_exposed_ceiling_areas_(floor_plan);
|
||||
|
||||
// Create layer for skeleton debug lines
|
||||
const skeleton_debug_layer = new THREE.Group();
|
||||
this.content_group_.add(skeleton_debug_layer);
|
||||
this.floor_plan_group_.add(skeleton_debug_layer);
|
||||
this.layers_['roof_skeleton_debug'] = skeleton_debug_layer;
|
||||
|
||||
let total_polygons = 0;
|
||||
@ -1718,7 +1735,6 @@ beestat.component.scene.prototype.add_roof_skeleton_debug_ = function() {
|
||||
const result = SkeletonBuilder.buildFromPolygon(coordinates);
|
||||
|
||||
if (!result) {
|
||||
console.warn('SkeletonBuilder returned null for polygon:', simple_polygon);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1760,47 +1776,6 @@ beestat.component.scene.prototype.add_roof_skeleton_debug_ = function() {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Test the SkeletonBuilder library with a simple square polygon.
|
||||
*/
|
||||
beestat.component.scene.prototype.test_skeleton_builder_ = function() {
|
||||
if (typeof SkeletonBuilder === 'undefined') {
|
||||
console.warn('SkeletonBuilder not yet loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Testing SkeletonBuilder...');
|
||||
|
||||
try {
|
||||
// Correct format: number[][][] = array of rings, each ring is array of [x,y] points
|
||||
// First ring is outer boundary, must be closed (first point repeated at end)
|
||||
const square = [
|
||||
[ // Outer ring
|
||||
[0, 0],
|
||||
[100, 0],
|
||||
[100, 100],
|
||||
[0, 100],
|
||||
[0, 0] // Close the ring by repeating first point
|
||||
]
|
||||
// Additional rings here would be holes
|
||||
];
|
||||
|
||||
console.log('Input polygon (correct format):', square);
|
||||
const result = SkeletonBuilder.buildFromPolygon(square);
|
||||
|
||||
if (result) {
|
||||
console.log('✓ SkeletonBuilder test passed!');
|
||||
console.log(' Vertices:', result.vertices.length);
|
||||
console.log(' Polygons:', result.polygons.length);
|
||||
console.log(' Result:', result);
|
||||
} else {
|
||||
console.error('✗ SkeletonBuilder test failed - returned null');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('✗ SkeletonBuilder test threw error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add environment layers (grass and earth strata) below the house.
|
||||
*/
|
||||
@ -1827,15 +1802,16 @@ beestat.component.scene.prototype.add_environment_ = function() {
|
||||
const padding = beestat.component.scene.environment_padding;
|
||||
const ground_color = this.get_appearance_value_('ground_color');
|
||||
const strata = [
|
||||
{'color': ground_color, 'thickness': 10, 'roughness': 0.95}, // User-selected ground
|
||||
{'color': 0x5a4a3a, 'thickness': 30, 'roughness': 0.85}, // Medium brown dirt
|
||||
{'color': 0x8b5e3c, 'thickness': 40, 'roughness': 0.85}, // Light brown dirt
|
||||
{'color': 0x6e6e6e, 'thickness': 40, 'roughness': 0.85} // Gray bedrock
|
||||
{'color': ground_color, 'thickness': 10, 'roughness': 0.95},
|
||||
{'color': 0x4a3f35, 'thickness': 60, 'roughness': 0.85},
|
||||
{'color': 0x6b5d4f, 'thickness': 60, 'roughness': 0.85},
|
||||
{'color': 0x4a3f35, 'thickness': 60, 'roughness': 0.85}
|
||||
];
|
||||
|
||||
const environment_layer = new THREE.Group();
|
||||
this.content_group_.add(environment_layer);
|
||||
this.layers_['environment'] = environment_layer;
|
||||
// Create environment group for ground strata
|
||||
this.environment_group_ = new THREE.Group();
|
||||
this.main_group_.add(this.environment_group_);
|
||||
this.layers_['environment'] = this.environment_group_;
|
||||
|
||||
strata.forEach(function(stratum) {
|
||||
const geometry = new THREE.BoxGeometry(
|
||||
@ -1856,11 +1832,11 @@ beestat.component.scene.prototype.add_environment_ = function() {
|
||||
mesh.userData.is_environment = true;
|
||||
mesh.receiveShadow = true;
|
||||
|
||||
environment_layer.add(mesh);
|
||||
this.environment_group_.add(mesh);
|
||||
current_z += stratum.thickness;
|
||||
}, this);
|
||||
|
||||
// Add celestial lights (sun and moon) to the environment layer
|
||||
// Add celestial lights (sun and moon) - toggled with environment visibility
|
||||
this.add_celestial_lights_();
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user