mirror of
https://github.com/beestat/app.git
synced 2026-04-12 20:22:14 -04:00
Debug
This commit is contained in:
parent
80dce469c7
commit
855c1ec794
@ -40,6 +40,9 @@ beestat.component.card.three_d = function() {
|
||||
change_function
|
||||
);
|
||||
|
||||
this.scene_settings_menu_open_ = false;
|
||||
this.scene_settings_values_ = undefined;
|
||||
|
||||
beestat.component.card.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.card.three_d, beestat.component.card);
|
||||
@ -117,6 +120,7 @@ beestat.component.card.three_d.prototype.decorate_contents_ = function(parent) {
|
||||
});
|
||||
parent.appendChild(fps_container);
|
||||
this.decorate_fps_ticker_(fps_container);
|
||||
this.update_fps_visibility_();
|
||||
|
||||
// Toolbar
|
||||
const toolbar_container = document.createElement('div');
|
||||
@ -129,6 +133,19 @@ beestat.component.card.three_d.prototype.decorate_contents_ = function(parent) {
|
||||
parent.appendChild(toolbar_container);
|
||||
this.decorate_toolbar_(toolbar_container);
|
||||
|
||||
// Scene settings panel
|
||||
const scene_settings_container = document.createElement('div');
|
||||
Object.assign(scene_settings_container.style, {
|
||||
'position': 'absolute',
|
||||
'top': `${beestat.style.size.gutter + 72}px`,
|
||||
'right': `${beestat.style.size.gutter}px`,
|
||||
'min-width': '220px',
|
||||
'max-width': '250px',
|
||||
'z-index': 2
|
||||
});
|
||||
parent.appendChild(scene_settings_container);
|
||||
this.decorate_scene_settings_panel_(scene_settings_container);
|
||||
|
||||
// Environment date slider (shown only in environment view)
|
||||
const environment_date_container = document.createElement('div');
|
||||
Object.assign(environment_date_container.style, {
|
||||
@ -374,11 +391,14 @@ beestat.component.card.three_d.prototype.decorate_drawing_pane_ = function(paren
|
||||
if (this.scene_ !== undefined) {
|
||||
this.scene_.dispose();
|
||||
}
|
||||
this.ensure_scene_settings_values_();
|
||||
this.scene_ = new beestat.component.scene(
|
||||
beestat.setting('visualize.floor_plan_id'),
|
||||
this.get_data_()
|
||||
);
|
||||
this.apply_weather_setting_to_scene_();
|
||||
this.scene_.set_scene_settings(this.scene_settings_values_, {
|
||||
'rerender': false
|
||||
});
|
||||
|
||||
this.scene_.addEventListener('change_active_room', function() {
|
||||
self.update_hud_();
|
||||
@ -515,23 +535,39 @@ beestat.component.card.three_d.prototype.get_weather_mode_ = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Map UI weather mode to scene weather effect.
|
||||
* Map weather mode to weather property values.
|
||||
*
|
||||
* @param {string} weather_mode
|
||||
*
|
||||
* @return {string} none|cloudy|rain|snow
|
||||
* @return {{cloud_density: number, rain_density: number, snow_density: number}}
|
||||
*/
|
||||
beestat.component.card.three_d.prototype.get_weather_from_mode_ = function(weather_mode) {
|
||||
beestat.component.card.three_d.prototype.get_weather_settings_from_mode_ = function(weather_mode) {
|
||||
switch (weather_mode) {
|
||||
case 'cloudy':
|
||||
return 'cloudy';
|
||||
return {
|
||||
'cloud_density': 1,
|
||||
'rain_density': 0,
|
||||
'snow_density': 0
|
||||
};
|
||||
case 'raining':
|
||||
return 'rain';
|
||||
return {
|
||||
'cloud_density': 1,
|
||||
'rain_density': 1,
|
||||
'snow_density': 0
|
||||
};
|
||||
case 'snowing':
|
||||
return 'snow';
|
||||
return {
|
||||
'cloud_density': 1,
|
||||
'rain_density': 0,
|
||||
'snow_density': 1
|
||||
};
|
||||
case 'sunny':
|
||||
default:
|
||||
return 'none';
|
||||
return {
|
||||
'cloud_density': 0,
|
||||
'rain_density': 0,
|
||||
'snow_density': 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@ -543,8 +579,220 @@ beestat.component.card.three_d.prototype.apply_weather_setting_to_scene_ = funct
|
||||
return;
|
||||
}
|
||||
|
||||
const weather = this.get_weather_from_mode_(this.get_weather_mode_());
|
||||
this.scene_.set_weather(weather);
|
||||
this.ensure_scene_settings_values_();
|
||||
const weather_settings = this.get_weather_settings_from_mode_(this.get_weather_mode_());
|
||||
Object.assign(this.scene_settings_values_, weather_settings);
|
||||
this.scene_.set_scene_settings(weather_settings, {
|
||||
'rerender': false
|
||||
});
|
||||
|
||||
if (this.scene_settings_container_ !== undefined) {
|
||||
this.decorate_scene_settings_panel_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get whether or not this user can access scene settings controls.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
beestat.component.card.three_d.prototype.can_access_scene_settings_ = function() {
|
||||
return (
|
||||
beestat.user.get() !== undefined &&
|
||||
Number(beestat.user.get().user_id) === 1
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure local scene settings state exists.
|
||||
*/
|
||||
beestat.component.card.three_d.prototype.ensure_scene_settings_values_ = function() {
|
||||
if (this.scene_settings_values_ !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.scene_settings_values_ = Object.assign({}, beestat.component.scene.default_settings);
|
||||
if (
|
||||
this.scene_settings_values_.tree_branch_depth === undefined &&
|
||||
this.scene_settings_values_.tree_branch_recursion_depth !== undefined
|
||||
) {
|
||||
this.scene_settings_values_.tree_branch_depth = this.scene_settings_values_.tree_branch_recursion_depth;
|
||||
}
|
||||
if (
|
||||
Number.isFinite(Number(this.scene_settings_values_.random_seed)) !== true ||
|
||||
Number(this.scene_settings_values_.random_seed) <= 0
|
||||
) {
|
||||
this.scene_settings_values_.random_seed = Math.floor(Math.random() * 2147483646) + 1;
|
||||
}
|
||||
Object.assign(
|
||||
this.scene_settings_values_,
|
||||
this.get_weather_settings_from_mode_(this.get_weather_mode_())
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set one scene setting from the settings panel and force rerender.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
*/
|
||||
beestat.component.card.three_d.prototype.set_scene_setting_from_panel_ = function(key, value) {
|
||||
this.ensure_scene_settings_values_();
|
||||
this.scene_settings_values_[key] = value;
|
||||
|
||||
if (this.scene_ !== undefined) {
|
||||
this.scene_.set_scene_settings({
|
||||
[key]: value
|
||||
}, {
|
||||
'rerender': true,
|
||||
'source': 'panel'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate scene settings panel.
|
||||
*
|
||||
* @param {HTMLDivElement=} parent
|
||||
*/
|
||||
beestat.component.card.three_d.prototype.decorate_scene_settings_panel_ = function(parent) {
|
||||
if (parent !== undefined) {
|
||||
this.scene_settings_container_ = parent;
|
||||
}
|
||||
if (this.scene_settings_container_ === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.scene_settings_container_.innerHTML = '';
|
||||
if (this.can_access_scene_settings_() !== true || this.scene_settings_menu_open_ !== true) {
|
||||
this.scene_settings_container_.style.display = 'none';
|
||||
this.update_fps_visibility_();
|
||||
return;
|
||||
}
|
||||
this.scene_settings_container_.style.display = 'block';
|
||||
|
||||
this.ensure_scene_settings_values_();
|
||||
|
||||
const panel = document.createElement('div');
|
||||
Object.assign(panel.style, {
|
||||
'background': 'rgba(32, 42, 48, 0.94)',
|
||||
'border': '1px solid rgba(255,255,255,0.16)',
|
||||
'border-radius': '8px',
|
||||
'padding': '10px',
|
||||
'color': '#fff',
|
||||
'font-size': beestat.style.font_size.small,
|
||||
'display': 'flex',
|
||||
'flex-direction': 'column',
|
||||
'grid-gap': '8px'
|
||||
});
|
||||
this.scene_settings_container_.appendChild(panel);
|
||||
|
||||
const get_title_case_label = (key) => {
|
||||
return key
|
||||
.split('_')
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const add_boolean_setting = (label, key) => {
|
||||
const row = document.createElement('label');
|
||||
Object.assign(row.style, {
|
||||
'display': 'flex',
|
||||
'justify-content': 'space-between',
|
||||
'align-items': 'center',
|
||||
'grid-gap': '10px'
|
||||
});
|
||||
const text = document.createElement('span');
|
||||
text.innerText = label;
|
||||
row.appendChild(text);
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.checked = this.scene_settings_values_[key] === true;
|
||||
Object.assign(input.style, {
|
||||
'visibility': 'visible',
|
||||
'appearance': 'auto',
|
||||
'-webkit-appearance': 'checkbox',
|
||||
'accent-color': beestat.style.color.lightblue.base,
|
||||
'width': '16px',
|
||||
'height': '16px',
|
||||
'margin': '0'
|
||||
});
|
||||
input.addEventListener('change', () => {
|
||||
this.set_scene_setting_from_panel_(key, input.checked === true);
|
||||
});
|
||||
row.appendChild(input);
|
||||
panel.appendChild(row);
|
||||
};
|
||||
|
||||
const add_number_setting = (label, key, min, max, step) => {
|
||||
const row = document.createElement('label');
|
||||
Object.assign(row.style, {
|
||||
'display': 'flex',
|
||||
'justify-content': 'space-between',
|
||||
'align-items': 'center',
|
||||
'grid-gap': '10px'
|
||||
});
|
||||
const text = document.createElement('span');
|
||||
text.innerText = label;
|
||||
row.appendChild(text);
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'number';
|
||||
input.value = String(this.scene_settings_values_[key]);
|
||||
input.min = String(min);
|
||||
input.max = String(max);
|
||||
input.step = String(step);
|
||||
Object.assign(input.style, {
|
||||
'width': '62px',
|
||||
'background': '#1a242a',
|
||||
'color': '#fff',
|
||||
'border': '1px solid rgba(255,255,255,0.2)',
|
||||
'border-radius': '4px',
|
||||
'padding': '2px 4px'
|
||||
});
|
||||
input.addEventListener('change', () => {
|
||||
const parsed = Number(input.value);
|
||||
if (Number.isFinite(parsed) !== true) {
|
||||
input.value = String(this.scene_settings_values_[key]);
|
||||
return;
|
||||
}
|
||||
const clamped = Math.max(min, Math.min(max, parsed));
|
||||
const normalized = step >= 1 ? Math.round(clamped) : clamped;
|
||||
input.value = String(normalized);
|
||||
this.set_scene_setting_from_panel_(key, normalized);
|
||||
});
|
||||
row.appendChild(input);
|
||||
panel.appendChild(row);
|
||||
};
|
||||
|
||||
const add_separator = () => {
|
||||
const separator = document.createElement('div');
|
||||
Object.assign(separator.style, {
|
||||
'height': '1px',
|
||||
'background': 'rgba(255,255,255,0.16)',
|
||||
'margin': '2px 0'
|
||||
});
|
||||
panel.appendChild(separator);
|
||||
};
|
||||
|
||||
// Weather
|
||||
add_number_setting(get_title_case_label('cloud_density'), 'cloud_density', 0, 2, 0.1);
|
||||
add_number_setting(get_title_case_label('rain_density'), 'rain_density', 0, 2, 0.1);
|
||||
add_number_setting(get_title_case_label('snow_density'), 'snow_density', 0, 2, 0.1);
|
||||
|
||||
add_separator();
|
||||
|
||||
// Tree
|
||||
add_boolean_setting(get_title_case_label('tree_enabled'), 'tree_enabled');
|
||||
add_number_setting(get_title_case_label('tree_branch_depth'), 'tree_branch_depth', 0, 4, 1);
|
||||
|
||||
add_separator();
|
||||
|
||||
// Light / Sky
|
||||
add_number_setting(get_title_case_label('star_density'), 'star_density', 0, 2, 0.1);
|
||||
add_boolean_setting(get_title_case_label('light_user_enabled'), 'light_user_enabled');
|
||||
this.update_fps_visibility_();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1112,6 +1360,21 @@ beestat.component.card.three_d.prototype.decorate_fps_ticker_ = function(parent)
|
||||
this.fps_interval_ = window.setInterval(set_text, 250);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show FPS only while scene settings are open.
|
||||
*/
|
||||
beestat.component.card.three_d.prototype.update_fps_visibility_ = function() {
|
||||
if (this.fps_container_ === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const show = (
|
||||
this.can_access_scene_settings_() === true &&
|
||||
this.scene_settings_menu_open_ === true
|
||||
);
|
||||
this.fps_container_.style.display = show ? 'block' : 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* Toolbar.
|
||||
*
|
||||
@ -1200,6 +1463,23 @@ beestat.component.card.three_d.prototype.decorate_toolbar_ = function(parent) {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.can_access_scene_settings_() === true) {
|
||||
tile_group.add_tile(new beestat.component.tile()
|
||||
.set_icon('tune')
|
||||
.set_title('Scene Settings')
|
||||
.set_text_color(beestat.style.color.gray.light)
|
||||
.set_background_color(this.scene_settings_menu_open_ === true ? beestat.style.color.lightblue.base : beestat.style.color.bluegray.base)
|
||||
.set_background_hover_color(this.scene_settings_menu_open_ === true ? beestat.style.color.lightblue.light : beestat.style.color.bluegray.light)
|
||||
.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
self.scene_settings_menu_open_ = self.scene_settings_menu_open_ !== true;
|
||||
self.decorate_toolbar_();
|
||||
self.decorate_scene_settings_panel_();
|
||||
self.update_fps_visibility_();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Labels (hidden while environment view is on)
|
||||
if (show_environment === false) {
|
||||
tile_group.add_tile(new beestat.component.tile()
|
||||
|
||||
@ -263,15 +263,150 @@ beestat.component.scene.sidereal_day_seconds = 86164.0905;
|
||||
*/
|
||||
beestat.component.scene.star_drift_visual_factor = 0.12;
|
||||
|
||||
/**
|
||||
* Runtime scene settings exposed through the scene settings panel.
|
||||
*
|
||||
* @type {{
|
||||
* cloud_density: number,
|
||||
* rain_density: number,
|
||||
* snow_density: number,
|
||||
* tree_enabled: boolean,
|
||||
* tree_branch_depth: number,
|
||||
* star_density: number,
|
||||
* light_user_enabled: boolean,
|
||||
* random_seed: number
|
||||
* }}
|
||||
*/
|
||||
beestat.component.scene.default_settings = {
|
||||
'cloud_density': 1,
|
||||
'rain_density': 1,
|
||||
'snow_density': 1,
|
||||
'tree_enabled': true,
|
||||
'tree_branch_depth': 1,
|
||||
'star_density': 1,
|
||||
'light_user_enabled': true,
|
||||
'random_seed': 1
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalization area used to convert weather density to particle counts.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
beestat.component.scene.weather_density_unit_area = 2500000;
|
||||
|
||||
/**
|
||||
* Build deterministic PRNG from a numeric seed.
|
||||
*
|
||||
* @param {number} seed
|
||||
*
|
||||
* @return {function(): number}
|
||||
*/
|
||||
beestat.component.scene.prototype.create_seeded_random_generator_ = function(seed) {
|
||||
let state = (seed >>> 0);
|
||||
if (state === 0) {
|
||||
state = 0x6d2b79f5;
|
||||
}
|
||||
|
||||
return function() {
|
||||
state += 0x6d2b79f5;
|
||||
let t = state;
|
||||
t = Math.imul(t ^ (t >>> 15), t | 1);
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset random generator for this scene from current seed setting.
|
||||
*/
|
||||
beestat.component.scene.prototype.reset_random_generator_ = function() {
|
||||
const raw_seed = Number(this.get_scene_setting_('random_seed'));
|
||||
const normalized_seed = Number.isFinite(raw_seed)
|
||||
? Math.max(1, Math.round(raw_seed))
|
||||
: 1;
|
||||
this.random_seed_ = normalized_seed;
|
||||
this.random_generator_ = this.create_seeded_random_generator_(normalized_seed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get deterministic random number in [0, 1).
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
beestat.component.scene.prototype.random_ = function() {
|
||||
if (typeof this.random_generator_ !== 'function') {
|
||||
this.reset_random_generator_();
|
||||
}
|
||||
return this.random_generator_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Run scene-generation logic using deterministic Math.random.
|
||||
*
|
||||
* @param {function()} callback
|
||||
*/
|
||||
beestat.component.scene.prototype.with_seeded_random_ = function(callback) {
|
||||
const original_random = Math.random;
|
||||
this.reset_random_generator_();
|
||||
Math.random = this.random_.bind(this);
|
||||
try {
|
||||
callback();
|
||||
} finally {
|
||||
Math.random = original_random;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build deterministic unsigned seed from string parts.
|
||||
*
|
||||
* @param {Array<*>} parts
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_seed_from_parts_ = function(parts) {
|
||||
const input = parts.map((part) => String(part)).join('|');
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
hash ^= input.charCodeAt(i);
|
||||
hash = Math.imul(hash, 16777619);
|
||||
}
|
||||
hash >>>= 0;
|
||||
return hash === 0 ? 1 : hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run callback with a temporary deterministic random source.
|
||||
*
|
||||
* @param {number} seed
|
||||
* @param {function()} callback
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
beestat.component.scene.prototype.with_random_seed_ = function(seed, callback) {
|
||||
const normalized_seed = Math.max(1, Number(seed || 1) >>> 0);
|
||||
const original_random = Math.random;
|
||||
const local_random = this.create_seeded_random_generator_(normalized_seed);
|
||||
Math.random = local_random;
|
||||
try {
|
||||
return callback();
|
||||
} finally {
|
||||
Math.random = original_random;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rerender the scene by removing the primary group, then re-adding it and the
|
||||
* floor plan. This avoids having to reconstruct everything and then also
|
||||
* having to manually save camera info etc.
|
||||
*/
|
||||
beestat.component.scene.prototype.rerender = function() {
|
||||
this.reset_celestial_lights_for_rerender_();
|
||||
this.scene_.remove(this.main_group_);
|
||||
this.add_main_group_();
|
||||
this.add_floor_plan_();
|
||||
this.with_seeded_random_(function() {
|
||||
this.add_main_group_();
|
||||
this.add_floor_plan_();
|
||||
}.bind(this));
|
||||
this.apply_appearance_rotation_to_lights_();
|
||||
|
||||
// Ensure everything gets updated with the latest info.
|
||||
@ -280,6 +415,35 @@ beestat.component.scene.prototype.rerender = function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset celestial objects so rerender can rebuild stars/lights from settings.
|
||||
*/
|
||||
beestat.component.scene.prototype.reset_celestial_lights_for_rerender_ = function() {
|
||||
if (this.sun_light_ !== undefined && this.sun_light_.target !== undefined && this.sun_light_.target.parent !== null) {
|
||||
this.sun_light_.target.parent.remove(this.sun_light_.target);
|
||||
}
|
||||
if (this.moon_light_ !== undefined && this.moon_light_.target !== undefined && this.moon_light_.target.parent !== null) {
|
||||
this.moon_light_.target.parent.remove(this.moon_light_.target);
|
||||
}
|
||||
if (this.celestial_light_group_ !== undefined && this.celestial_light_group_.parent !== null) {
|
||||
this.celestial_light_group_.parent.remove(this.celestial_light_group_);
|
||||
}
|
||||
|
||||
delete this.celestial_light_group_;
|
||||
delete this.sun_light_;
|
||||
delete this.moon_light_;
|
||||
delete this.sun_light_helper_;
|
||||
delete this.moon_light_helper_;
|
||||
delete this.sun_path_line_;
|
||||
delete this.sun_visual_group_;
|
||||
delete this.sun_core_mesh_;
|
||||
delete this.sun_glow_sprite_;
|
||||
delete this.moon_visual_group_;
|
||||
delete this.moon_sprite_;
|
||||
delete this.star_group_;
|
||||
delete this.stars_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an appearance value with fallback to default if not set.
|
||||
*
|
||||
@ -295,6 +459,65 @@ beestat.component.scene.prototype.get_appearance_value_ = function(key) {
|
||||
return beestat.component.scene.default_appearance[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a scene setting value with default fallback.
|
||||
*
|
||||
* @param {string} key
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_scene_setting_ = function(key) {
|
||||
if (this.scene_settings_ !== undefined && this.scene_settings_[key] !== undefined) {
|
||||
return this.scene_settings_[key];
|
||||
}
|
||||
return beestat.component.scene.default_settings[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all currently effective scene settings.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_scene_settings = function() {
|
||||
const current_settings = Object.assign({}, beestat.component.scene.default_settings);
|
||||
if (this.scene_settings_ !== undefined) {
|
||||
Object.assign(current_settings, this.scene_settings_);
|
||||
}
|
||||
return current_settings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update scene settings.
|
||||
*
|
||||
* @param {object} scene_settings
|
||||
* @param {object=} options
|
||||
*
|
||||
* @return {beestat.component.scene}
|
||||
*/
|
||||
beestat.component.scene.prototype.set_scene_settings = function(scene_settings, options) {
|
||||
if (scene_settings === undefined || scene_settings === null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.scene_settings_ === undefined) {
|
||||
this.scene_settings_ = {};
|
||||
}
|
||||
Object.assign(this.scene_settings_, scene_settings);
|
||||
|
||||
const rerender = options !== undefined && options.rerender === true;
|
||||
if (this.rendered_ === true) {
|
||||
if (rerender === true) {
|
||||
this.rerender();
|
||||
} else {
|
||||
this.update_weather_targets_();
|
||||
this.update_tree_foliage_season_();
|
||||
this.update_weather_();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the width of this component.
|
||||
*
|
||||
@ -325,6 +548,10 @@ beestat.component.scene.prototype.decorate_ = function(parent) {
|
||||
// Dark background to help reduce apparant flicker when resizing
|
||||
parent.style('background', '#202a30');
|
||||
|
||||
if (this.scene_settings_ === undefined) {
|
||||
this.scene_settings_ = {};
|
||||
}
|
||||
|
||||
this.debug_ = {
|
||||
'axes': false,
|
||||
'directional_light_helpers': false,
|
||||
@ -351,8 +578,10 @@ beestat.component.scene.prototype.decorate_ = function(parent) {
|
||||
this.add_skybox_(parent);
|
||||
this.add_static_lights_();
|
||||
|
||||
this.add_main_group_();
|
||||
this.add_floor_plan_();
|
||||
this.with_seeded_random_(function() {
|
||||
this.add_main_group_();
|
||||
this.add_floor_plan_();
|
||||
}.bind(this));
|
||||
|
||||
this.fps_ = 0;
|
||||
this.fps_frame_count_ = 0;
|
||||
|
||||
@ -56,6 +56,8 @@ beestat.component.scene.prototype.update_tree_foliage_season_ = function() {
|
||||
}
|
||||
|
||||
const state = this.get_tree_foliage_state_();
|
||||
const tree_foliage_enabled = state.visible === true;
|
||||
const tree_branch_enabled = state.visible !== true;
|
||||
if (has_foliage_meshes === true) {
|
||||
for (let i = 0; i < this.tree_foliage_meshes_.length; i++) {
|
||||
const mesh = this.tree_foliage_meshes_[i];
|
||||
@ -68,7 +70,7 @@ beestat.component.scene.prototype.update_tree_foliage_season_ = function() {
|
||||
mesh.material.transparent = beestat.component.scene.debug_tree_canopy_opacity < 1;
|
||||
mesh.material.depthWrite = beestat.component.scene.debug_tree_canopy_opacity >= 1;
|
||||
mesh.material.needsUpdate = true;
|
||||
mesh.visible = state.visible;
|
||||
mesh.visible = tree_foliage_enabled === true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +80,9 @@ beestat.component.scene.prototype.update_tree_foliage_season_ = function() {
|
||||
if (branch_group !== undefined) {
|
||||
// Hide branches when canopy is visible; show them when canopy is not visible.
|
||||
// Debug override can force branch meshes hidden at all times.
|
||||
branch_group.visible = this.debug_.hide_tree_branches !== true && state.visible !== true;
|
||||
branch_group.visible =
|
||||
this.debug_.hide_tree_branches !== true &&
|
||||
tree_branch_enabled === true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,6 +95,10 @@ beestat.component.scene.prototype.update_tree_foliage_season_ = function() {
|
||||
* @param {number} ground_surface_z
|
||||
*/
|
||||
beestat.component.scene.prototype.add_trees_ = function(ground_surface_z) {
|
||||
if (this.get_scene_setting_('tree_enabled') !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
|
||||
const tree_group = new THREE.Group();
|
||||
tree_group.userData.is_environment = true;
|
||||
@ -98,8 +106,6 @@ beestat.component.scene.prototype.add_trees_ = function(ground_surface_z) {
|
||||
this.tree_foliage_meshes_ = [];
|
||||
this.tree_branch_groups_ = [];
|
||||
|
||||
const foliage_enabled = beestat.component.scene.environment_tree_foliage_enabled;
|
||||
|
||||
const trees = [];
|
||||
floor_plan.data.groups.forEach(function(group) {
|
||||
if (Array.isArray(group.trees) === true) {
|
||||
@ -109,7 +115,7 @@ beestat.component.scene.prototype.add_trees_ = function(ground_surface_z) {
|
||||
}
|
||||
});
|
||||
|
||||
trees.forEach(function(tree_data) {
|
||||
trees.forEach(function(tree_data, tree_index) {
|
||||
const tree_type = ['conical', 'round', 'oval'].includes(tree_data.type)
|
||||
? tree_data.type
|
||||
: 'round';
|
||||
@ -118,14 +124,27 @@ beestat.component.scene.prototype.add_trees_ = function(ground_surface_z) {
|
||||
const tree_x = Number(tree_data.x || 0);
|
||||
const tree_y = Number(tree_data.y || 0);
|
||||
|
||||
let tree;
|
||||
if (tree_type === 'conical') {
|
||||
tree = this.create_conical_tree_(tree_height, tree_diameter, foliage_enabled);
|
||||
} else if (tree_type === 'oval') {
|
||||
tree = this.create_oval_tree_(tree_height, tree_diameter, foliage_enabled);
|
||||
} else {
|
||||
tree = this.create_round_tree_(tree_height, tree_diameter, foliage_enabled);
|
||||
}
|
||||
const tree_seed = this.get_seed_from_parts_([
|
||||
this.get_scene_setting_('random_seed'),
|
||||
'tree',
|
||||
tree_index,
|
||||
tree_type,
|
||||
tree_x,
|
||||
tree_y,
|
||||
tree_height,
|
||||
tree_diameter
|
||||
]);
|
||||
|
||||
const tree = this.with_random_seed_(tree_seed, function() {
|
||||
this.active_tree_seed_ = tree_seed;
|
||||
|
||||
if (tree_type === 'conical') {
|
||||
return this.create_conical_tree_(tree_height, tree_diameter, true);
|
||||
} else if (tree_type === 'oval') {
|
||||
return this.create_oval_tree_(tree_height, tree_diameter, true);
|
||||
}
|
||||
return this.create_round_tree_(tree_height, tree_diameter, true);
|
||||
}.bind(this));
|
||||
|
||||
tree.position.set(tree_x, tree_y, ground_surface_z);
|
||||
tree.rotation.z = 0;
|
||||
|
||||
@ -259,7 +259,9 @@ beestat.component.scene.prototype.add_stars_ = function() {
|
||||
this.stars_ = [];
|
||||
|
||||
const radius = 4200;
|
||||
for (let i = 0; i < beestat.component.scene.star_count; i++) {
|
||||
const star_density = Math.max(0, Number(this.get_scene_setting_('star_density') || 0));
|
||||
const star_count = Math.max(0, Math.round(1000 * star_density));
|
||||
for (let i = 0; i < star_count; i++) {
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
const phi = Math.acos((Math.random() * 2) - 1);
|
||||
|
||||
@ -667,6 +669,10 @@ beestat.component.scene.prototype.get_light_color_from_temperature_ = function(t
|
||||
* @param {object} group The floor plan group.
|
||||
*/
|
||||
beestat.component.scene.prototype.add_light_sources_ = function(layer, group) {
|
||||
if (this.get_scene_setting_('light_user_enabled') !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(group.light_sources) !== true || group.light_sources.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -319,7 +319,7 @@ beestat.component.scene.prototype.create_stick_mesh_ = function(config) {
|
||||
: (radius_bottom * resolved_top_ratio)
|
||||
);
|
||||
const radial_segments = Math.max(3, config.radial_segments || 7);
|
||||
const height_segments = Math.max(1, config.height_segments || 6);
|
||||
const segments = Math.max(1, Math.round(height / 12));
|
||||
const control_count = Math.max(2, config.control_count || 5);
|
||||
const max_drift = Math.max(0, config.max_drift || 0);
|
||||
const direction_jitter = config.direction_jitter || (radius_bottom * 0.15);
|
||||
@ -353,7 +353,7 @@ beestat.component.scene.prototype.create_stick_mesh_ = function(config) {
|
||||
radius_bottom,
|
||||
height,
|
||||
radial_segments,
|
||||
height_segments
|
||||
segments
|
||||
);
|
||||
geometry.rotateX(-Math.PI / 2);
|
||||
|
||||
@ -492,7 +492,6 @@ beestat.component.scene.prototype.create_round_tree_ = function(height, max_diam
|
||||
'height': trunk_height,
|
||||
'radius_bottom': trunk_radius_bottom,
|
||||
'radial_segments': 7,
|
||||
'height_segments': 8,
|
||||
'control_count': 6,
|
||||
'max_drift': 8,
|
||||
'direction_jitter': 3,
|
||||
@ -627,7 +626,10 @@ beestat.component.scene.prototype.create_round_tree_ = function(height, max_diam
|
||||
};
|
||||
};
|
||||
const branch_height_samples = [];
|
||||
const recursive_depth_limit = 1;
|
||||
const recursive_depth_limit = Math.max(
|
||||
0,
|
||||
Math.round(Number(this.get_scene_setting_('tree_branch_depth') || 0))
|
||||
);
|
||||
const children_per_branch = 2;
|
||||
if (foliage_enabled === true && this.tree_foliage_meshes_ === undefined) {
|
||||
this.tree_foliage_meshes_ = [];
|
||||
@ -696,7 +698,6 @@ beestat.component.scene.prototype.create_round_tree_ = function(height, max_diam
|
||||
'height': length,
|
||||
'radius_bottom': radius_bottom,
|
||||
'radial_segments': 7,
|
||||
'height_segments': 6,
|
||||
'control_count': 6,
|
||||
'max_drift': length * 0.24,
|
||||
'direction_jitter': length * 0.12,
|
||||
@ -787,7 +788,13 @@ beestat.component.scene.prototype.create_round_tree_ = function(height, max_diam
|
||||
}
|
||||
|
||||
if (foliage_enabled === true) {
|
||||
const canopy_result = create_canopy_from_branch_function_();
|
||||
const canopy_seed = this.get_seed_from_parts_([
|
||||
this.active_tree_seed_ === undefined ? this.get_scene_setting_('random_seed') : this.active_tree_seed_,
|
||||
'canopy'
|
||||
]);
|
||||
const canopy_result = this.with_random_seed_(canopy_seed, function() {
|
||||
return create_canopy_from_branch_function_();
|
||||
});
|
||||
const canopy_mesh = canopy_result.mesh;
|
||||
canopy_mesh.castShadow = true;
|
||||
canopy_mesh.receiveShadow = true;
|
||||
@ -799,7 +806,8 @@ beestat.component.scene.prototype.create_round_tree_ = function(height, max_diam
|
||||
if (foliage_enabled === true) {
|
||||
this.tree_branch_groups_.push(branches);
|
||||
}
|
||||
branches.visible = this.debug_.hide_tree_branches !== true && foliage_enabled !== true;
|
||||
branches.visible =
|
||||
this.debug_.hide_tree_branches !== true;
|
||||
tree.add(branches);
|
||||
if (foliage_enabled === true) {
|
||||
tree.add(foliage);
|
||||
|
||||
@ -16,7 +16,44 @@ beestat.component.scene.prototype.set_weather = function(weather) {
|
||||
floor_plan.data.appearance = {};
|
||||
}
|
||||
floor_plan.data.appearance.weather = weather;
|
||||
this.update_weather_targets_();
|
||||
|
||||
// Backward-compatible weather mode support by translating to density values.
|
||||
let weather_settings;
|
||||
switch (weather) {
|
||||
case 'snow':
|
||||
weather_settings = {
|
||||
'cloud_density': 1,
|
||||
'rain_density': 0,
|
||||
'snow_density': 1
|
||||
};
|
||||
break;
|
||||
case 'rain':
|
||||
weather_settings = {
|
||||
'cloud_density': 1,
|
||||
'rain_density': 1,
|
||||
'snow_density': 0
|
||||
};
|
||||
break;
|
||||
case 'cloudy':
|
||||
weather_settings = {
|
||||
'cloud_density': 1,
|
||||
'rain_density': 0,
|
||||
'snow_density': 0
|
||||
};
|
||||
break;
|
||||
case 'sunny':
|
||||
case 'none':
|
||||
default:
|
||||
weather_settings = {
|
||||
'cloud_density': 0,
|
||||
'rain_density': 0,
|
||||
'snow_density': 0
|
||||
};
|
||||
break;
|
||||
}
|
||||
this.set_scene_settings(weather_settings, {
|
||||
'rerender': false
|
||||
});
|
||||
|
||||
if (this.rendered_ === true) {
|
||||
this.update_();
|
||||
@ -27,41 +64,74 @@ beestat.component.scene.prototype.set_weather = function(weather) {
|
||||
|
||||
|
||||
/**
|
||||
* Get weather transition profile for visuals.
|
||||
* Get design count at density 1 for a weather channel.
|
||||
*
|
||||
* @param {string} weather
|
||||
* @param {string} density_key
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_weather_design_count_ = function(density_key) {
|
||||
switch (density_key) {
|
||||
case 'cloud_density':
|
||||
return Math.max(1, Number(beestat.component.scene.weather_cloud_max_count || 1));
|
||||
case 'rain_density':
|
||||
return Math.max(1, Number(beestat.component.scene.weather_rain_max_count || 1));
|
||||
case 'snow_density':
|
||||
return Math.max(1, Number(beestat.component.scene.weather_snow_max_count || 1));
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get design capacity count (density 1) for a weather channel and area.
|
||||
*
|
||||
* @param {string} density_key
|
||||
* @param {number=} opt_area
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_weather_design_capacity_count_ = function(density_key, opt_area) {
|
||||
const design_count = this.get_weather_design_count_(density_key);
|
||||
const area = Math.max(
|
||||
1,
|
||||
Number(opt_area || this.weather_area_ || beestat.component.scene.weather_density_unit_area)
|
||||
);
|
||||
const unit_area = Math.max(1, Number(beestat.component.scene.weather_density_unit_area || 1));
|
||||
return Math.max(0, Math.round(design_count * (area / unit_area)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert density setting to particle count using scene area.
|
||||
*
|
||||
* @param {string} density_key
|
||||
* @param {number=} opt_area
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_weather_count_from_density_ = function(density_key, opt_area) {
|
||||
const density = Math.max(0, Number(this.get_scene_setting_(density_key) || 0));
|
||||
const design_count = this.get_weather_design_count_(density_key);
|
||||
const area = Math.max(
|
||||
1,
|
||||
Number(opt_area || this.weather_area_ || beestat.component.scene.weather_density_unit_area)
|
||||
);
|
||||
const unit_area = Math.max(1, Number(beestat.component.scene.weather_density_unit_area || 1));
|
||||
|
||||
return Math.max(0, Math.round(design_count * density * (area / unit_area)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get weather transition profile for visuals.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_weather_profile_ = function(weather) {
|
||||
switch (weather) {
|
||||
case 'snow':
|
||||
return {
|
||||
'cloud_count': beestat.component.scene.weather_cloud_max_count,
|
||||
'rain_count': 0,
|
||||
'snow_count': beestat.component.scene.weather_snow_max_count
|
||||
};
|
||||
case 'rain':
|
||||
return {
|
||||
'cloud_count': Math.round(beestat.component.scene.weather_cloud_max_count * 0.92),
|
||||
'rain_count': beestat.component.scene.weather_rain_max_count,
|
||||
'snow_count': 0
|
||||
};
|
||||
case 'cloudy':
|
||||
return {
|
||||
'cloud_count': Math.round(beestat.component.scene.weather_cloud_max_count * 0.72),
|
||||
'rain_count': 0,
|
||||
'snow_count': 0
|
||||
};
|
||||
case 'sunny':
|
||||
case 'none':
|
||||
default:
|
||||
return {
|
||||
'cloud_count': 0,
|
||||
'rain_count': 0,
|
||||
'snow_count': 0
|
||||
};
|
||||
}
|
||||
beestat.component.scene.prototype.get_weather_profile_ = function() {
|
||||
return {
|
||||
'cloud_count': this.get_weather_count_from_density_('cloud_density'),
|
||||
'rain_count': this.get_weather_count_from_density_('rain_density'),
|
||||
'snow_count': this.get_weather_count_from_density_('snow_density')
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -71,6 +141,10 @@ beestat.component.scene.prototype.get_weather_profile_ = function(weather) {
|
||||
* @return {number}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_cloud_dimming_factor_ = function() {
|
||||
const configured_cloud_count = Math.max(
|
||||
1,
|
||||
this.get_weather_design_capacity_count_('cloud_density')
|
||||
);
|
||||
const current_cloud_count = this.current_cloud_count_ === undefined
|
||||
? 0
|
||||
: this.current_cloud_count_;
|
||||
@ -78,7 +152,7 @@ beestat.component.scene.prototype.get_cloud_dimming_factor_ = function() {
|
||||
0,
|
||||
Math.min(
|
||||
1,
|
||||
current_cloud_count / beestat.component.scene.weather_cloud_max_count
|
||||
current_cloud_count / configured_cloud_count
|
||||
)
|
||||
);
|
||||
|
||||
@ -90,7 +164,7 @@ beestat.component.scene.prototype.get_cloud_dimming_factor_ = function() {
|
||||
* Update weather transition targets based on appearance weather.
|
||||
*/
|
||||
beestat.component.scene.prototype.update_weather_targets_ = function() {
|
||||
this.weather_profile_target_ = this.get_weather_profile_(this.get_appearance_value_('weather'));
|
||||
this.weather_profile_target_ = this.get_weather_profile_();
|
||||
|
||||
this.weather_transition_start_profile_ = {
|
||||
'cloud_count': this.current_cloud_count_ === undefined ? 0 : this.current_cloud_count_,
|
||||
@ -107,9 +181,10 @@ beestat.component.scene.prototype.update_weather_targets_ = function() {
|
||||
* @return {number}
|
||||
*/
|
||||
beestat.component.scene.prototype.get_snow_cover_blend_ = function() {
|
||||
const configured_snow_count = this.get_weather_design_capacity_count_('snow_density');
|
||||
if (
|
||||
this.current_snow_count_ === undefined ||
|
||||
beestat.component.scene.weather_snow_max_count <= 0
|
||||
configured_snow_count <= 0
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
@ -118,7 +193,7 @@ beestat.component.scene.prototype.get_snow_cover_blend_ = function() {
|
||||
0,
|
||||
Math.min(
|
||||
1,
|
||||
this.current_snow_count_ / beestat.component.scene.weather_snow_max_count
|
||||
this.current_snow_count_ / configured_snow_count
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -340,6 +415,10 @@ beestat.component.scene.prototype.add_weather_ = function(center_x, center_y, pl
|
||||
'min_z': -780,
|
||||
'max_z': 140
|
||||
};
|
||||
this.weather_area_ = Math.max(
|
||||
1,
|
||||
(bounds.max_x - bounds.min_x) * (bounds.max_y - bounds.min_y)
|
||||
);
|
||||
|
||||
this.weather_group_ = new THREE.Group();
|
||||
this.weather_group_.userData.is_environment = true;
|
||||
@ -356,7 +435,14 @@ beestat.component.scene.prototype.add_weather_ = function(center_x, center_y, pl
|
||||
this.rain_particle_texture_ = this.create_rain_particle_texture_();
|
||||
}
|
||||
|
||||
const cloud_count = beestat.component.scene.weather_cloud_max_count;
|
||||
const configured_cloud_count = this.get_weather_count_from_density_(
|
||||
'cloud_density',
|
||||
this.weather_area_
|
||||
);
|
||||
const cloud_capacity = Math.max(
|
||||
this.get_weather_design_capacity_count_('cloud_density', this.weather_area_),
|
||||
configured_cloud_count
|
||||
);
|
||||
const cloud_opacity = 0.2;
|
||||
const cloud_bounds = {
|
||||
'min_x': bounds.min_x - 260,
|
||||
@ -370,7 +456,7 @@ beestat.component.scene.prototype.add_weather_ = function(center_x, center_y, pl
|
||||
this.cloud_sprites_ = [];
|
||||
this.cloud_motion_ = [];
|
||||
|
||||
for (let i = 0; i < cloud_count; i++) {
|
||||
for (let i = 0; i < cloud_capacity; i++) {
|
||||
const cloud_material = new THREE.SpriteMaterial({
|
||||
'map': this.cloud_texture_,
|
||||
'color': 0xdce3ee,
|
||||
@ -415,7 +501,10 @@ beestat.component.scene.prototype.add_weather_ = function(center_x, center_y, pl
|
||||
|
||||
this.rain_particles_ = this.create_precipitation_system_(
|
||||
bounds,
|
||||
beestat.component.scene.weather_rain_max_count,
|
||||
Math.max(
|
||||
this.get_weather_design_capacity_count_('rain_density', this.weather_area_),
|
||||
this.get_weather_count_from_density_('rain_density', this.weather_area_)
|
||||
),
|
||||
{
|
||||
'size': 11,
|
||||
'color': 0xa8c7ff,
|
||||
@ -430,7 +519,10 @@ beestat.component.scene.prototype.add_weather_ = function(center_x, center_y, pl
|
||||
|
||||
this.snow_particles_ = this.create_precipitation_system_(
|
||||
bounds,
|
||||
beestat.component.scene.weather_snow_max_count,
|
||||
Math.max(
|
||||
this.get_weather_design_capacity_count_('snow_density', this.weather_area_),
|
||||
this.get_weather_count_from_density_('snow_density', this.weather_area_)
|
||||
),
|
||||
{
|
||||
'size': 10,
|
||||
'color': 0xffffff,
|
||||
@ -445,7 +537,7 @@ beestat.component.scene.prototype.add_weather_ = function(center_x, center_y, pl
|
||||
|
||||
this.weather_last_update_ms_ = window.performance.now();
|
||||
|
||||
const initial_weather_profile = this.get_weather_profile_(this.get_appearance_value_('weather'));
|
||||
const initial_weather_profile = this.get_weather_profile_();
|
||||
this.weather_profile_target_ = initial_weather_profile;
|
||||
this.current_cloud_count_ = initial_weather_profile.cloud_count;
|
||||
this.current_rain_count_ = initial_weather_profile.rain_count;
|
||||
@ -517,11 +609,15 @@ beestat.component.scene.prototype.update_weather_ = function() {
|
||||
|
||||
if (this.cloud_sprites_ !== undefined && this.cloud_motion_ !== undefined) {
|
||||
const now_seconds = now_ms / 1000;
|
||||
const cloud_normalization_count = Math.max(
|
||||
1,
|
||||
this.get_weather_design_capacity_count_('cloud_density')
|
||||
);
|
||||
const cloud_density = Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
1,
|
||||
this.current_cloud_count_ / beestat.component.scene.weather_cloud_max_count
|
||||
this.current_cloud_count_ / cloud_normalization_count
|
||||
)
|
||||
);
|
||||
for (let i = 0; i < this.cloud_sprites_.length; i++) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user