diff --git a/js/component/card/floor_plan_editor.js b/js/component/card/floor_plan_editor.js index deccbbf..7499b01 100644 --- a/js/component/card/floor_plan_editor.js +++ b/js/component/card/floor_plan_editor.js @@ -13,23 +13,6 @@ beestat.component.card.floor_plan_editor = function(thermostat_id) { beestat.setting('visualize.floor_plan_id') ).x === Infinity; -/* const change_function = beestat.debounce(function() { - // todo replace these with (if entity set active false?) - delete self.state_.active_group; - - self.rerender(); - - // Center the content if the floor plan changed. - if (self.floor_plan_ !== undefined) { - self.floor_plan_.center_content(); - } - }, 10); - - beestat.dispatcher.addEventListener( - 'setting.visualize.floor_plan_id', - change_function - );*/ - beestat.component.card.apply(this, arguments); // Snapping initial @@ -58,6 +41,39 @@ beestat.component.card.floor_plan_editor = function(thermostat_id) { }; beestat.extend(beestat.component.card.floor_plan_editor, beestat.component.card); +beestat.component.card.floor_plan_editor.layer_type_meta_ = { + 'rooms': { + 'active_state_key': 'active_room_entity', + 'id_key': 'room_id', + 'getter_name': 'get_room', + 'clears_geometry_selection': true + }, + 'surfaces': { + 'active_state_key': 'active_surface_entity', + 'id_key': 'surface_id', + 'getter_name': 'get_surface', + 'clears_geometry_selection': true + }, + 'openings': { + 'active_state_key': 'active_opening_entity', + 'id_key': 'opening_id', + 'getter_name': 'get_opening', + 'clears_geometry_selection': false + }, + 'trees': { + 'active_state_key': 'active_tree_entity', + 'id_key': 'tree_id', + 'getter_name': 'get_tree', + 'clears_geometry_selection': false + }, + 'light_sources': { + 'active_state_key': 'active_light_source_entity', + 'id_key': 'light_source_id', + 'getter_name': 'get_light_source', + 'clears_geometry_selection': false + } +}; + /** * Decorate. * @@ -474,58 +490,28 @@ beestat.component.card.floor_plan_editor.prototype.decorate_drawing_pane_ = func self.floor_plan_.set_width(drawing_canvas_container.getBoundingClientRect().width); }); - // Rerender when stuff happens - this.floor_plan_.addEventListener('add_room', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('remove_room', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('remove_point', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('add_surface', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('remove_surface', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('add_tree', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('remove_tree', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('add_opening', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('remove_opening', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('add_light_source', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('remove_light_source', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('undo', function() { - self.update_floor_plan_(); - self.rerender(); - }); - this.floor_plan_.addEventListener('redo', function() { + // Rerender when floor-plan model changes. + const rerender_events = [ + 'add_room', + 'remove_room', + 'remove_point', + 'add_surface', + 'remove_surface', + 'add_tree', + 'remove_tree', + 'add_opening', + 'remove_opening', + 'add_light_source', + 'remove_light_source', + 'undo', + 'redo' + ]; + const handle_floor_plan_mutation = function() { self.update_floor_plan_(); self.rerender(); + }; + rerender_events.forEach(function(event_name) { + self.floor_plan_.addEventListener(event_name, handle_floor_plan_mutation); }); this.floor_plan_.addEventListener('change_group', self.rerender.bind(this)); @@ -887,48 +873,10 @@ beestat.component.card.floor_plan_editor.prototype.set_layer_object_visibility_ object.editor_hidden = visible !== true; if (visible !== true) { - if ( - type === 'rooms' && - this.state_.active_room_entity !== undefined && - this.state_.active_room_entity.get_room().room_id === object_id - ) { - this.state_.active_room_entity.set_active(false); - } - if ( - type === 'surfaces' && - this.state_.active_surface_entity !== undefined && - this.state_.active_surface_entity.get_surface().surface_id === object_id - ) { - this.state_.active_surface_entity.set_active(false); - } - if ( - type === 'trees' && - this.state_.active_tree_entity !== undefined && - this.state_.active_tree_entity.get_tree().tree_id === object_id - ) { - this.state_.active_tree_entity.set_active(false); - } - if ( - type === 'openings' && - this.state_.active_opening_entity !== undefined && - this.state_.active_opening_entity.get_opening().opening_id === object_id - ) { - this.state_.active_opening_entity.set_active(false); - } - if ( - type === 'light_sources' && - this.state_.active_light_source_entity !== undefined && - this.state_.active_light_source_entity.get_light_source().light_source_id === object_id - ) { - this.state_.active_light_source_entity.set_active(false); - } + this.deactivate_active_entity_for_layer_object_(type, object_id); } - this.floor_plan_.update_infobox(); - this.floor_plan_.update_toolbar(); - this.update_info_pane_(); - this.update_floor_plan_(); - this.rerender(); + this.sync_after_layer_change_(); }; /** @@ -948,48 +896,10 @@ beestat.component.card.floor_plan_editor.prototype.set_layer_object_locked_ = fu object.editor_locked = locked; if (locked === true) { - if ( - type === 'rooms' && - this.state_.active_room_entity !== undefined && - this.state_.active_room_entity.get_room().room_id === object_id - ) { - this.state_.active_room_entity.set_active(false); - } - if ( - type === 'surfaces' && - this.state_.active_surface_entity !== undefined && - this.state_.active_surface_entity.get_surface().surface_id === object_id - ) { - this.state_.active_surface_entity.set_active(false); - } - if ( - type === 'trees' && - this.state_.active_tree_entity !== undefined && - this.state_.active_tree_entity.get_tree().tree_id === object_id - ) { - this.state_.active_tree_entity.set_active(false); - } - if ( - type === 'openings' && - this.state_.active_opening_entity !== undefined && - this.state_.active_opening_entity.get_opening().opening_id === object_id - ) { - this.state_.active_opening_entity.set_active(false); - } - if ( - type === 'light_sources' && - this.state_.active_light_source_entity !== undefined && - this.state_.active_light_source_entity.get_light_source().light_source_id === object_id - ) { - this.state_.active_light_source_entity.set_active(false); - } + this.deactivate_active_entity_for_layer_object_(type, object_id); } - this.floor_plan_.update_infobox(); - this.floor_plan_.update_toolbar(); - this.update_info_pane_(); - this.update_floor_plan_(); - this.rerender(); + this.sync_after_layer_change_(); }; /** @@ -1039,7 +949,7 @@ beestat.component.card.floor_plan_editor.prototype.set_layer_visible_ = function * @param {boolean} locked */ beestat.component.card.floor_plan_editor.prototype.set_group_locked_ = function(group, locked) { - ['rooms', 'surfaces', 'openings', 'trees', 'light_sources'].forEach(function(type) { + this.get_layer_types_().forEach(function(type) { const collection = group[type] || []; collection.forEach(function(object) { object.editor_locked = locked; @@ -1047,11 +957,9 @@ beestat.component.card.floor_plan_editor.prototype.set_group_locked_ = function( }); if (locked === true) { - this.deactivate_active_entity_for_group_type_(group, 'rooms'); - this.deactivate_active_entity_for_group_type_(group, 'surfaces'); - this.deactivate_active_entity_for_group_type_(group, 'openings'); - this.deactivate_active_entity_for_group_type_(group, 'trees'); - this.deactivate_active_entity_for_group_type_(group, 'light_sources'); + this.get_layer_types_().forEach(function(type) { + this.deactivate_active_entity_for_group_type_(group, type); + }, this); } this.sync_after_layer_change_(); @@ -1064,7 +972,7 @@ beestat.component.card.floor_plan_editor.prototype.set_group_locked_ = function( * @param {boolean} visible */ beestat.component.card.floor_plan_editor.prototype.set_group_visible_ = function(group, visible) { - ['rooms', 'surfaces', 'openings', 'trees', 'light_sources'].forEach(function(type) { + this.get_layer_types_().forEach(function(type) { const collection = group[type] || []; collection.forEach(function(object) { object.editor_hidden = visible !== true; @@ -1072,11 +980,9 @@ beestat.component.card.floor_plan_editor.prototype.set_group_visible_ = function }); if (visible !== true) { - this.deactivate_active_entity_for_group_type_(group, 'rooms'); - this.deactivate_active_entity_for_group_type_(group, 'surfaces'); - this.deactivate_active_entity_for_group_type_(group, 'openings'); - this.deactivate_active_entity_for_group_type_(group, 'trees'); - this.deactivate_active_entity_for_group_type_(group, 'light_sources'); + this.get_layer_types_().forEach(function(type) { + this.deactivate_active_entity_for_group_type_(group, type); + }, this); } this.sync_after_layer_change_(); @@ -1089,38 +995,13 @@ beestat.component.card.floor_plan_editor.prototype.set_group_visible_ = function * @param {string} type rooms|surfaces|openings|trees */ beestat.component.card.floor_plan_editor.prototype.deactivate_active_entity_for_group_type_ = function(group, type) { - if (type === 'rooms' && this.state_.active_room_entity !== undefined) { - if (this.state_.active_room_entity.group_ === group) { - this.state_.active_room_entity.set_active(false); - } + const metadata = this.get_layer_type_meta_(type); + if (metadata === undefined) { return; } - - if (type === 'surfaces' && this.state_.active_surface_entity !== undefined) { - if (this.state_.active_surface_entity.group_ === group) { - this.state_.active_surface_entity.set_active(false); - } - return; - } - - if (type === 'trees' && this.state_.active_tree_entity !== undefined) { - if (this.state_.active_tree_entity.group_ === group) { - this.state_.active_tree_entity.set_active(false); - } - return; - } - - if (type === 'openings' && this.state_.active_opening_entity !== undefined) { - if (this.state_.active_opening_entity.group_ === group) { - this.state_.active_opening_entity.set_active(false); - } - return; - } - - if (type === 'light_sources' && this.state_.active_light_source_entity !== undefined) { - if (this.state_.active_light_source_entity.group_ === group) { - this.state_.active_light_source_entity.set_active(false); - } + const active_entity = this.state_[metadata.active_state_key]; + if (active_entity !== undefined && active_entity.group_ === group) { + active_entity.set_active(false); } }; @@ -1175,59 +1056,28 @@ beestat.component.card.floor_plan_editor.prototype.reorder_layer_object_ = funct * Ensure hidden active entities are cleared. */ beestat.component.card.floor_plan_editor.prototype.ensure_active_entity_visibility_ = function() { - if ( - this.state_.active_room_entity !== undefined && - ( - this.state_.active_room_entity.get_room().editor_hidden === true || - this.state_.active_room_entity.get_room().editor_locked === true - ) - ) { - delete this.state_.active_room_entity; - delete this.state_.active_wall_entity; - delete this.state_.active_point_entity; - } - - if ( - this.state_.active_surface_entity !== undefined && - ( - this.state_.active_surface_entity.get_surface().editor_hidden === true || - this.state_.active_surface_entity.get_surface().editor_locked === true - ) - ) { - delete this.state_.active_surface_entity; - delete this.state_.active_wall_entity; - delete this.state_.active_point_entity; - } - - if ( - this.state_.active_tree_entity !== undefined && - ( - this.state_.active_tree_entity.get_tree().editor_hidden === true || - this.state_.active_tree_entity.get_tree().editor_locked === true - ) - ) { - delete this.state_.active_tree_entity; - } - - if ( - this.state_.active_opening_entity !== undefined && - ( - this.state_.active_opening_entity.get_opening().editor_hidden === true || - this.state_.active_opening_entity.get_opening().editor_locked === true - ) - ) { - delete this.state_.active_opening_entity; - } - - if ( - this.state_.active_light_source_entity !== undefined && - ( - this.state_.active_light_source_entity.get_light_source().editor_hidden === true || - this.state_.active_light_source_entity.get_light_source().editor_locked === true - ) - ) { - delete this.state_.active_light_source_entity; - } + this.get_layer_types_().forEach(function(type) { + const metadata = this.get_layer_type_meta_(type); + const active_entity = this.state_[metadata.active_state_key]; + if (active_entity === undefined) { + return; + } + const getter = active_entity[metadata.getter_name]; + if (typeof getter !== 'function') { + return; + } + const active_object = getter.call(active_entity); + if ( + active_object !== undefined && + (active_object.editor_hidden === true || active_object.editor_locked === true) + ) { + delete this.state_[metadata.active_state_key]; + if (metadata.clears_geometry_selection === true) { + delete this.state_.active_wall_entity; + delete this.state_.active_point_entity; + } + } + }, this); }; /** @@ -1261,26 +1111,51 @@ beestat.component.card.floor_plan_editor.prototype.apply_pending_layer_selection }; /** - * Get the id key by object type. + * Layer metadata by type. * - * @param {string} type rooms|surfaces|trees + * @param {string} type rooms|surfaces|openings|trees|light_sources * - * @return {string} + * @return {object|undefined} */ -beestat.component.card.floor_plan_editor.prototype.get_layer_object_id_key_ = function(type) { - if (type === 'rooms') { - return 'room_id'; +beestat.component.card.floor_plan_editor.prototype.get_layer_type_meta_ = function(type) { + return beestat.component.card.floor_plan_editor.layer_type_meta_[type]; +}; + +/** + * Get all supported layer types. + * + * @return {string[]} + */ +beestat.component.card.floor_plan_editor.prototype.get_layer_types_ = function() { + return Object.keys(beestat.component.card.floor_plan_editor.layer_type_meta_); +}; + +/** + * Deactivate active entity for a specific object id/type. + * + * @param {string} type rooms|surfaces|openings|trees|light_sources + * @param {string} object_id + */ +beestat.component.card.floor_plan_editor.prototype.deactivate_active_entity_for_layer_object_ = function(type, object_id) { + const metadata = this.get_layer_type_meta_(type); + if (metadata === undefined) { + return; } - if (type === 'surfaces') { - return 'surface_id'; + const active_entity = this.state_[metadata.active_state_key]; + if (active_entity === undefined) { + return; } - if (type === 'openings') { - return 'opening_id'; + const getter = active_entity[metadata.getter_name]; + if (typeof getter !== 'function') { + return; } - if (type === 'light_sources') { - return 'light_source_id'; + const active_object = getter.call(active_entity); + if ( + active_object !== undefined && + active_object[metadata.id_key] === object_id + ) { + active_entity.set_active(false); } - return 'tree_id'; }; /** @@ -1293,10 +1168,13 @@ beestat.component.card.floor_plan_editor.prototype.get_layer_object_id_key_ = fu * @return {object|undefined} */ beestat.component.card.floor_plan_editor.prototype.get_layer_object_by_id_ = function(group, type, object_id) { + const metadata = this.get_layer_type_meta_(type); + if (metadata === undefined) { + return; + } const collection = group[type] || []; - const id_key = this.get_layer_object_id_key_(type); for (let i = 0; i < collection.length; i++) { - if (collection[i][id_key] === object_id) { + if (collection[i][metadata.id_key] === object_id) { return collection[i]; } } @@ -2570,17 +2448,6 @@ beestat.component.card.floor_plan_editor.prototype.update_floor_plan_ = function beestat.floor_plan.queue_data_save_(floor_plan_id, 1000); }; -/** - * Get cloned floor plan data. - * - * @param {number} floor_plan_id Floor plan ID - * - * @return {object} The modified floor plan data. - */ -beestat.component.card.floor_plan_editor.prototype.get_floor_plan_data_ = function(floor_plan_id) { - return beestat.clone(beestat.cache.floor_plan[floor_plan_id].data); -}; - /** * Decorate the menu. * diff --git a/js/component/card/three_d.js b/js/component/card/three_d.js index c662b70..5a644c6 100644 --- a/js/component/card/three_d.js +++ b/js/component/card/three_d.js @@ -29,7 +29,6 @@ beestat.component.card.three_d = function() { }; // Things that update the scene that don't require a rerender. - // TODO these probably need moved to the layer instead of here beestat.dispatcher.addEventListener( [ 'setting.visualize.data_type', @@ -325,137 +324,74 @@ beestat.component.card.three_d.prototype.decorate_contents_ = function(parent) { ); } - // Don't go before there's data. -/* required_begin = moment.max( - required_begin, - moment.utc(thermostat.data_begin) - );*/ - - // Don't go after now. -/* required_end = moment.min( - required_end, - moment().subtract(1, 'hour') - );*/ - - /** - * If the needed data exists in the database and the runtime_sensor - * cache is empty, then query the data. If the needed data does not exist in - * the database, check every 2 seconds until it does. - */ - // TODO somewhat problematic because I need to check if data is synced from multiple thermostats now - // if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) { const sensor_ids = Object.keys(beestat.floor_plan.get_sensor_ids_map(this.floor_plan_id_)); if (sensor_ids.length > 0) { - if (true) { - if ( - beestat.cache.data.three_d__runtime_sensor === undefined || - beestat.cache.data.three_d__runtime_thermostat === undefined - ) { - // console.log('data is undefined need to load it'); - this.show_loading_('Fetching'); + if ( + beestat.cache.data.three_d__runtime_sensor === undefined || + beestat.cache.data.three_d__runtime_thermostat === undefined + ) { + this.show_loading_('Fetching'); - const value = [ - required_begin.format(), - required_end.format() - ]; - const operator = 'between'; + const value = [ + required_begin.format(), + required_end.format() + ]; + const operator = 'between'; - const sensor_ids = Object.keys(beestat.floor_plan.get_sensor_ids_map(this.floor_plan_id_)); - const thermostat_ids = Object.keys(beestat.floor_plan.get_thermostat_ids_map(this.floor_plan_id_)); - // if (sensor_ids.length > 0) { - const api_call = new beestat.api(); + const thermostat_ids = Object.keys(beestat.floor_plan.get_thermostat_ids_map(this.floor_plan_id_)); + const api_call = new beestat.api(); - // Sensor data - sensor_ids.forEach(function(sensor_id) { - api_call.add_call( - 'runtime_sensor', - 'read', - { - 'attributes': { - 'sensor_id': sensor_id, - 'timestamp': { - 'value': value, - 'operator': operator - } - } - }, - 'runtime_sensor_' + sensor_id - ); - }); - - // Thermostat data - thermostat_ids.forEach(function(thermostat_id) { - api_call.add_call( - 'runtime_thermostat', - 'read', - { - 'attributes': { - 'thermostat_id': thermostat_id, - 'timestamp': { - 'value': value, - 'operator': operator - } - } - }, - 'runtime_thermostat_' + thermostat_id - ); - }); - - api_call.set_callback(function(response) { - let runtime_sensors = []; - let runtime_thermostats = []; - for (let alias in response) { - if (alias.includes('runtime_sensor_') === true) { - runtime_sensors = runtime_sensors.concat(response[alias]); - } else { - runtime_thermostats = runtime_thermostats.concat(response[alias]); + // Sensor data + sensor_ids.forEach(function(sensor_id) { + api_call.add_call( + 'runtime_sensor', + 'read', + { + 'attributes': { + 'sensor_id': sensor_id, + 'timestamp': { + 'value': value, + 'operator': operator } } - beestat.cache.set('data.three_d__runtime_sensor', runtime_sensors); - beestat.cache.set('data.three_d__runtime_thermostat', runtime_thermostats); - }); + }, + 'runtime_sensor_' + sensor_id + ); + }); - api_call.send(); - - // } - } else if (this.has_data_() === false) { - console.info('has data false'); - /*chart_container.style('filter', 'blur(3px)'); - var no_data = $.createElement('div'); - no_data.style({ - 'position': 'absolute', - 'top': 0, - 'left': 0, - 'width': '100%', - 'height': '100%', - 'display': 'flex', - 'flex-direction': 'column', - 'justify-content': 'center', - 'text-align': 'center' - }); - no_data.innerText('No data to display'); - container.appendChild(no_data);*/ - } - } else { - this.show_loading_('Syncing'); - window.setTimeout(function() { - new beestat.api() - .add_call( - 'thermostat', - 'read_id', - { - 'attributes': { - 'inactive': 0 + // Thermostat data + thermostat_ids.forEach(function(thermostat_id) { + api_call.add_call( + 'runtime_thermostat', + 'read', + { + 'attributes': { + 'thermostat_id': thermostat_id, + 'timestamp': { + 'value': value, + 'operator': operator } - }, - 'thermostat' - ) - .set_callback(function(response) { - beestat.cache.set('thermostat', response); - self.rerender(); - }) - .send(); - }, 2000); + } + }, + 'runtime_thermostat_' + thermostat_id + ); + }); + + api_call.set_callback(function(response) { + let runtime_sensors = []; + let runtime_thermostats = []; + for (let alias in response) { + if (alias.includes('runtime_sensor_') === true) { + runtime_sensors = runtime_sensors.concat(response[alias]); + } else { + runtime_thermostats = runtime_thermostats.concat(response[alias]); + } + } + beestat.cache.set('data.three_d__runtime_sensor', runtime_sensors); + beestat.cache.set('data.three_d__runtime_thermostat', runtime_thermostats); + }); + + api_call.send(); } } }; @@ -498,7 +434,6 @@ beestat.component.card.three_d.prototype.decorate_drawing_pane_ = function(paren }); // Set the initial date. - // if (this.has_data_() === true) { this.update_scene_(); this.scene_.render($(parent)); @@ -895,15 +830,6 @@ beestat.component.card.three_d.prototype.apply_weather_setting_to_scene_ = funct } }; -/** - * 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 true; -}; - /** * Ensure local scene settings state exists. */ @@ -1122,7 +1048,6 @@ beestat.component.card.three_d.prototype.decorate_scene_settings_panel_ = functi this.scene_settings_container_.innerHTML = ''; this.scene_settings_panel_content_ = undefined; if ( - this.can_access_scene_settings_() !== true || this.get_show_environment_() !== true || this.scene_settings_menu_open_ !== true ) { @@ -1868,7 +1793,6 @@ beestat.component.card.three_d.prototype.update_fps_visibility_ = function() { } const show = ( - this.can_access_scene_settings_() === true && this.get_show_environment_() === true && this.scene_settings_menu_open_ === true ); @@ -1954,7 +1878,7 @@ beestat.component.card.three_d.prototype.decorate_toolbar_ = function(parent) { ); } - if (this.can_access_scene_settings_() === true && show_environment === true) { + if (show_environment === true) { tile_group.add_tile(new beestat.component.tile() .set_icon('tune') .set_title('Scene Settings') @@ -2560,6 +2484,31 @@ beestat.component.card.three_d.prototype.remove_global_listeners_ = function() { beestat.dispatcher.removeEventListener('resize.three_d'); }; +/** + * Shared teardown path for stale-instance disposal and normal disposal. + */ +beestat.component.card.three_d.prototype.teardown_ = function() { + if (this.rerender_timeout_id_ !== undefined) { + window.clearTimeout(this.rerender_timeout_id_); + this.rerender_timeout_id_ = undefined; + this.rerender_pending_delay_ms_ = undefined; + } + this.rerender_waiting_for_visibility_ = false; + if (this.visibility_observer_ !== undefined) { + this.visibility_observer_.disconnect(); + this.visibility_observer_ = undefined; + } + this.hide_loading_(); + window.clearInterval(this.fps_interval_); + delete this.fps_interval_; + this.remove_global_listeners_(); + + if (this.scene_ !== undefined) { + this.scene_.dispose(); + delete this.scene_; + } +}; + /** * Force teardown for stale card instances that were not formally disposed. */ @@ -2569,49 +2518,12 @@ beestat.component.card.three_d.prototype.force_dispose_stale_instance_ = functio } this.disposed_ = true; - if (this.rerender_timeout_id_ !== undefined) { - window.clearTimeout(this.rerender_timeout_id_); - this.rerender_timeout_id_ = undefined; - this.rerender_pending_delay_ms_ = undefined; - } - this.rerender_waiting_for_visibility_ = false; - if (this.visibility_observer_ !== undefined) { - this.visibility_observer_.disconnect(); - this.visibility_observer_ = undefined; - } - this.hide_loading_(); - window.clearInterval(this.fps_interval_); - delete this.fps_interval_; - this.remove_global_listeners_(); - - if (this.scene_ !== undefined) { - this.scene_.dispose(); - delete this.scene_; - } + this.teardown_(); }; beestat.component.card.three_d.prototype.dispose = function() { this.disposed_ = true; - if (this.rerender_timeout_id_ !== undefined) { - window.clearTimeout(this.rerender_timeout_id_); - this.rerender_timeout_id_ = undefined; - this.rerender_pending_delay_ms_ = undefined; - } - this.rerender_waiting_for_visibility_ = false; - if (this.visibility_observer_ !== undefined) { - this.visibility_observer_.disconnect(); - this.visibility_observer_ = undefined; - } - this.hide_loading_(); - - window.clearInterval(this.fps_interval_); - delete this.fps_interval_; - this.remove_global_listeners_(); - - if (this.scene_ !== undefined) { - this.scene_.dispose(); - delete this.scene_; - } + this.teardown_(); if (beestat.component.card.three_d.active_instance_ === this) { delete beestat.component.card.three_d.active_instance_; } diff --git a/js/component/floor_plan_entity.js b/js/component/floor_plan_entity.js index 8c2d04a..8f34a83 100644 --- a/js/component/floor_plan_entity.js +++ b/js/component/floor_plan_entity.js @@ -256,3 +256,80 @@ beestat.component.floor_plan_entity.prototype.get_x = function() { beestat.component.floor_plan_entity.prototype.get_y = function() { return this.y_; }; + +/** + * Collect snap x/y coordinates from selected shape collections. + * + * @param {{ + * groups: object[], + * shape_specs: Array<{collection: string, point_mode: string}>, + * should_skip_shape: (function(object, object, object): boolean)= + * }} options + * + * @return {{snap_x: number[], snap_y: number[]}} + */ +beestat.component.floor_plan_entity.prototype.collect_snap_points_ = function(options) { + const snap_x = {}; + const snap_y = {}; + const groups = Array.isArray(options.groups) ? options.groups : []; + const shape_specs = Array.isArray(options.shape_specs) ? options.shape_specs : []; + const should_skip_shape = typeof options.should_skip_shape === 'function' + ? options.should_skip_shape + : function() { + return false; + }; + + groups.forEach(function(group) { + if (group === undefined || group === null) { + return; + } + + shape_specs.forEach(function(shape_spec) { + const shapes = group[shape_spec.collection]; + if (Array.isArray(shapes) !== true) { + return; + } + + shapes.forEach(function(shape) { + if (shape === undefined || shape.editor_hidden === true) { + return; + } + if (should_skip_shape(shape, shape_spec, group) === true) { + return; + } + + if (shape_spec.point_mode === 'point') { + snap_x[Number(shape.x || 0)] = true; + snap_y[Number(shape.y || 0)] = true; + return; + } + + if (Array.isArray(shape.points) !== true) { + return; + } + + shape.points.forEach(function(point) { + const point_x = Number(point.x || 0); + const point_y = Number(point.y || 0); + if (shape_spec.point_mode === 'absolute') { + snap_x[point_x] = true; + snap_y[point_y] = true; + return; + } + + snap_x[point_x + Number(shape.x || 0)] = true; + snap_y[point_y + Number(shape.y || 0)] = true; + }); + }); + }); + }); + + return { + 'snap_x': Object.keys(snap_x).map(function(key) { + return Number(key); + }), + 'snap_y': Object.keys(snap_y).map(function(key) { + return Number(key); + }) + }; +}; diff --git a/js/component/floor_plan_entity/light_source.js b/js/component/floor_plan_entity/light_source.js index ac88ccd..e6da963 100644 --- a/js/component/floor_plan_entity/light_source.js +++ b/js/component/floor_plan_entity/light_source.js @@ -308,66 +308,42 @@ beestat.component.floor_plan_entity.light_source.prototype.after_mouseup_handler */ beestat.component.floor_plan_entity.light_source.prototype.update_snap_points_ = function() { const self = this; - const snap_x = {}; - const snap_y = {}; - - const append_shapes = function(shapes, skip_self_light_source) { - if (Array.isArray(shapes) !== true) { - return; - } - - shapes.forEach(function(shape) { - if (shape.editor_hidden === true) { - return; + const group_below = this.floor_plan_.get_group_below(this.group_); + const groups = [this.group_]; + if (group_below !== undefined) { + groups.push(group_below); + } + const snap_points = this.collect_snap_points_({ + 'groups': groups, + 'shape_specs': [ + { + 'collection': 'rooms', + 'point_mode': 'relative' + }, + { + 'collection': 'surfaces', + 'point_mode': 'relative' + }, + { + 'collection': 'openings', + 'point_mode': 'absolute' + }, + { + 'collection': 'light_sources', + 'point_mode': 'point' } - - if ( - skip_self_light_source === true && + ], + 'should_skip_shape': function(shape, shape_spec) { + return ( + shape_spec.collection === 'light_sources' && shape.light_source_id !== undefined && self.light_source_ !== undefined && self.light_source_.light_source_id === shape.light_source_id - ) { - return; - } - - if (Array.isArray(shape.points) === true) { - shape.points.forEach(function(point) { - const is_opening = shape.opening_id !== undefined; - const absolute_x = is_opening - ? Number(point.x || 0) - : Number(point.x || 0) + Number(shape.x || 0); - const absolute_y = is_opening - ? Number(point.y || 0) - : Number(point.y || 0) + Number(shape.y || 0); - snap_x[absolute_x] = true; - snap_y[absolute_y] = true; - }); - } else { - snap_x[Number(shape.x || 0)] = true; - snap_y[Number(shape.y || 0)] = true; - } - }); - }; - - append_shapes(this.group_.rooms, false); - append_shapes(this.group_.surfaces, false); - append_shapes(this.group_.openings, false); - append_shapes(this.group_.light_sources, true); - - const group_below = this.floor_plan_.get_group_below(this.group_); - if (group_below !== undefined) { - append_shapes(group_below.rooms, false); - append_shapes(group_below.surfaces, false); - append_shapes(group_below.openings, false); - append_shapes(group_below.light_sources, false); - } - - this.snap_x_ = Object.keys(snap_x).map(function(key) { - return Number(key); - }); - this.snap_y_ = Object.keys(snap_y).map(function(key) { - return Number(key); + ); + } }); + this.snap_x_ = snap_points.snap_x; + this.snap_y_ = snap_points.snap_y; }; /** diff --git a/js/component/floor_plan_entity/opening.js b/js/component/floor_plan_entity/opening.js index e43bbd4..9ce3dc6 100644 --- a/js/component/floor_plan_entity/opening.js +++ b/js/component/floor_plan_entity/opening.js @@ -441,58 +441,39 @@ beestat.component.floor_plan_entity.opening.prototype.clear_snap_lines_ = functi */ beestat.component.floor_plan_entity.opening.prototype.update_snap_points_ = function() { const self = this; - const snap_x = {}; - const snap_y = {}; - - const append_shapes = function(shapes, skip_self_opening) { - if (Array.isArray(shapes) !== true) { - return; - } - - shapes.forEach(function(shape) { - if (shape.editor_hidden === true || Array.isArray(shape.points) !== true) { - return; + const group_below = this.floor_plan_.get_group_below(this.group_); + const groups = [this.group_]; + if (group_below !== undefined) { + groups.push(group_below); + } + const snap_points = this.collect_snap_points_({ + 'groups': groups, + 'shape_specs': [ + { + 'collection': 'rooms', + 'point_mode': 'relative' + }, + { + 'collection': 'surfaces', + 'point_mode': 'relative' + }, + { + 'collection': 'openings', + 'point_mode': 'absolute' } - if ( - skip_self_opening === true && + ], + 'should_skip_shape': function(shape, shape_spec) { + return ( + shape_spec.collection === 'openings' && self.opening_ !== undefined && shape.opening_id !== undefined && self.opening_.opening_id !== undefined && shape.opening_id === self.opening_.opening_id - ) { - return; - } - shape.points.forEach(function(point) { - const is_opening = shape.opening_id !== undefined; - const absolute_x = is_opening - ? Number(point.x || 0) - : Number(point.x || 0) + Number(shape.x || 0); - const absolute_y = is_opening - ? Number(point.y || 0) - : Number(point.y || 0) + Number(shape.y || 0); - snap_x[absolute_x] = true; - snap_y[absolute_y] = true; - }); - }); - }; - - append_shapes(this.group_.rooms, false); - append_shapes(this.group_.surfaces, false); - append_shapes(this.group_.openings, true); - - const group_below = this.floor_plan_.get_group_below(this.group_); - if (group_below !== undefined) { - append_shapes(group_below.rooms, false); - append_shapes(group_below.surfaces, false); - append_shapes(group_below.openings, false); - } - - this.snap_x_ = Object.keys(snap_x).map(function(key) { - return Number(key); - }); - this.snap_y_ = Object.keys(snap_y).map(function(key) { - return Number(key); + ); + } }); + this.snap_x_ = snap_points.snap_x; + this.snap_y_ = snap_points.snap_y; }; /** diff --git a/js/component/floor_plan_entity/room.js b/js/component/floor_plan_entity/room.js index 04d1c95..27e0a04 100644 --- a/js/component/floor_plan_entity/room.js +++ b/js/component/floor_plan_entity/room.js @@ -195,9 +195,6 @@ beestat.component.floor_plan_entity.prototype.decorate_walls_ = function(parent) wall_entity.addEventListener('mousedown', function() { wall_entity.set_active(true); }); - wall_entity.addEventListener('mousedown', function() { - wall_entity.set_active(true); - }); // Add toolbar button on activate. wall_entity.addEventListener('activate', function() { @@ -305,60 +302,26 @@ beestat.component.floor_plan_entity.room.prototype.set_active = function(active) * Pre-generate a list of snappable x/y values. */ beestat.component.floor_plan_entity.room.prototype.update_snap_points_ = function() { - const snap_x = {}; - const snap_y = {}; - - // Snap to rooms in this group. - this.group_.rooms.forEach(function(room) { - if (room.editor_hidden === true) { - return; - } - room.points.forEach(function(point) { - snap_x[point.x + room.x] = true; - snap_y[point.y + room.y] = true; - }); - }); - (this.group_.openings || []).forEach(function(opening) { - if (opening.editor_hidden === true || Array.isArray(opening.points) !== true) { - return; - } - opening.points.forEach(function(point) { - // Opening points are stored in absolute editor coordinates. - snap_x[point.x] = true; - snap_y[point.y] = true; - }); - }); - - // Snap to rooms in the group under this one. const group_below = this.floor_plan_.get_group_below(this.group_); + const groups = [this.group_]; if (group_below !== undefined) { - group_below.rooms.forEach(function(room) { - if (room.editor_hidden === true) { - return; - } - room.points.forEach(function(point) { - snap_x[point.x + room.x] = true; - snap_y[point.y + room.y] = true; - }); - }); - (group_below.openings || []).forEach(function(opening) { - if (opening.editor_hidden === true || Array.isArray(opening.points) !== true) { - return; - } - opening.points.forEach(function(point) { - // Opening points are stored in absolute editor coordinates. - snap_x[point.x] = true; - snap_y[point.y] = true; - }); - }); + groups.push(group_below); } - - this.snap_x_ = Object.keys(snap_x).map(function(key) { - return Number(key); - }); - this.snap_y_ = Object.keys(snap_y).map(function(key) { - return Number(key); + const snap_points = this.collect_snap_points_({ + 'groups': groups, + 'shape_specs': [ + { + 'collection': 'rooms', + 'point_mode': 'relative' + }, + { + 'collection': 'openings', + 'point_mode': 'absolute' + } + ] }); + this.snap_x_ = snap_points.snap_x; + this.snap_y_ = snap_points.snap_y; }; /** diff --git a/js/component/floor_plan_entity/surface.js b/js/component/floor_plan_entity/surface.js index 80d9392..bbc6a77 100644 --- a/js/component/floor_plan_entity/surface.js +++ b/js/component/floor_plan_entity/surface.js @@ -165,43 +165,28 @@ beestat.component.floor_plan_entity.surface.prototype.set_active = function(acti * Pre-generate a list of snappable x/y values. */ beestat.component.floor_plan_entity.surface.prototype.update_snap_points_ = function() { - const snap_x = {}; - const snap_y = {}; - - const append_shapes = function(shapes) { - if (shapes === undefined) { - return; - } - - shapes.forEach(function(shape) { - if (shape.editor_hidden === true || Array.isArray(shape.points) !== true) { - return; - } - shape.points.forEach(function(point) { - const is_opening = shape.opening_id !== undefined; - const absolute_x = is_opening ? Number(point.x || 0) : Number(point.x || 0) + Number(shape.x || 0); - const absolute_y = is_opening ? Number(point.y || 0) : Number(point.y || 0) + Number(shape.y || 0); - snap_x[absolute_x] = true; - snap_y[absolute_y] = true; - }); - }); - }; - - append_shapes(this.group_.rooms); - append_shapes(this.group_.surfaces); - append_shapes(this.group_.openings); - const group_below = this.floor_plan_.get_group_below(this.group_); + const groups = [this.group_]; if (group_below !== undefined) { - append_shapes(group_below.rooms); - append_shapes(group_below.surfaces); - append_shapes(group_below.openings); + groups.push(group_below); } - - this.snap_x_ = Object.keys(snap_x).map(function(key) { - return Number(key); - }); - this.snap_y_ = Object.keys(snap_y).map(function(key) { - return Number(key); + const snap_points = this.collect_snap_points_({ + 'groups': groups, + 'shape_specs': [ + { + 'collection': 'rooms', + 'point_mode': 'relative' + }, + { + 'collection': 'surfaces', + 'point_mode': 'relative' + }, + { + 'collection': 'openings', + 'point_mode': 'absolute' + } + ] }); + this.snap_x_ = snap_points.snap_x; + this.snap_y_ = snap_points.snap_y; }; diff --git a/js/component/scene.js b/js/component/scene.js index f343a3a..026924c 100644 --- a/js/component/scene.js +++ b/js/component/scene.js @@ -112,34 +112,6 @@ beestat.component.scene.room_floor_thickness = 6; */ beestat.component.scene.surface_z_lift = 0.75; -/** - * Default number of decorative trees to place around the environment. - * - * @type {number} - */ -beestat.component.scene.environment_tree_count = 14; - -/** - * Toggle tree foliage visibility for environment trees. - * - * @type {boolean} - */ -beestat.component.scene.environment_tree_foliage_enabled = true; - -/** - * Debug opacity for round/oval canopies when foliage is visible. - * - * @type {number} - */ -beestat.component.scene.debug_tree_canopy_opacity = 1; - -/** - * Keep round/oval branch meshes visible even when foliage is visible. - * - * @type {boolean} - */ -beestat.component.scene.debug_show_branches_with_foliage = true; - /** * Round/oval primary branch density in branches per height unit. * @@ -221,27 +193,6 @@ beestat.component.scene.sun_light_intensity = 0.6; */ beestat.component.scene.moon_light_intensity = 0.13125; -/** - * Peak per-room interior light intensity used at night. - * - * @type {number} - */ -beestat.component.scene.interior_light_intensity = 0.9; - -/** - * Max number of interior point lights allowed to cast shadows. - * - * @type {number} - */ -beestat.component.scene.interior_light_shadow_max = 1; - -/** - * Number of star sprites generated in the sky dome. - * - * @type {number} - */ -beestat.component.scene.star_count = 900; - /** * Minimum star sprite size. * @@ -428,7 +379,6 @@ beestat.component.scene.prototype.rerender = 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. if (this.rendered_ === true) { @@ -550,8 +500,6 @@ beestat.component.scene.prototype.reset_celestial_lights_for_rerender_ = functio 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_; @@ -591,19 +539,6 @@ beestat.component.scene.prototype.get_scene_setting_ = function(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. * @@ -695,25 +630,12 @@ beestat.component.scene.prototype.decorate_ = function(parent) { this.scene_settings_ = {}; } - this.debug_ = { - 'axes': false, - 'directional_light_helpers': false, - 'sun_light_helper': false, - 'moon_light_helper': false, - 'watcher': false, - 'roof_edges': false, - 'straight_skeleton': false, - 'openings': false, - 'opening_cutters': false, - 'hide_tree_branches': false, - 'light_source_orbs': false - }; this.room_interaction_enabled_ = true; this.width_ = this.initial_width_ || this.state_.scene_width || 800; this.height_ = 500; - this.add_scene_(parent); + this.add_scene_(); this.add_renderer_(parent); this.add_camera_(); this.add_controls_(parent); @@ -783,37 +705,9 @@ beestat.component.scene.prototype.set_initial_camera_state = function(camera_sta /** * Add the scene. Everything gets added to the scene. - * - * @param {rocket.Elements} parent Parent */ -beestat.component.scene.prototype.add_scene_ = function(parent) { +beestat.component.scene.prototype.add_scene_ = function() { this.scene_ = new THREE.Scene(); - - if (this.debug_.axes === true) { - this.scene_.add( - new THREE.AxesHelper(800) - .setColors( - 0xff0000, - 0x00ff00, - 0x0000ff - ) - ); - } - - if (this.debug_.watcher === true) { - this.debug_info_ = {}; - this.debug_container_ = $.createElement('div').style({ - 'position': 'absolute', - 'top': (beestat.style.size.gutter / 2), - 'left': (beestat.style.size.gutter / 2), - 'padding': (beestat.style.size.gutter / 2), - 'background': 'rgba(0, 0, 0, 0.5)', - 'color': '#fff', - 'font-family': 'Consolas, Courier, Monospace', - 'white-space': 'pre' - }); - parent.appendChild(this.debug_container_); - } }; /** @@ -1034,185 +928,6 @@ beestat.component.scene.prototype.update_ = function() { } this.update_tree_foliage_season_(); - - // 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_(); - } -}; - -/** - * Add a helpful debug window that can be refreshed with the contents of - * this.debug_info_. - * - * @param {rocket.Elements} parent - */ -beestat.component.scene.prototype.add_debug_ = function(parent) { - if (this.debug_.watcher === true) { - this.debug_info_ = {}; - this.debug_container_ = $.createElement('div').style({ - 'position': 'absolute', - 'top': (beestat.style.size.gutter / 2), - 'left': (beestat.style.size.gutter / 2), - 'padding': (beestat.style.size.gutter / 2), - 'background': 'rgba(0, 0, 0, 0.5)', - 'color': '#fff', - 'font-family': 'Consolas, Courier, Monospace', - 'white-space': 'pre' - }); - parent.appendChild(this.debug_container_); - } -}; - -/** - * Update the debug window. - */ -beestat.component.scene.prototype.update_debug_ = function() { - if (this.debug_.watcher === true) { - this.debug_container_.innerHTML( - JSON.stringify(this.debug_info_, null, 2) - ); - } -}; - -/** - * Add red outline visualization for exposed ceiling areas (future roof locations). - */ -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.floor_plan_group_.add(roof_outlines_layer); - this.layers_['roof_outlines'] = roof_outlines_layer; - - // Render each exposed area as red outline - exposed_areas.forEach(function(area) { - area.polygons.forEach(function(polygon) { - if (polygon.length < 3) { - return; - } - - // Create line points - const points = []; - polygon.forEach(function(point) { - points.push(new THREE.Vector3(point.x, point.y, area.ceiling_z)); - }); - // Close the loop - points.push(new THREE.Vector3(polygon[0].x, polygon[0].y, area.ceiling_z)); - - // Create red line - const geometry = new THREE.BufferGeometry().setFromPoints(points); - const material = new THREE.LineBasicMaterial({ - 'color': 0xff0000, // Red - 'linewidth': 2 - }); - - const line = new THREE.Line(geometry, material); - line.layers.set(beestat.component.scene.layer_visible); - roof_outlines_layer.add(line); - }); - }); -}; - -/** - * Visualize the straight skeleton for each roof polygon with debug lines. - */ -beestat.component.scene.prototype.add_roof_skeleton_debug_ = function() { - const skeleton_builder = this.get_skeleton_builder_(); - if (skeleton_builder === undefined) { - 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.floor_plan_group_.add(skeleton_debug_layer); - this.layers_['roof_skeleton_debug'] = skeleton_debug_layer; - - let total_polygons = 0; - let successful_skeletons = 0; - - // Process each exposed area - exposed_areas.forEach(function(area) { - area.polygons.forEach(function(polygon) { - if (polygon.length < 3) { - return; - } - - total_polygons++; - - try { - // Simplify polygon to remove self-intersections and clean up topology - // This splits complex polygons (L-shapes, T-shapes) into simpler ones - const simplified = ClipperLib.Clipper.SimplifyPolygon( - polygon, - ClipperLib.PolyFillType.pftNonZero - ); - - // SimplifyPolygon can return multiple polygons if the original was self-intersecting - simplified.forEach(function(simple_polygon) { - if (simple_polygon.length < 3) { - return; - } - - // Convert ClipperLib format {x, y} to SkeletonBuilder format [[x, y], ...] - const ring = simple_polygon.map(function(point) { - return [point.x, point.y]; - }); - // Close the ring by repeating the first point - ring.push([simple_polygon[0].x, simple_polygon[0].y]); - - // Build the straight skeleton - const coordinates = [ring]; // Array of rings (outer ring only, no holes) - const result = skeleton_builder.buildFromPolygon(coordinates); - - if (!result) { - return; - } - - successful_skeletons++; - - // Visualize each skeleton polygon face with blue lines - result.polygons.forEach(function(face) { - if (face.length < 2) { - return; - } - - // Create line points from the face vertices - const points = []; - face.forEach(function(vertex_index) { - const vertex = result.vertices[vertex_index]; - points.push(new THREE.Vector3(vertex[0], vertex[1], area.ceiling_z)); - }); - // Close the loop - const first_vertex = result.vertices[face[0]]; - points.push(new THREE.Vector3(first_vertex[0], first_vertex[1], area.ceiling_z)); - - // Create blue line for skeleton edges - const geometry = new THREE.BufferGeometry().setFromPoints(points); - const material = new THREE.LineBasicMaterial({ - 'color': 0x00ffff, // Cyan - 'linewidth': 1 - }); - - const line = new THREE.Line(geometry, material); - line.layers.set(beestat.component.scene.layer_visible); - skeleton_debug_layer.add(line); - }); - }); // End simplified.forEach - } catch (error) { - console.error('Error building skeleton for polygon:', error, polygon); - } - }); - }); - }; /** diff --git a/js/component/scene/environment.js b/js/component/scene/environment.js index cc5dc44..720ada8 100644 --- a/js/component/scene/environment.js +++ b/js/component/scene/environment.js @@ -66,9 +66,6 @@ beestat.component.scene.prototype.update_tree_foliage_season_ = function() { } mesh.material.color.copy(state.color); mesh.userData.base_tree_foliage_color = state.color.getHex(); - mesh.material.opacity = beestat.component.scene.debug_tree_canopy_opacity; - 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 = tree_foliage_enabled === true; } @@ -78,11 +75,7 @@ beestat.component.scene.prototype.update_tree_foliage_season_ = function() { for (let i = 0; i < this.tree_branch_groups_.length; i++) { const branch_group = this.tree_branch_groups_[i]; 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 && - tree_branch_enabled === true; + branch_group.visible = tree_branch_enabled === true; } } } diff --git a/js/component/scene/layout.js b/js/component/scene/layout.js index f0953fa..00a0128 100644 --- a/js/component/scene/layout.js +++ b/js/component/scene/layout.js @@ -91,18 +91,7 @@ beestat.component.scene.prototype.add_floor_plan_ = function() { self.add_walls_(walls_layer, group); }); - let opening_cutter_debug_layer; - if (this.debug_.opening_cutters === true) { - opening_cutter_debug_layer = new THREE.Group(); - this.floor_plan_group_.add(opening_cutter_debug_layer); - this.layers_['opening_cutters_debug'] = opening_cutter_debug_layer; - } - - this.apply_opening_cuts_( - walls_layer, - floor_plan, - opening_cutter_debug_layer - ); + this.apply_opening_cuts_(walls_layer, floor_plan); const openings_layer = new THREE.Group(); this.floor_plan_group_.add(openings_layer); @@ -119,27 +108,9 @@ beestat.component.scene.prototype.add_floor_plan_ = function() { self.add_light_sources_(light_sources_layer, group); }); - if (this.debug_.openings === true) { - const openings_debug_layer = new THREE.Group(); - this.floor_plan_group_.add(openings_debug_layer); - this.layers_['openings_debug'] = openings_debug_layer; - - floor_plan.data.groups.forEach(function(group) { - self.add_openings_debug_(openings_debug_layer, group); - }); - } - // Add roofs using straight skeleton this.add_roofs_(); - if (this.debug_.roof_edges) { - this.add_roof_outline_debug_(); - } - - if (this.debug_.straight_skeleton) { - this.add_roof_skeleton_debug_(); - } - this.add_environment_(); }; diff --git a/js/component/scene/light.js b/js/component/scene/light.js index 0ed5d08..583e128 100644 --- a/js/component/scene/light.js +++ b/js/component/scene/light.js @@ -40,15 +40,6 @@ beestat.component.scene.prototype.add_directional_lights_ = function() { 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); - }); - } }; @@ -80,7 +71,6 @@ beestat.component.scene.prototype.add_static_lights_ = function() { // Add directional fill lights this.add_directional_lights_(); - this.apply_appearance_rotation_to_lights_(); }; @@ -209,14 +199,6 @@ beestat.component.scene.prototype.add_celestial_lights_ = function() { this.sun_glow_sprite_.renderOrder = 11; this.sun_visual_group_.add(this.sun_glow_sprite_); - if (this.debug_.sun_light_helper === true) { - this.sun_light_helper_ = new THREE.DirectionalLightHelper( - this.sun_light_, - 100 - ); - this.celestial_light_group_.add(this.sun_light_helper_); - } - // Moon light this.moon_light_ = new THREE.DirectionalLight( 0xaaccff, // Cool bluish color for moonlight @@ -258,17 +240,7 @@ beestat.component.scene.prototype.add_celestial_lights_ = function() { this.moon_sprite_.scale.set(405, 405, 1); this.moon_visual_group_.add(this.moon_sprite_); - if (this.debug_.moon_light_helper === true) { - this.moon_light_helper_ = new THREE.DirectionalLightHelper( - this.moon_light_, - 100 - ); - this.celestial_light_group_.add(this.moon_light_helper_); - } - this.add_stars_(); - - this.apply_appearance_rotation_to_lights_(); }; @@ -397,17 +369,6 @@ beestat.component.scene.prototype.update_sun_path_arc_ = function(date, latitude }; -/** - * Static (ambient/directional fill) lights should not rotate with floor-plan - * appearance. Celestial lights are handled in update_celestial_lights_. - */ -beestat.component.scene.prototype.apply_appearance_rotation_to_lights_ = function() { - if (this.static_light_group_ !== undefined) { - this.static_light_group_.rotation.y = 0; - } -}; - - /** * Build target sun colors from altitude. * Warmer near horizon and more neutral when the sun is high. @@ -506,12 +467,6 @@ beestat.component.scene.prototype.update_celestial_lights_ = function(date, lati Math.min(1, (-sun_pos.altitude - 0.05) / 0.25) ); - const interior_night_factor = Math.max( - 0, - Math.min(1, (-sun_pos.altitude + 0.03) / 0.3) - ); - this.target_interior_light_intensity_ = - beestat.component.scene.interior_light_intensity * interior_night_factor; const max_sun_intensity = Math.max(0.0001, Number(beestat.component.scene.sun_light_intensity || 0.0001)); const normalized_sun_brightness = Math.max( 0, @@ -576,18 +531,6 @@ beestat.component.scene.prototype.update_celestial_lights_ = function(date, lati : moon_intensity; } this.target_moon_intensity_ *= cloud_dimming; - - // 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) { - this.moon_light_.updateMatrixWorld(); - this.moon_light_.target.updateMatrixWorld(); - this.moon_light_helper_.update(); - } }; @@ -607,14 +550,6 @@ beestat.component.scene.prototype.update_celestial_light_intensities_ = function if (this.target_moon_intensity_ === undefined) { this.target_moon_intensity_ = 0; } - if (this.target_interior_light_intensity_ === undefined) { - const hour = this.date_ !== undefined ? Number(this.date_.format('H')) : 12; - this.target_interior_light_intensity_ = ( - (hour >= 19 || hour <= 5) - ? beestat.component.scene.interior_light_intensity - : 0 - ); - } if (this.target_light_source_intensity_multiplier_ === undefined) { const hour = this.date_ !== undefined ? Number(this.date_.format('H')) : 12; this.target_light_source_intensity_multiplier_ = (hour >= 19 || hour <= 5) ? 1 : 0; @@ -640,11 +575,6 @@ beestat.component.scene.prototype.update_celestial_light_intensities_ = function const color_lerp_factor = 0.08; this.sun_light_.color.lerp(this.target_sun_light_color_, color_lerp_factor); - if (this.interior_lights_ !== undefined) { - this.interior_lights_.forEach((light) => { - light.intensity += (this.target_interior_light_intensity_ - light.intensity) * lerp_factor; - }); - } if (Array.isArray(this.light_sources_) === true) { this.light_sources_.forEach((light) => { const base_intensity = Number(light.userData.base_intensity || 0); @@ -789,29 +719,6 @@ beestat.component.scene.prototype.add_light_sources_ = function(layer, group) { this.light_sources_ = []; } - if (this.debug_.light_source_orbs === true) { - if (this.light_source_marker_geometry_ === undefined) { - this.light_source_marker_geometry_ = new THREE.SphereGeometry(2.2, 12, 12); - } - if (this.light_source_glow_geometry_ === undefined) { - this.light_source_glow_geometry_ = new THREE.SphereGeometry(6, 16, 16); - } - if (this.light_source_marker_material_ === undefined) { - this.light_source_marker_material_ = new THREE.MeshStandardMaterial({ - 'roughness': 0.2, - 'metalness': 0.05 - }); - } - if (this.light_source_glow_material_ === undefined) { - this.light_source_glow_material_ = new THREE.MeshBasicMaterial({ - 'transparent': true, - 'opacity': 0.28, - 'depthWrite': false, - 'blending': THREE.AdditiveBlending - }); - } - } - const group_elevation = Number(group.elevation || 0); const floor_thickness = Number(beestat.component.scene.room_floor_thickness || 0); const user_light_cast_shadows = this.get_scene_setting_('light_user_cast_shadows') === true; @@ -830,33 +737,6 @@ beestat.component.scene.prototype.add_light_sources_ = function(layer, group) { const light_intensity = 0.9 * intensity_level; const light_color = this.get_light_color_from_temperature_(light_source.temperature_k); - if (this.debug_.light_source_orbs === true) { - const marker = new THREE.Mesh( - this.light_source_marker_geometry_, - this.light_source_marker_material_.clone() - ); - marker.material.color.copy(light_color); - marker.material.emissive.copy(light_color); - marker.material.emissiveIntensity = 0.9 + (intensity_level * 0.35); - marker.position.set(x, y, z); - marker.castShadow = false; - marker.receiveShadow = false; - marker.userData.is_light_source = true; - layer.add(marker); - - const glow = new THREE.Mesh( - this.light_source_glow_geometry_, - this.light_source_glow_material_.clone() - ); - glow.material.color.copy(light_color); - glow.material.opacity = 0.15 + (intensity_level * 0.08); - glow.position.set(x, y, z); - glow.castShadow = false; - glow.receiveShadow = false; - glow.userData.is_light_source = true; - layer.add(glow); - } - const light = new THREE.PointLight(light_color, light_intensity, 240, 2); light.userData.base_intensity = light_intensity; light.intensity = light_intensity * Number(this.target_light_source_intensity_multiplier_ || 0); @@ -910,61 +790,4 @@ beestat.component.scene.prototype.update_user_light_shadow_settings_ = function( }; -/** - * Add warm interior point lights, one per room. Lights are invisible and their - * intensity is animated based on night/day state. - * - * @param {object} floor_plan The floor plan data. - */ -beestat.component.scene.prototype.add_interior_lights_ = function(floor_plan) { - this.interior_lights_ = []; - this.interior_light_group_ = new THREE.Group(); - this.floor_plan_group_.add(this.interior_light_group_); - this.layers_['interior_lights'] = this.interior_light_group_; - let shadowed_light_count = 0; - - floor_plan.data.groups.forEach(function(group) { - group.rooms.forEach((room) => { - if (room.points === undefined || room.points.length < 3) { - return; - } - - const geojson_polygon = []; - room.points.forEach(function(point) { - geojson_polygon.push([ - point.x, - point.y - ]); - }); - const light_point = polylabel([geojson_polygon]); - - const group_elevation = Number(group.elevation || 0); - const room_height = Number(room.height || group.height || 96); - const room_elevation = Number(room.elevation !== undefined ? room.elevation : group_elevation); - - const light = new THREE.PointLight(0xffd79a, 0, 170, 2); - light.position.set( - Number(room.x || 0) + light_point[0], - Number(room.y || 0) + light_point[1], - -room_elevation - (room_height * 0.45) - ); - if (shadowed_light_count < beestat.component.scene.interior_light_shadow_max) { - light.castShadow = 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 = 220; - shadowed_light_count++; - } else { - light.castShadow = false; - } - - this.interior_light_group_.add(light); - this.interior_lights_.push(light); - }); - }, this); -}; diff --git a/js/component/scene/opening.js b/js/component/scene/opening.js index cce8e2b..09f027e 100644 --- a/js/component/scene/opening.js +++ b/js/component/scene/opening.js @@ -160,40 +160,15 @@ beestat.component.scene.prototype.get_opening_center_z_ = function(group, openin }; -/** - * Add a debug wireframe for an opening cutter. - * - * @param {THREE.Group} layer The debug layer. - * @param {THREE.Mesh} cutter The cutter mesh. - */ -beestat.component.scene.prototype.add_opening_cutter_debug_ = function(layer, cutter) { - const edges_geometry = new THREE.EdgesGeometry(cutter.geometry); - const wireframe = new THREE.LineSegments( - edges_geometry, - new THREE.LineBasicMaterial({ - 'color': 0xff7700 - }) - ); - wireframe.position.copy(cutter.position); - wireframe.rotation.copy(cutter.rotation); - wireframe.scale.copy(cutter.scale); - wireframe.layers.set(beestat.component.scene.layer_visible); - - layer.add(wireframe); -}; - - /** * Subtract opening cutters from wall meshes. * * @param {THREE.Group} walls_layer The wall mesh layer. * @param {object} floor_plan The floor plan data. - * @param {THREE.Group=} opening_cutter_debug_layer Optional debug cutter layer. */ beestat.component.scene.prototype.apply_opening_cuts_ = function( walls_layer, - floor_plan, - opening_cutter_debug_layer + floor_plan ) { if (window.CSG === undefined || typeof window.CSG.subtract !== 'function') { return; @@ -227,10 +202,6 @@ beestat.component.scene.prototype.apply_opening_cuts_ = function( return; } - if (opening_cutter_debug_layer !== undefined) { - this.add_opening_cutter_debug_(opening_cutter_debug_layer, cutter); - } - const cutter_box = new THREE.Box3().setFromObject(cutter); group_wall_meshes.forEach(function(wall_mesh) { @@ -280,50 +251,6 @@ beestat.component.scene.prototype.apply_opening_cuts_ = function( }; -/** - * Add red wireframe boxes to visualize opening placement in 3D. - * - * @param {THREE.Group} layer The layer to add opening debug to. - * @param {object} group The floor plan group. - */ -beestat.component.scene.prototype.add_openings_debug_ = function(layer, group) { - if (group.openings === undefined || group.openings.length === 0) { - return; - } - - const wall_thickness = beestat.component.scene.wall_thickness; - - group.openings.forEach(function(opening) { - const opening_line = this.get_opening_line_params_(opening); - const width = opening_line.width; - const height = Math.max(1, Number(opening.height || this.get_opening_default_height_(opening.type))); - const center_z = this.get_opening_center_z_(group, opening, height); - - const geometry = new THREE.BoxGeometry( - width, - wall_thickness, - height - ); - - const edges_geometry = new THREE.EdgesGeometry(geometry); - const wireframe = new THREE.LineSegments( - edges_geometry, - new THREE.LineBasicMaterial({ - 'color': 0xff0000 - }) - ); - - wireframe.position.x = opening_line.center_x; - wireframe.position.y = opening_line.center_y; - wireframe.position.z = center_z; - wireframe.rotation.z = opening_line.rotation_radians; - wireframe.layers.set(beestat.component.scene.layer_visible); - - layer.add(wireframe); - }, this); -}; - - /** * Add 3D opening fixtures. * diff --git a/js/component/scene/tree.js b/js/component/scene/tree.js index dca6ea3..f5ed5f5 100644 --- a/js/component/scene/tree.js +++ b/js/component/scene/tree.js @@ -670,15 +670,11 @@ beestat.component.scene.prototype.create_round_tree_ = function(height, max_diam const branch_axis = new THREE.Vector3(0, 0, -1); const foliage = new THREE.Group(); foliage.userData.is_environment = true; - const canopy_opacity = beestat.component.scene.debug_tree_canopy_opacity; const foliage_material = new THREE.MeshStandardMaterial({ 'color': 0x4f9f2f, 'roughness': 0.82, 'metalness': 0.0, 'flatShading': true, - 'transparent': canopy_opacity < 1, - 'opacity': canopy_opacity, - 'depthWrite': canopy_opacity >= 1, 'side': THREE.DoubleSide }); const create_canopy_from_branch_function_ = function() { @@ -1063,8 +1059,7 @@ beestat.component.scene.prototype.create_round_tree_ = function(height, max_diam if (has_foliage === true) { this.tree_branch_groups_.push(branches); } - branches.visible = - this.debug_.hide_tree_branches !== true; + branches.visible = true; tree.add(branches); if (has_foliage === true) { tree.add(foliage); diff --git a/js/component/scene/weather.js b/js/component/scene/weather.js index a4820ae..21fc8fe 100644 --- a/js/component/scene/weather.js +++ b/js/component/scene/weather.js @@ -3,40 +3,6 @@ */ -/** - * Set weather on the floor-plan appearance. - * - * @param {string} weather - * - * @return {beestat.component.scene} - */ -beestat.component.scene.prototype.set_weather = function(weather) { - const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_]; - if (floor_plan.data.appearance === undefined) { - floor_plan.data.appearance = {}; - } - const normalized_weather = beestat.weather.get_settings_(weather).condition; - floor_plan.data.appearance.weather = normalized_weather; - const weather_settings = { - 'cloud_density': beestat.weather.get_cloud_density(normalized_weather), - 'cloud_darkness': beestat.weather.get_cloud_darkness(normalized_weather), - 'rain_density': beestat.weather.get_rain_density(normalized_weather), - 'snow_density': beestat.weather.get_snow_density(normalized_weather), - 'lightning_frequency': beestat.weather.get_lightning_frequency(normalized_weather), - 'wind_speed': beestat.weather.get_wind_speed(normalized_weather) - }; - this.set_scene_settings(weather_settings, { - 'rerender': false - }); - - if (this.rendered_ === true) { - this.update_(); - } - - return this; -}; - - /** * Get design count at density 1 for a weather channel. *