1
0
mirror of https://github.com/beestat/app.git synced 2026-02-26 05:00:21 -05:00
This commit is contained in:
Jon Ziebell 2026-02-22 01:27:34 -05:00
parent f7d0e01dd0
commit 410f775cb1
6 changed files with 1092 additions and 124 deletions

View File

@ -458,8 +458,9 @@ input[type=range]::-moz-range-thumb {
.icon.cloud_question:before { content: "\F0A39"; }
.icon.code_tags:before { content: "\F0174"; }
.icon.cog:before { content: "\F0493"; }
.icon.content_copy:before { content: "\F018F"; }
.icon.credit_card_lock:before { content: "\F18E7"; }
.icon.content_copy:before { content: "\F018F"; }
.icon.creation:before { content: "\F0674"; }
.icon.credit_card_lock:before { content: "\F18E7"; }
.icon.credit_card_settings:before { content: "\F0FF5"; }
.icon.currency_usd:before { content: "\F01C1"; }
.icon.delete:before { content: "\F01B4"; }

File diff suppressed because it is too large Load Diff

View File

@ -184,7 +184,7 @@ beestat.component.scene.default_appearance = {
'roof_style': 'hip',
'siding_color': '#889aaa',
'ground_color': '#4a7c3f',
'weather': 'none'
'weather': 'sunny'
};
/**
* Snow cover tint used to blend roof/ground surfaces during snowfall.
@ -295,7 +295,7 @@ beestat.component.scene.default_settings = {
'rain_density': 1,
'snow_density': 1,
'lightning_frequency': 0,
'wind_speed': 1,
'wind_speed': 0.4,
'wind_direction': 0,
'tree_wobble': true,
'tree_enabled': true,
@ -617,10 +617,20 @@ beestat.component.scene.prototype.set_scene_settings = function(scene_settings,
return this;
}
const previous_lightning_frequency = Number(this.get_scene_setting_('lightning_frequency') || 0);
const previous_user_light_cast_shadows = this.get_scene_setting_('light_user_cast_shadows') === true;
if (this.scene_settings_ === undefined) {
this.scene_settings_ = {};
}
Object.assign(this.scene_settings_, scene_settings);
const current_lightning_frequency = Number(this.get_scene_setting_('lightning_frequency') || 0);
const current_user_light_cast_shadows = this.get_scene_setting_('light_user_cast_shadows') === true;
const lightning_frequency_changed = Math.abs(
current_lightning_frequency - previous_lightning_frequency
) > 0.0001;
const user_light_cast_shadows_changed =
current_user_light_cast_shadows !== previous_user_light_cast_shadows;
const rerender = options !== undefined && options.rerender === true;
if (this.rendered_ === true) {
@ -630,6 +640,21 @@ beestat.component.scene.prototype.set_scene_settings = function(scene_settings,
this.update_weather_targets_();
this.update_tree_foliage_season_();
this.update_weather_();
if (
lightning_frequency_changed === true &&
typeof this.sync_lightning_schedule_for_frequency_change_ === 'function'
) {
this.sync_lightning_schedule_for_frequency_change_(
previous_lightning_frequency,
current_lightning_frequency
);
}
if (
user_light_cast_shadows_changed === true &&
typeof this.update_user_light_shadow_settings_ === 'function'
) {
this.update_user_light_shadow_settings_();
}
}
}

View File

@ -483,13 +483,19 @@ beestat.component.scene.prototype.update_celestial_lights_ = function(date, lati
const cloud_dimming = this.get_cloud_dimming_factor_();
// Calculate target intensity for smooth transitions.
// Keep most of the falloff near the horizon so direct highlights don't look
// "full sun" once the sun disk visually fades.
// Keep the transition tight around the horizon so sunrise "pops in" with
// the same quick behavior as sunset "drops out".
const sun_transition_start_altitude = -0.015;
const sun_transition_end_altitude = 0.075;
const sun_horizon_visibility = Math.max(
0,
Math.min(1, (sun_pos.altitude + 0.06) / 0.18)
Math.min(
1,
(sun_pos.altitude - sun_transition_start_altitude) /
Math.max(0.0001, sun_transition_end_altitude - sun_transition_start_altitude)
)
);
const sun_intensity_factor = Math.pow(sun_horizon_visibility, 1.7);
const sun_intensity_factor = Math.pow(sun_horizon_visibility, 2.4);
this.target_sun_intensity_ =
beestat.component.scene.sun_light_intensity * sun_intensity_factor;
this.target_sun_intensity_ *= cloud_dimming;
@ -872,6 +878,38 @@ beestat.component.scene.prototype.add_light_sources_ = function(layer, group) {
};
/**
* Apply the current user-light shadow setting to existing user lights.
*/
beestat.component.scene.prototype.update_user_light_shadow_settings_ = function() {
if (Array.isArray(this.light_sources_) !== true) {
return;
}
const user_light_cast_shadows = this.get_scene_setting_('light_user_cast_shadows') === true;
this.light_sources_.forEach(function(light) {
if (light === undefined || light === null || light.isPointLight !== true) {
return;
}
light.castShadow = user_light_cast_shadows;
if (user_light_cast_shadows === true) {
light.shadow.mapSize.width = 512;
light.shadow.mapSize.height = 512;
light.shadow.bias = -0.0012;
light.shadow.normalBias = 0.025;
light.shadow.radius = 2;
light.shadow.camera.near = 1;
light.shadow.camera.far = 240;
}
});
if (this.renderer_ !== undefined && this.renderer_.shadowMap !== undefined) {
this.renderer_.shadowMap.needsUpdate = true;
}
};
/**
* Add warm interior point lights, one per room. Lights are invisible and their
* intensity is animated based on night/day state.

View File

@ -86,11 +86,11 @@ beestat.component.scene.prototype.update_tree_wind_ = function() {
return;
}
const wind_speed = Math.max(0, Math.min(5, Number(this.get_scene_setting_('wind_speed') || 0)));
const wind_speed = Math.max(0, Math.min(2, Number(this.get_scene_setting_('wind_speed') || 0)));
const wind_direction = Math.max(0, Math.min(360, Number(this.get_scene_setting_('wind_direction') || 0)));
const tree_wobble_enabled = this.get_scene_setting_('tree_wobble') !== false;
// Keep overall tree effect lower than prior tuning while preserving responsiveness.
const wind_strength = wind_speed * 0.5;
const wind_strength = wind_speed * 1.25;
const time_seconds = window.performance.now() / 1000;
const wind_radians = THREE.MathUtils.degToRad(wind_direction);
const wind_direction_x = Math.cos(wind_radians);

View File

@ -6,7 +6,7 @@
/**
* Set weather on the floor-plan appearance.
*
* @param {string} weather none|sunny|cloudy|rain|snow|storm
* @param {string} weather
*
* @return {beestat.component.scene}
*/
@ -17,17 +17,160 @@ beestat.component.scene.prototype.set_weather = function(weather) {
}
floor_plan.data.appearance.weather = weather;
// Backward-compatible weather mode support by translating to density values.
// Translate weather presets to scene density values.
let weather_settings;
switch (weather) {
case 'storm':
case 'few_clouds':
weather_settings = {
'cloud_density': 0.18,
'cloud_darkness': 0,
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 0.45
};
break;
case 'partly_cloudy':
weather_settings = {
'cloud_density': 0.3,
'cloud_darkness': 0.1,
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 0.55
};
break;
case 'mostly_cloudy':
weather_settings = {
'cloud_density': 0.75,
'cloud_darkness': 0.45,
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 0.7
};
break;
case 'drizzle':
weather_settings = {
'cloud_density': 0.9,
'cloud_darkness': 0.7,
'rain_density': 0.35,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 0.75
};
break;
case 'showers':
weather_settings = {
'cloud_density': 1.2,
'cloud_darkness': 1.1,
'rain_density': 1.2,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 1
};
break;
case 'freezing_rain':
weather_settings = {
'cloud_density': 1.2,
'cloud_darkness': 1.2,
'rain_density': 1.1,
'snow_density': 0.2,
'lightning_frequency': 0,
'wind_speed': 1
};
break;
case 'hail':
case 'pellets':
weather_settings = {
'cloud_density': 1.25,
'cloud_darkness': 1.25,
'rain_density': 1.2,
'snow_density': 0.15,
'lightning_frequency': 0.1,
'wind_speed': 1.1
};
break;
case 'flurries':
weather_settings = {
'cloud_density': 0.85,
'cloud_darkness': 0.7,
'rain_density': 0,
'snow_density': 0.55,
'lightning_frequency': 0,
'wind_speed': 0.65
};
break;
case 'freezing_snow':
weather_settings = {
'cloud_density': 1.1,
'cloud_darkness': 1,
'rain_density': 0.05,
'snow_density': 1.1,
'lightning_frequency': 0,
'wind_speed': 0.7
};
break;
case 'blizzard':
weather_settings = {
'cloud_density': 1.4,
'cloud_darkness': 1.5,
'rain_density': 0.1,
'snow_density': 1.8,
'lightning_frequency': 0,
'wind_speed': 1.6
};
break;
case 'windy':
weather_settings = {
'cloud_density': 0.55,
'cloud_darkness': 0.3,
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 1.5
};
break;
case 'tornado':
weather_settings = {
'cloud_density': 1.35,
'cloud_darkness': 1.6,
'rain_density': 1.3,
'snow_density': 0,
'lightning_frequency': 0.5,
'wind_speed': 2
};
break;
case 'fog':
weather_settings = {
'cloud_density': 0.6,
'cloud_darkness': 0.2,
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 0.25
};
break;
case 'haze':
case 'smoke':
case 'dust':
weather_settings = {
'cloud_density': 0.45,
'cloud_darkness': 0.35,
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 0.6
};
break;
case 'thunderstorm':
weather_settings = {
'cloud_density': 1.5,
'cloud_darkness': 2,
'rain_density': 2,
'snow_density': 0,
'lightning_frequency': 1,
'wind_speed': 4
'wind_speed': 1.6
};
break;
case 'snow':
@ -37,7 +180,7 @@ beestat.component.scene.prototype.set_weather = function(weather) {
'rain_density': 0,
'snow_density': 1,
'lightning_frequency': 0,
'wind_speed': 1
'wind_speed': 0.4
};
break;
case 'rain':
@ -47,21 +190,20 @@ beestat.component.scene.prototype.set_weather = function(weather) {
'rain_density': 1,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 2
'wind_speed': 0.8
};
break;
case 'cloudy':
case 'overcast':
weather_settings = {
'cloud_density': 0.5,
'cloud_darkness': 0.4,
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 2
'wind_speed': 0.8
};
break;
case 'sunny':
case 'none':
default:
weather_settings = {
'cloud_density': 0.03,
@ -69,7 +211,7 @@ beestat.component.scene.prototype.set_weather = function(weather) {
'rain_density': 0,
'snow_density': 0,
'lightning_frequency': 0,
'wind_speed': 1
'wind_speed': 0.4
};
break;
}
@ -423,13 +565,13 @@ beestat.component.scene.prototype.update_precipitation_system_ = function(
const span_y = bounds.max_y - bounds.min_y;
const span_z = bounds.max_z - bounds.min_z;
const positions = precipitation.points.geometry.attributes.position.array;
const clamped_wind_speed = Math.max(0, Math.min(5, Number(wind_speed || 0)));
const clamped_wind_speed = Math.max(0, Math.min(2, Number(wind_speed || 0)));
const clamped_wind_direction = Math.max(0, Math.min(360, Number(wind_direction || 0)));
const wind_direction_radians = THREE.MathUtils.degToRad(clamped_wind_direction);
const wind_x = Math.cos(wind_direction_radians);
const wind_y = Math.sin(wind_direction_radians);
const max_wind_angle = Number(precipitation.max_wind_angle || 0);
const wind_angle = (clamped_wind_speed / 5) * max_wind_angle;
const wind_angle = (clamped_wind_speed / 2) * max_wind_angle;
const wind_angle_radians = THREE.MathUtils.degToRad(wind_angle);
const vertical_scale = Math.cos(wind_angle_radians);
const horizontal_scale = Math.sin(wind_angle_radians);
@ -437,7 +579,7 @@ beestat.component.scene.prototype.update_precipitation_system_ = function(
1,
Number(precipitation.max_wind_speed_scale || 2)
);
const wind_speed_scale = 1 + ((clamped_wind_speed / 5) * (max_wind_speed_scale - 1));
const wind_speed_scale = 1 + ((clamped_wind_speed / 2) * (max_wind_speed_scale - 1));
const wind_motion_multiplier = Math.max(0, Number(precipitation.wind_motion_multiplier || 1));
const direction_velocity_x = horizontal_scale * wind_x;
const direction_velocity_y = horizontal_scale * wind_y;
@ -507,6 +649,56 @@ beestat.component.scene.prototype.schedule_next_lightning_cluster_ = function(no
};
/**
* Sync lightning timing state after lightning frequency changes without rerender.
*
* @param {number} previous_frequency
* @param {number} current_frequency
*/
beestat.component.scene.prototype.sync_lightning_schedule_for_frequency_change_ = function(
previous_frequency,
current_frequency
) {
const previous = Math.max(0, Math.min(2, Number(previous_frequency || 0)));
const current = Math.max(0, Math.min(2, Number(current_frequency || 0)));
const now_ms = window.performance.now();
if (current <= 0) {
if (this.lightning_flash_light_ !== undefined) {
this.lightning_flash_light_.intensity = 0;
}
this.lightning_flash_remaining_s_ = 0;
this.lightning_next_strike_ms_ = undefined;
this.lightning_next_pulse_ms_ = undefined;
this.lightning_cluster_pulses_remaining_ = 0;
this.lightning_cluster_anchor_ = null;
return;
}
// Turning lightning on should feel immediate even though strike cadence is stochastic.
if (previous <= 0 && current > 0) {
this.lightning_next_strike_ms_ = now_ms + (120 + (Math.random() * 420));
this.lightning_next_pulse_ms_ = undefined;
this.lightning_cluster_pulses_remaining_ = 0;
this.lightning_cluster_anchor_ = null;
this.lightning_cluster_frequency_ = current;
return;
}
// For active lightning, apply new frequency promptly rather than waiting for old cadence.
if (Math.abs(current - previous) > 0.0001) {
this.lightning_cluster_frequency_ = current;
this.schedule_next_lightning_cluster_(now_ms, current);
if (this.lightning_next_strike_ms_ !== undefined) {
this.lightning_next_strike_ms_ = Math.min(
this.lightning_next_strike_ms_,
now_ms + (350 + (Math.random() * 650))
);
}
}
};
/**
* Trigger one lightning pulse.
*
@ -864,7 +1056,7 @@ beestat.component.scene.prototype.update_weather_ = function() {
if (delta_seconds <= 0) {
return;
}
const wind_speed = Math.max(0, Math.min(5, Number(this.get_scene_setting_('wind_speed') || 0)));
const wind_speed = Math.max(0, Math.min(2, Number(this.get_scene_setting_('wind_speed') || 0)));
const wind_direction = Math.max(0, Math.min(360, Number(this.get_scene_setting_('wind_direction') || 0)));
if (this.weather_profile_target_ === undefined) {