diff --git a/api/floor_plan.php b/api/floor_plan.php
new file mode 100644
index 0000000..d9804c3
--- /dev/null
+++ b/api/floor_plan.php
@@ -0,0 +1,20 @@
+ [
+ 'read_id',
+ 'update',
+ 'create',
+ 'delete'
+ ],
+ 'public' => []
+ ];
+
+}
diff --git a/api/index.php b/api/index.php
index 2c154a8..6997a41 100644
--- a/api/index.php
+++ b/api/index.php
@@ -36,8 +36,15 @@ set_exception_handler([$request, 'exception_handler']);
// The shutdown handler will output the response.
register_shutdown_function([$request, 'shutdown_handler']);
+$post_body = file_get_contents('php://input');
+if($post_body !== '') {
+ $data = json_decode($post_body, true);
+} else {
+ $data = $_REQUEST;
+}
+
// Go!
-$request->process($_REQUEST);
+$request->process($data);
// Useful function
function array_median($array) {
diff --git a/css/dashboard.css b/css/dashboard.css
index b9b1a6f..6a61e10 100644
--- a/css/dashboard.css
+++ b/css/dashboard.css
@@ -252,7 +252,7 @@ input[type=checkbox] {
background: transparent;
top: 5px;
left: 4px;
- border: 3px solid #20bf6b;
+ border: 3px solid #2d98da; /* lightblue.base */
border-top: none;
border-right: none;
transform: rotate(-45deg);
@@ -268,6 +268,49 @@ input[type=checkbox] {
opacity: 0.25;
}
+input[type=radio] {
+ visibility: hidden;
+}
+
+.radio {
+ position: relative;
+ user-select: none;
+ margin-bottom: 4px;
+}
+
+.radio label {
+ cursor: pointer;
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ top: 0;
+ left: 0;
+ border-radius: 4px;
+ box-shadow: inset 0px 1px 1px rgba(0,0,0,0.5), 0px 1px 0px rgba(255, 255, 255, 0.4);
+ background: #37474f; /* bluegray light */
+}
+
+.radio label::after {
+ opacity: 0;
+ content: '';
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ background: #2d98da; /* lightblue.base */
+ top: 4px;
+ left: 4px;
+ transition: opacity 200ms ease;
+ border-radius: 2px;
+}
+
+.radio input[type=radio]:checked + label:after {
+ opacity: 1;
+}
+
+.radio input[type=radio]:disabled + label:after {
+ opacity: 0.25;
+}
+
/**
* This is a stripped down version of https://flexgridlite.elliotdahl.com/
@@ -385,6 +428,8 @@ input[type=checkbox] {
.icon.calendar_edit:before { content: "\F08A7"; }
.icon.calendar_range:before { content: "\F0679"; }
.icon.cancel:before { content: "\F073A"; }
+.icon.card_plus_outline:before { content: "\F1200"; }
+.icon.card_remove_outline:before { content: "\F1605"; }
.icon.cash:before { content: "\F0114"; }
.icon.chart_bar_stacked:before { content: "\F076A"; }
.icon.chart_line:before { content: "\F012A"; }
@@ -400,12 +445,15 @@ input[type=checkbox] {
.icon.earth:before { content: "\F01E7"; }
.icon.email:before { content: "\F01EE"; }
.icon.exit_to_app:before { content: "\F0206"; }
-.icon.eye_circle:before { content: "\F0B94"; }
.icon.eye:before { content: "\F0208"; }
+.icon.eye_circle:before { content: "\F0B94"; }
.icon.eye_off:before { content: "\F0209"; }
.icon.fan:before { content: "\F0210"; }
.icon.fire:before { content: "\F0238"; }
+.icon.floor_plan:before { content: "\F0821"; }
.icon.google_play:before { content: "\F02BC"; }
+.icon.grid:before { content: "\F02C1"; }
+.icon.grid_off:before { content: "\F02C2"; }
.icon.heart:before { content: "\F02D1"; }
.icon.help_circle:before { content: "\F02D7"; }
.icon.home:before { content: "\F02DC"; }
@@ -414,17 +462,20 @@ input[type=checkbox] {
.icon.information:before { content: "\F02FC"; }
.icon.key:before { content: "\F0306"; }
.icon.layers:before { content: "\F0328"; }
-.icon.magnify_minus:before { content: "\F034A"; }
+.icon.layers_plus:before { content: "\F0E4D"; }
+.icon.magnify_close:before { content: "\F0980"; }
+.icon.magnify_plus_outline:before { content: "\F06ED"; }
+.icon.magnify_minus_outline:before { content: "\F06EC"; }
.icon.map_marker:before { content: "\F05F8"; }
.icon.menu_down:before { content: "\F035D"; }
.icon.menu_up:before { content: "\F0360"; }
.icon.message:before { content: "\F0361"; }
.icon.network_strength_4:before { content: "\F08FA"; }
.icon.network_strength_off:before { content: "\F08FC"; }
-.icon.numeric_1_box:before { content: "\F03A4"; }
-.icon.numeric_4_box:before { content: "\F03AD"; }
+/*.icon.numeric_1_box:before { content: "\F03A4"; }
.icon.numeric_3_box:before { content: "\F03AA"; }
-.icon.numeric_7_box:before { content: "\F03B6"; }
+.icon.numeric_4_box:before { content: "\F03AD"; }
+.icon.numeric_7_box:before { content: "\F03B6"; }*/
.icon.open_in_new:before { content: "\F03CC"; }
.icon.patreon:before { content: "\F0882"; }
.icon.pound:before { content: "\F0423"; }
@@ -437,6 +488,8 @@ input[type=checkbox] {
.icon.tune:before { content: "\F062E"; }
.icon.twitter:before { content: "\F0544"; }
.icon.update:before { content: "\F06B0"; }
+.icon.vector_square_plus:before { content: "\F18DB"; }
+.icon.vector_square_remove:before { content: "\F18DC"; }
.icon.view_quilt:before { content: "\F0574"; }
.icon.water_off:before { content: "\F058D"; }
.icon.water_percent:before { content: "\F058E"; }
@@ -455,6 +508,88 @@ input[type=checkbox] {
.icon.wifi_strength_1_alert:before { content: "\F0920"; }
.icon.wifi_strength_4:before { content: "\F0928"; }
.icon.zigbee:before { content: "\F0D41"; }
+.icon.home_plus:before { content: "\F0975"; }
+.icon.home_switch:before { content: "\F1794"; }
+.icon.home_remove:before { content: "\F1247"; }
+.icon.arrow_expand_vertical:before { content: "\F084F"; }
+.icon.label:before { content: "\F0315"; }
+
+.icon.numeric_0:before { content: "\F0B39"; }
+.icon.numeric_0_box:before { content: "\F03A1"; }
+.icon.numeric_1:before { content: "\F0B3A"; }
+.icon.numeric_1_box:before { content: "\F03A4"; }
+.icon.numeric_10:before { content: "\F0FE9"; }
+.icon.numeric_10_box:before { content: "\F0F7D"; }
+.icon.numeric_2:before { content: "\F0B3B"; }
+.icon.numeric_2_box:before { content: "\F03A7"; }
+.icon.numeric_3:before { content: "\F0B3C"; }
+.icon.numeric_3_box:before { content: "\F03AA"; }
+.icon.numeric_4:before { content: "\F0B3D"; }
+.icon.numeric_4_box:before { content: "\F03AD"; }
+.icon.numeric_5:before { content: "\F0B3E"; }
+.icon.numeric_5_box:before { content: "\F03B1"; }
+.icon.numeric_6:before { content: "\F0B3F"; }
+.icon.numeric_6_box:before { content: "\F03B3"; }
+.icon.numeric_7:before { content: "\F0B40"; }
+.icon.numeric_7_box:before { content: "\F03B6"; }
+.icon.numeric_8:before { content: "\F0B41"; }
+.icon.numeric_8_box:before { content: "\F03B9"; }
+.icon.numeric_9:before { content: "\F0B42"; }
+.icon.numeric_9_box:before { content: "\F03BC"; }
+.icon.alpha_a:before { content: "\F0AEE"; }
+.icon.alpha_a_box:before { content: "\F0B08"; }
+.icon.alpha_b:before { content: "\F0AEF"; }
+.icon.alpha_b_box:before { content: "\F0B09"; }
+.icon.alpha_c:before { content: "\F0AF0"; }
+.icon.alpha_c_box:before { content: "\F0B0A"; }
+.icon.alpha_d:before { content: "\F0AF1"; }
+.icon.alpha_d_box:before { content: "\F0B0B"; }
+.icon.alpha_e:before { content: "\F0AF2"; }
+.icon.alpha_e_box:before { content: "\F0B0C"; }
+.icon.alpha_f:before { content: "\F0AF3"; }
+.icon.alpha_f_box:before { content: "\F0B0D"; }
+.icon.alpha_g:before { content: "\F0AF4"; }
+.icon.alpha_g_box:before { content: "\F0B0E"; }
+.icon.alpha_h:before { content: "\F0AF5"; }
+.icon.alpha_h_box:before { content: "\F0B0F"; }
+.icon.alpha_i:before { content: "\F0AF6"; }
+.icon.alpha_i_box:before { content: "\F0B10"; }
+.icon.alpha_j:before { content: "\F0AF7"; }
+.icon.alpha_j_box:before { content: "\F0B11"; }
+.icon.alpha_k:before { content: "\F0AF8"; }
+.icon.alpha_k_box:before { content: "\F0B12"; }
+.icon.alpha_l:before { content: "\F0AF9"; }
+.icon.alpha_l_box:before { content: "\F0B13"; }
+.icon.alpha_m:before { content: "\F0AFA"; }
+.icon.alpha_m_box:before { content: "\F0B14"; }
+.icon.alpha_n:before { content: "\F0AFB"; }
+.icon.alpha_n_box:before { content: "\F0B15"; }
+.icon.alpha_o:before { content: "\F0AFC"; }
+.icon.alpha_o_box:before { content: "\F0B16"; }
+.icon.alpha_p:before { content: "\F0AFD"; }
+.icon.alpha_p_box:before { content: "\F0B17"; }
+.icon.alpha_q:before { content: "\F0AFE"; }
+.icon.alpha_q_box:before { content: "\F0B18"; }
+.icon.alpha_r:before { content: "\F0AFF"; }
+.icon.alpha_r_box:before { content: "\F0B19"; }
+.icon.alpha_s:before { content: "\F0B00"; }
+.icon.alpha_s_box:before { content: "\F0B1A"; }
+.icon.alpha_t:before { content: "\F0B01"; }
+.icon.alpha_t_box:before { content: "\F0B1B"; }
+.icon.alpha_u:before { content: "\F0B02"; }
+.icon.alpha_u_box:before { content: "\F0B1C"; }
+.icon.alpha_v:before { content: "\F0B03"; }
+.icon.alpha_v_box:before { content: "\F0B1D"; }
+.icon.alpha_w:before { content: "\F0B04"; }
+.icon.alpha_w_box:before { content: "\F0B1E"; }
+.icon.alpha_x:before { content: "\F0B05"; }
+.icon.alpha_x_box:before { content: "\F0B1F"; }
+.icon.alpha_y:before { content: "\F0B06"; }
+.icon.alpha_y_box:before { content: "\F0B20"; }
+.icon.alpha_z:before { content: "\F0B07"; }
+.icon.alpha_z_box:before { content: "\F0B21"; }
+
+
.icon.f16:before { font-size: 16px; }
.icon.f24:before { font-size: 24px; }
diff --git a/font/material_icon/material_icon.eot b/font/material_icon/material_icon.eot
index c5a9085..ee030ec 100755
Binary files a/font/material_icon/material_icon.eot and b/font/material_icon/material_icon.eot differ
diff --git a/font/material_icon/material_icon.ttf b/font/material_icon/material_icon.ttf
index 522dc31..d685510 100755
Binary files a/font/material_icon/material_icon.ttf and b/font/material_icon/material_icon.ttf differ
diff --git a/font/material_icon/material_icon.woff b/font/material_icon/material_icon.woff
index 2e633e8..deb8ae9 100755
Binary files a/font/material_icon/material_icon.woff and b/font/material_icon/material_icon.woff differ
diff --git a/font/material_icon/material_icon.woff2 b/font/material_icon/material_icon.woff2
index 2784ba1..c8cadcf 100755
Binary files a/font/material_icon/material_icon.woff2 and b/font/material_icon/material_icon.woff2 differ
diff --git a/js/.eslintrc.json b/js/.eslintrc.json
index c701868..f579fe3 100644
--- a/js/.eslintrc.json
+++ b/js/.eslintrc.json
@@ -9,7 +9,9 @@
"beestat": true,
"moment": true,
"Highcharts": true,
- "Sentry": true
+ "Sentry": true,
+ "THREE": true,
+ "ClipperLib": true
},
"extends": "eslint:all",
"rules": {
@@ -59,6 +61,7 @@
"strict": "off",
"valid-jsdoc": ["error", {"requireReturn": false, "requireParamDescription": false}],
"vars-on-top": "off",
+ "operator_assignment": "off",
// Node.js and CommonJS
"callback-return": "off",
diff --git a/js/beestat.js b/js/beestat.js
index 68f5a69..79d2b80 100644
--- a/js/beestat.js
+++ b/js/beestat.js
@@ -80,6 +80,8 @@ window.addEventListener('resize', rocket.throttle(100, function() {
beestat.dispatcher.dispatchEvent('breakpoint');
}
});
+
+ beestat.dispatcher.dispatchEvent('resize');
}));
// First run
diff --git a/js/beestat/api.js b/js/beestat/api.js
index 86cbd5c..9ef88fc 100644
--- a/js/beestat/api.js
+++ b/js/beestat/api.js
@@ -35,19 +35,12 @@ beestat.api.prototype.send = function(opt_api_call) {
// Add in the API key
opt_api_call.api_key = beestat.api.api_key;
- // Build the query string
- var query_string = Object.keys(opt_api_call)
- .map(function(k) {
- return encodeURIComponent(k) + '=' + encodeURIComponent(opt_api_call[k]);
- })
- .join('&');
-
this.xhr_.addEventListener('load', function() {
self.load_(this.responseText);
});
- this.xhr_.open('POST', 'api/?' + query_string);
- this.xhr_.send();
+ this.xhr_.open('POST', 'api/');
+ this.xhr_.send(JSON.stringify(opt_api_call));
} else {
if (this.api_calls_.length === 0) {
throw new Error('Must add at least one API call.');
diff --git a/js/beestat/sensor.js b/js/beestat/sensor.js
index 8a7c529..cc247f9 100644
--- a/js/beestat/sensor.js
+++ b/js/beestat/sensor.js
@@ -3,13 +3,15 @@ beestat.sensor = {};
/**
* Get a sorted list of all sensors attached to the current thermostat.
*
+ * @param {number} thermostat_id Thermostat to get this list for.
+ *
* @return {array} The sensors.
*/
-beestat.sensor.get_sorted = function() {
+beestat.sensor.get_sorted = function(thermostat_id) {
// Get and sort all the sensors.
- var sensors = [];
- $.values(beestat.cache.sensor).forEach(function(sensor) {
- if (sensor.thermostat_id === beestat.setting('thermostat_id')) {
+ const sensors = [];
+ Object.values(beestat.cache.sensor).forEach(function(sensor) {
+ if (sensor.thermostat_id === (thermostat_id || beestat.setting('thermostat_id'))) {
sensors.push(sensor);
}
});
diff --git a/js/beestat/setting.js b/js/beestat/setting.js
index a8961f5..c0ea89d 100644
--- a/js/beestat/setting.js
+++ b/js/beestat/setting.js
@@ -66,7 +66,9 @@ beestat.setting = function(argument_1, opt_value, opt_callback) {
'first_run': true,
- 'thermostat.#.profile.ignore_solar_gain': false
+ 'thermostat.#.profile.ignore_solar_gain': false,
+
+ 'floor_plan_id': null
};
// Figure out what we're trying to do.
diff --git a/js/component.js b/js/component.js
index 780dd51..597a367 100644
--- a/js/component.js
+++ b/js/component.js
@@ -59,6 +59,8 @@ beestat.component.prototype.render = function(parent) {
*/
beestat.component.prototype.rerender = function() {
if (this.rendered_ === true) {
+ this.rendered_ = false;
+
var new_container = $.createElement('div')
.style('position', 'relative');
this.decorate_(new_container);
@@ -70,6 +72,8 @@ beestat.component.prototype.rerender = function() {
window.setTimeout(function() {
self.dispatchEvent('render');
}, 0);
+
+ this.rendered_ = true;
}
return this;
};
diff --git a/js/component/button.js b/js/component/button.js
index fd00ce0..e5e448f 100644
--- a/js/component/button.js
+++ b/js/component/button.js
@@ -34,6 +34,10 @@ beestat.component.button.prototype.decorate_ = function(parent) {
});
parent.appendChild(this.button_);
+ if (this.title_ !== undefined) {
+ this.button_.setAttribute('title', this.title_);
+ }
+
if (this.icon_ !== undefined && this.text_ !== undefined) {
// Text + Icon
this.button_.style({
@@ -239,6 +243,21 @@ beestat.component.button.prototype.set_bubble_color = function(bubble_color) {
return this;
};
+/**
+ * Set the title for the button.
+ *
+ * @param {string} title
+ *
+ * @return {beestat.component.button} This.
+ */
+beestat.component.button.prototype.set_title = function(title) {
+ this.title_ = title;
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+ return this;
+};
+
/**
* Do the normal event listener stuff.
*
diff --git a/js/component/button_group.js b/js/component/button_group.js
index e8cd300..4b0105f 100644
--- a/js/component/button_group.js
+++ b/js/component/button_group.js
@@ -53,3 +53,13 @@ beestat.component.button_group.prototype.add_button = function(button) {
beestat.component.button_group.prototype.get_buttons = function() {
return this.buttons_;
};
+
+/**
+ * Remove this component from the page. Disposes the buttons first.
+ */
+beestat.component.button_group.prototype.dispose = function() {
+ this.buttons_.forEach(function(button) {
+ button.dispose();
+ });
+ beestat.component.prototype.dispose.apply(this, arguments);
+};
diff --git a/js/component/card/air_quality_detail.js b/js/component/card/air_quality_detail.js
index ade2820..752d5fb 100644
--- a/js/component/card/air_quality_detail.js
+++ b/js/component/card/air_quality_detail.js
@@ -305,7 +305,7 @@ beestat.component.card.air_quality_detail.prototype.decorate_top_right_ = functi
if (this.has_data_() === true) {
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Reset Zoom')
- .set_icon('magnify_minus')
+ .set_icon('magnify_close')
.set_callback(function() {
self.charts_.air_quality.reset_zoom();
}));
diff --git a/js/component/card/early_access.js b/js/component/card/early_access.js
index 669a821..df5bdce 100644
--- a/js/component/card/early_access.js
+++ b/js/component/card/early_access.js
@@ -13,5 +13,5 @@ beestat.extend(beestat.component.card.early_access, beestat.component.card);
*/
beestat.component.card.early_access.prototype.decorate_contents_ = function(parent) {
parent.style('background', beestat.style.color.green.base);
- parent.appendChild($.createElement('p').innerText('Welcome to the early access release for Air Quality in beestat! Please let me know if you have any feedback or issues.'));
+ parent.appendChild($.createElement('p').innerText('Welcome to the early access release for Visualize. Currently this is limited to the floor plan builder. The 3D view with sensor data visualizations is still a work in progress.'));
};
diff --git a/js/component/card/floor_plan_editor.js b/js/component/card/floor_plan_editor.js
new file mode 100644
index 0000000..f92392d
--- /dev/null
+++ b/js/component/card/floor_plan_editor.js
@@ -0,0 +1,534 @@
+/**
+ * Floor plan editor.
+ *
+ * @param {number} thermostat_id
+ */
+beestat.component.card.floor_plan_editor = function(thermostat_id) {
+ const self = this;
+ this.thermostat_id_ = thermostat_id;
+
+ var change_function = beestat.debounce(function() {
+ delete self.state_.active_group;
+ delete self.state_.active_room;
+ self.rerender();
+ }, 10);
+
+ beestat.dispatcher.addEventListener(
+ [
+ 'setting.floor_plan_id',
+ 'cache.floor_plan'
+ ],
+ change_function
+ );
+
+ beestat.component.card.apply(this, arguments);
+
+ // Snapping initial
+ if (this.state_.snapping === undefined) {
+ this.state_.snapping = true;
+ }
+};
+beestat.extend(beestat.component.card.floor_plan_editor, beestat.component.card);
+
+/**
+ * Decorate.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.floor_plan_editor.prototype.decorate_contents_ = function(parent) {
+ const self = this;
+
+ if (Object.keys(beestat.cache.floor_plan).length === 0) {
+ const center_container = $.createElement('div').style('text-align', 'center');
+ parent.appendChild(center_container);
+
+ center_container.appendChild($.createElement('p').innerText('You haven\'t created any floor plans yet.'));
+ const get_started_button = new beestat.component.button()
+ .set_icon('home_plus')
+ .set_text('Get Started')
+ .set_background_color(beestat.style.color.green.dark)
+ .set_background_hover_color(beestat.style.color.green.light)
+ .render(center_container)
+ .addEventListener('click', function() {
+ new beestat.component.modal.create_floor_plan(
+ self.thermostat_id_
+ ).render();
+ });
+ center_container.appendChild(get_started_button);
+ } else {
+ const floor_plan = beestat.cache.floor_plan[beestat.setting('floor_plan_id')];
+ if (this.state_.active_group === undefined) {
+ this.state_.active_group = floor_plan.data.groups[0];
+ }
+
+ const drawing_pane_container = $.createElement('div');
+ drawing_pane_container.style({
+ 'position': 'relative',
+ 'overflow-x': 'hidden'
+ });
+ parent.appendChild(drawing_pane_container);
+ this.decorate_drawing_pane_(drawing_pane_container);
+
+ this.info_pane_container_ = $.createElement('div')
+ .style('margin-top', beestat.style.size.gutter / 2);
+ parent.appendChild(this.info_pane_container_);
+ this.decorate_info_pane_(this.info_pane_container_);
+
+ this.update_floor_plan_();
+ }
+};
+
+/**
+ * Decorate the drawing pane.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.floor_plan_editor.prototype.decorate_drawing_pane_ = function(parent) {
+ const self = this;
+
+ // Dispose existing SVG to remove any global listeners.
+ if (this.floor_plan_ !== undefined) {
+ this.floor_plan_.dispose();
+ }
+
+ // Create and render a new SVG component.
+ this.floor_plan_ = new beestat.component.floor_plan(
+ beestat.setting('floor_plan_id'),
+ this.state_
+ );
+
+ this.floor_plan_.render(parent);
+
+ setTimeout(function() {
+ self.floor_plan_.set_width(parent.getBoundingClientRect().width);
+ }, 0);
+
+ beestat.dispatcher.removeEventListener('resize.floor_plan_editor');
+ beestat.dispatcher.addEventListener('resize.floor_plan_editor', function() {
+ self.floor_plan_.set_width(parent.getBoundingClientRect().width);
+ });
+
+ // Rerender when stuff happens
+ this.floor_plan_.addEventListener('add_room', self.rerender.bind(this));
+ this.floor_plan_.addEventListener('remove_room', self.rerender.bind(this));
+ this.floor_plan_.addEventListener('clear_room', self.rerender.bind(this));
+ this.floor_plan_.addEventListener('remove_point', self.rerender.bind(this));
+ this.floor_plan_.addEventListener('toggle_snapping', self.rerender.bind(this));
+ this.floor_plan_.addEventListener('change_group', self.rerender.bind(this));
+ this.floor_plan_.addEventListener('zoom', self.rerender.bind(this));
+
+ // Add all of the entities to the SVG.
+ this.entities_ = {
+ 'room': []
+ };
+
+ const group_below = this.floor_plan_.get_group_below(this.state_.active_group);
+ if (group_below !== undefined) {
+ group_below.rooms.forEach(function(room) {
+ const room_entity = new beestat.component.floor_plan_entity.room(self.floor_plan_, self.state_)
+ .set_enabled(false)
+ .set_room(room)
+ .set_group(self.state_.active_group);
+ room_entity.render(self.floor_plan_.get_g());
+ });
+ }
+
+ // Loop over the rooms in this group and add them.
+ this.state_.active_group.rooms.forEach(function(room) {
+ const room_entity = new beestat.component.floor_plan_entity.room(self.floor_plan_, self.state_)
+ .set_room(room)
+ .set_group(self.state_.active_group);
+
+ // Update the GUI and save when a room changes.
+ room_entity.addEventListener('update', function() {
+ self.floor_plan_.update_infobox();
+ self.update_info_pane_();
+ self.update_floor_plan_();
+ });
+
+ // Update GUI when a room is selected.
+ room_entity.addEventListener('activate', function() {
+ self.floor_plan_.update_infobox();
+ self.update_info_pane_();
+ });
+
+ // Activate the currently active room (mostly for rerenders).
+ if (room === self.state_.active_room) {
+ room_entity.set_active(true);
+ }
+
+ // Render the room and save to the list of current entities.
+ room_entity.render(self.floor_plan_.get_g());
+ self.entities_.room.push(room_entity);
+ });
+};
+
+/**
+ * Decorate the info pane.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_ = function(parent) {
+ if (this.state_.active_room !== undefined) {
+ this.decorate_info_pane_room_(parent);
+ } else {
+ this.decorate_info_pane_floor_(parent);
+ }
+};
+
+/**
+ * Decorate the info pane for a floor.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = function(parent) {
+ const self = this;
+
+ const grid = $.createElement('div')
+ .style({
+ 'display': 'grid',
+ 'grid-template-columns': 'repeat(auto-fit, minmax(150px, 1fr))',
+ 'column-gap': beestat.style.size.gutter
+ });
+ parent.appendChild(grid);
+
+ let div;
+
+ // Group Name
+ div = $.createElement('div');
+ grid.appendChild(div);
+ const name_input = new beestat.component.input.text()
+ .set_label('Floor Name')
+ .set_placeholder('Unnamed Floor')
+ .set_width('100%')
+ .set_maxlength('50')
+ .set_requirements({
+ 'required': true
+ })
+ .render(div);
+
+ if (this.state_.active_group.name !== undefined) {
+ name_input.set_value(this.state_.active_group.name);
+ }
+
+ name_input.addEventListener('input', function() {
+ self.state_.active_group.name = name_input.get_value();
+ self.floor_plan_.update_infobox();
+ });
+ name_input.addEventListener('change', function() {
+ self.state_.active_group.name = name_input.get_value();
+ self.update_floor_plan_();
+ });
+
+ // Elevation
+ div = $.createElement('div');
+ grid.appendChild(div);
+ const elevation_input = new beestat.component.input.text()
+ .set_label('Elevation (inches)')
+ .set_placeholder(this.state_.active_group.elevation)
+ .set_value(this.state_.active_group.elevation || '')
+ .set_width('100%')
+ .set_maxlength('5')
+ .set_requirements({
+ 'type': 'integer',
+ 'required': true
+ })
+ .render(div);
+
+ elevation_input.set_value(this.state_.active_group.elevation);
+
+ elevation_input.addEventListener('change', function() {
+ if (elevation_input.meets_requirements() === true) {
+ self.state_.active_group.elevation = elevation_input.get_value();
+ self.update_floor_plan_();
+ } else {
+ elevation_input.set_value(self.state_.active_group.elevation);
+ }
+ });
+
+ // Ceiling Height
+ div = $.createElement('div');
+ grid.appendChild(div);
+ const height_input = new beestat.component.input.text()
+ .set_label('Ceiling Height (inches)')
+ .set_placeholder(this.state_.active_group.height)
+ .set_value(this.state_.active_group.height || '')
+ .set_width('100%')
+ .set_maxlength('4')
+ .set_requirements({
+ 'type': 'integer',
+ 'min_value': 1,
+ 'required': true
+ })
+ .render(div);
+
+ height_input.set_value(this.state_.active_group.height);
+
+ height_input.addEventListener('change', function() {
+ if (height_input.meets_requirements() === true) {
+ self.state_.active_group.height = height_input.get_value();
+ self.update_floor_plan_();
+ } else {
+ height_input.set_value(self.state_.active_group.height);
+ }
+ });
+
+ // Sensor
+ div = $.createElement('div');
+ grid.appendChild(div);
+};
+
+/**
+ * Decorate the info pane for a room.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = function(parent) {
+ const self = this;
+
+ const grid = $.createElement('div')
+ .style({
+ 'display': 'grid',
+ 'grid-template-columns': 'repeat(auto-fit, minmax(150px, 1fr))',
+ 'column-gap': beestat.style.size.gutter
+ });
+ parent.appendChild(grid);
+
+ let div;
+
+ // Room Name
+ div = $.createElement('div');
+ grid.appendChild(div);
+ const name_input = new beestat.component.input.text()
+ .set_label('Room Name')
+ .set_placeholder('Unnamed Room')
+ .set_width('100%')
+ .set_maxlength('50')
+ .set_requirements({
+ 'required': true
+ })
+ .render(div);
+
+ if (this.state_.active_room.name !== undefined) {
+ name_input.set_value(this.state_.active_room.name);
+ }
+
+ name_input.addEventListener('input', function() {
+ self.state_.active_room.name = name_input.get_value();
+ self.floor_plan_.update_infobox();
+ });
+ name_input.addEventListener('change', function() {
+ self.state_.active_room.name = name_input.get_value();
+ self.update_floor_plan_();
+ });
+
+ // Elevation
+ div = $.createElement('div');
+ grid.appendChild(div);
+ const elevation_input = new beestat.component.input.text()
+ .set_label('Elevation (inches)')
+ .set_placeholder(this.state_.active_group.elevation)
+ .set_value(this.state_.active_room.elevation || '')
+ .set_width('100%')
+ .set_maxlength('5')
+ .set_requirements({
+ 'type': 'integer'
+ })
+ .render(div);
+
+ if (this.state_.active_room.elevation !== undefined) {
+ elevation_input.set_value(this.state_.active_room.elevation);
+ }
+
+ elevation_input.addEventListener('change', function() {
+ if (elevation_input.meets_requirements() === true) {
+ self.state_.active_room.elevation = elevation_input.get_value();
+ self.update_floor_plan_();
+ } else {
+ elevation_input.set_value('');
+ }
+ });
+
+ // Ceiling Height
+ div = $.createElement('div');
+ grid.appendChild(div);
+ const height_input = new beestat.component.input.text()
+ .set_label('Ceiling Height (inches)')
+ .set_placeholder(this.state_.active_group.height)
+ .set_value(this.state_.active_room.height || '')
+ .set_width('100%')
+ .set_maxlength('4')
+ .set_requirements({
+ 'type': 'integer',
+ 'min_value': 1
+ })
+ .render(div);
+
+ if (this.state_.active_room.height !== undefined) {
+ height_input.set_value(this.state_.active_room.height);
+ }
+
+ height_input.addEventListener('change', function() {
+ if (height_input.meets_requirements() === true) {
+ self.state_.active_room.height = height_input.get_value();
+ self.update_floor_plan_();
+ } else {
+ height_input.set_value('');
+ }
+ });
+
+ // Sensor
+ div = $.createElement('div');
+ grid.appendChild(div);
+ const sensor_input = new beestat.component.input.select()
+ .add_option({
+ 'label': 'None',
+ 'value': ''
+ })
+ .set_width('100%')
+ .set_label('Sensor');
+
+ const sensors = {};
+ Object.values(beestat.cache.thermostat).forEach(function(thermostat) {
+ const thermostat_sensors = beestat.sensor.get_sorted(
+ thermostat.thermostat_id
+ );
+ sensors[thermostat.thermostat_id] = thermostat_sensors;
+ });
+
+ // Put the sensors in the select.
+ for (let thermostat_id in sensors) {
+ const thermostat = beestat.cache.thermostat[thermostat_id];
+ sensors[thermostat_id].forEach(function(sensor) {
+ sensor_input.add_option({
+ 'group': thermostat.name,
+ 'value': sensor.sensor_id,
+ 'label': sensor.name
+ });
+ });
+ }
+
+ sensor_input.render(div);
+
+ if (self.state_.active_room.sensor_id !== undefined) {
+ sensor_input.set_value(self.state_.active_room.sensor_id);
+ } else {
+ sensor_input.set_value('');
+ }
+
+ sensor_input.addEventListener('change', function() {
+ if (sensor_input.get_value() === '') {
+ delete self.state_.active_room.sensor_id;
+ } else {
+ self.state_.active_room.sensor_id = Number(sensor_input.get_value());
+ }
+ self.update_floor_plan_();
+ });
+};
+
+/**
+ * Rerender just the info pane to avoid rerendering the entire SVG for
+ * resizes, drags, etc. This isn't super ideal but without making the info
+ * pane a separate component this is the way.
+ */
+beestat.component.card.floor_plan_editor.prototype.update_info_pane_ = function() {
+ var old_parent = this.info_pane_container_;
+ this.info_pane_container_ = $.createElement('div')
+ .style('margin-top', beestat.style.size.gutter / 2);
+
+ this.decorate_info_pane_(this.info_pane_container_);
+ old_parent.parentNode().replaceChild(this.info_pane_container_, old_parent);
+};
+
+/**
+ * Get the title of the card.
+ *
+ * @return {string} The title.
+ */
+beestat.component.card.floor_plan_editor.prototype.get_title_ = function() {
+ return 'Floor Plan';
+};
+
+/**
+ * Get the subtitle of the card.
+ *
+ * @return {string} Subtitle
+ */
+beestat.component.card.floor_plan_editor.prototype.get_subtitle_ = function() {
+ const floor_plan = beestat.cache.floor_plan[beestat.setting('floor_plan_id')];
+ return floor_plan.name;
+};
+
+/**
+ * Update the floor plan in the database.
+ */
+beestat.component.card.floor_plan_editor.prototype.update_floor_plan_ = function() {
+ const new_rooms = [];
+ this.entities_.room.forEach(function(entity) {
+ new_rooms.push(entity.get_room());
+ });
+
+ this.state_.active_group.rooms = new_rooms;
+
+ new beestat.api()
+ .add_call(
+ 'floor_plan',
+ 'update',
+ {
+ 'attributes': {
+ 'floor_plan_id': beestat.setting('floor_plan_id'),
+ 'data': beestat.cache.floor_plan[beestat.setting('floor_plan_id')].data
+ }
+ },
+ 'update_floor_plan'
+ )
+ .send();
+};
+
+/**
+ * Decorate the menu.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.card.floor_plan_editor.prototype.decorate_top_right_ = function(parent) {
+ const self = this;
+
+ var menu = (new beestat.component.menu()).render(parent);
+
+ menu.add_menu_item(new beestat.component.menu_item()
+ .set_text('Add New')
+ .set_icon('home_plus')
+ .set_callback(function() {
+ new beestat.component.modal.create_floor_plan(
+ self.thermostat_id_
+ ).render();
+ }));
+
+ if (Object.keys(beestat.cache.floor_plan).length > 1) {
+ menu.add_menu_item(new beestat.component.menu_item()
+ .set_text('Switch')
+ .set_icon('home_switch')
+ .set_callback(function() {
+ (new beestat.component.modal.change_floor_plan()).render();
+ }));
+ }
+
+ if (beestat.setting('floor_plan_id') !== null) {
+ menu.add_menu_item(new beestat.component.menu_item()
+ .set_text('Delete')
+ .set_icon('home_remove')
+ .set_callback(function() {
+ new beestat.component.modal.delete_floor_plan(
+ beestat.setting('floor_plan_id')
+ ).render();
+ }));
+ }
+
+ menu.add_menu_item(new beestat.component.menu_item()
+ .set_text('Help')
+ .set_icon('help_circle')
+ .set_callback(function() {
+ // TODO
+ // window.open('https://doc.beestat.io/???');
+ }));
+};
diff --git a/js/component/card/runtime_sensor_detail.js b/js/component/card/runtime_sensor_detail.js
index a30f677..941c388 100644
--- a/js/component/card/runtime_sensor_detail.js
+++ b/js/component/card/runtime_sensor_detail.js
@@ -312,7 +312,7 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_top_right_ = fun
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Reset Zoom')
- .set_icon('magnify_minus')
+ .set_icon('magnify_close')
.set_callback(function() {
self.charts_.temperature.reset_zoom();
}));
diff --git a/js/component/card/runtime_thermostat_detail.js b/js/component/card/runtime_thermostat_detail.js
index 606e124..2c083d4 100644
--- a/js/component/card/runtime_thermostat_detail.js
+++ b/js/component/card/runtime_thermostat_detail.js
@@ -299,7 +299,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_top_right_ =
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Reset Zoom')
- .set_icon('magnify_minus')
+ .set_icon('magnify_close')
.set_callback(function() {
self.charts_.temperature.reset_zoom();
}));
diff --git a/js/component/card/runtime_thermostat_summary.js b/js/component/card/runtime_thermostat_summary.js
index fe4c6c6..837be77 100755
--- a/js/component/card/runtime_thermostat_summary.js
+++ b/js/component/card/runtime_thermostat_summary.js
@@ -574,7 +574,7 @@ beestat.component.card.runtime_thermostat_summary.prototype.decorate_top_right_
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Reset Zoom')
- .set_icon('magnify_minus')
+ .set_icon('magnify_close')
.set_callback(function() {
self.chart_.reset_zoom();
}));
diff --git a/js/component/card/settings.js b/js/component/card/settings.js
index b4f79fa..753cb20 100644
--- a/js/component/card/settings.js
+++ b/js/component/card/settings.js
@@ -26,16 +26,16 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
const enable_gap_fill = new beestat.component.input.checkbox();
enable_gap_fill
.set_label('Enable Gap Fill')
- .set_value(beestat.setting('runtime_thermostat_summary_gap_fill'))
+ .set_checked(beestat.setting('runtime_thermostat_summary_gap_fill'))
.render(parent);
enable_gap_fill.addEventListener('change', function() {
- enable_gap_fill.disable();
+ enable_gap_fill.set_enabled(false);
beestat.setting(
'runtime_thermostat_summary_gap_fill',
- enable_gap_fill.get_value(),
+ enable_gap_fill.get_checked(),
function() {
- enable_gap_fill.enable();
+ enable_gap_fill.set_enabled(true);
}
);
});
@@ -44,16 +44,16 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
const enable_smart_scale = new beestat.component.input.checkbox();
enable_smart_scale
.set_label('Enable Smart Scale')
- .set_value(beestat.setting('runtime_thermostat_summary_smart_scale'))
+ .set_checked(beestat.setting('runtime_thermostat_summary_smart_scale'))
.render(parent);
enable_smart_scale.addEventListener('change', function() {
- enable_smart_scale.disable();
+ enable_smart_scale.set_enabled(false);
beestat.setting(
'runtime_thermostat_summary_smart_scale',
- enable_smart_scale.get_value(),
+ enable_smart_scale.get_checked(),
function() {
- enable_smart_scale.enable();
+ enable_smart_scale.set_enabled(true);
}
);
});
@@ -70,14 +70,14 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
const ignore_solar_gain_key = 'thermostat.' + thermostat.thermostat_id + '.profile.ignore_solar_gain';
ignore_solar_gain
.set_label('Ignore Solar Gain')
- .set_value(beestat.setting(ignore_solar_gain_key))
+ .set_checked(beestat.setting(ignore_solar_gain_key))
.render(parent);
ignore_solar_gain.addEventListener('change', function() {
- ignore_solar_gain.disable();
+ ignore_solar_gain.set_enabled(false);
beestat.setting(
ignore_solar_gain_key,
- ignore_solar_gain.get_value(),
+ ignore_solar_gain.get_checked(),
function() {
/**
* Clear the API call cache and delete the profile so it regenerates
@@ -117,7 +117,7 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
'thermostat'
)
.set_callback(function(response) {
- ignore_solar_gain.enable();
+ ignore_solar_gain.set_enabled(true);
beestat.cache.set('thermostat', response.thermostat);
})
.send();
@@ -134,14 +134,7 @@ beestat.component.card.settings.prototype.decorate_contents_ = function(parent)
);
var temperature_profiles_range_begin = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_maxlength(10)
.set_icon('calendar');
var temperature_profiles_range_begin_m =
diff --git a/js/component/card/visualize.js b/js/component/card/visualize.js
new file mode 100644
index 0000000..f9554b4
--- /dev/null
+++ b/js/component/card/visualize.js
@@ -0,0 +1,42 @@
+/**
+ * Visualize
+ *
+ * @param {number} thermostat_id The thermostat_id this card is displaying
+ * data for.
+ */
+beestat.component.card.visualize = function(thermostat_id) {
+ this.thermostat_id_ = thermostat_id;
+
+ beestat.component.card.apply(this, arguments);
+};
+beestat.extend(beestat.component.card.visualize, beestat.component.card);
+
+beestat.component.card.visualize.prototype.decorate_contents_ = function(parent) {
+ const scene = new beestat.component.scene(this.thermostat_id_);
+ scene.render(parent);
+
+ let date = new Date('2022-06-27 11:30:00');
+ scene.set_date(date);
+ // let date = new Date('2024-04-08 08:00:00'); // Total solar eclipse
+ // scene.set_date(date);
+ // setInterval(function() {
+ // date = new Date(date.getTime() + (60000 * 1));
+ // scene.set_date(date);
+ // }, 100);
+ //
+
+ beestat.dispatcher.addEventListener('cache.floor_plan', function() {
+ scene.rerender();
+ // todo get scene to remember date, camera position, etc on rerender
+ scene.set_date(date);
+ });
+};
+
+/**
+ * Get the title of the card.
+ *
+ * @return {string} The title.
+ */
+beestat.component.card.visualize.prototype.get_title_ = function() {
+ return 'Visualize';
+};
diff --git a/js/component/floor_plan.js b/js/component/floor_plan.js
new file mode 100644
index 0000000..c0f92bc
--- /dev/null
+++ b/js/component/floor_plan.js
@@ -0,0 +1,788 @@
+/**
+ * SVG Document
+ *
+ * @param {number} floor_plan_id The floor_plan_id to show.
+ * @param {object} state Shared state.
+ */
+beestat.component.floor_plan = function(floor_plan_id, state) {
+ this.toolbar_buttons_ = {};
+ this.floor_plan_id_ = floor_plan_id;
+
+ beestat.component.apply(this, arguments);
+
+ /**
+ * Override this component's state with a state common to all floor plan
+ * entities.
+ */
+ this.state_ = state;
+};
+beestat.extend(beestat.component.floor_plan, beestat.component);
+
+/**
+ * Render the SVG document to the parent.
+ *
+ * @param {rocket.Elements} parent
+ *
+ * @return {beestat.component.floor_plan} This
+ */
+beestat.component.floor_plan.prototype.render = function(parent) {
+ const self = this;
+
+ this.svg_ = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+
+ this.defs_ = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
+ this.svg_.appendChild(this.defs_);
+
+ this.g_ = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+ this.svg_.appendChild(this.g_);
+
+ this.add_grid_();
+
+ this.width_ = this.state_.floor_plan_width || 800;
+ this.height_ = 500;
+
+ this.svg_.setAttribute('width', this.width_);
+ this.svg_.setAttribute('height', this.height_);
+
+ this.svg_.style.background = beestat.style.color.bluegray.dark;
+ this.svg_.style.userSelect = 'none';
+
+ this.set_zoomable_();
+ this.set_draggable_();
+
+ if (this.state_.floor_plan_view_box === undefined) {
+ this.view_box_ = {
+ 'x': 0,
+ 'y': 0,
+ 'width': this.width_,
+ 'height': this.height_
+ };
+ } else {
+ this.view_box_ = this.state_.floor_plan_view_box;
+ }
+ this.update_view_box_();
+
+ this.toolbar_container_ = $.createElement('div');
+ this.toolbar_container_.style({
+ 'position': 'absolute',
+ 'top': beestat.style.size.gutter,
+ 'left': beestat.style.size.gutter + (beestat.style.size.gutter / 2),
+ 'width': '40px'
+ });
+ parent.appendChild(this.toolbar_container_);
+
+ this.floors_container_ = $.createElement('div');
+ this.floors_container_.style({
+ 'position': 'absolute',
+ 'top': beestat.style.size.gutter,
+ 'left': 40 + beestat.style.size.gutter + (beestat.style.size.gutter / 2)
+ });
+ parent.appendChild(this.floors_container_);
+
+ this.update_toolbar();
+
+ this.infobox_container_ = $.createElement('div');
+ this.infobox_container_.style({
+ 'position': 'absolute',
+ 'color': beestat.style.color.gray.base,
+ 'top': beestat.style.size.gutter,
+ 'right': beestat.style.size.gutter,
+ 'line-height': 32,
+ 'user-select': 'none'
+ });
+ parent.appendChild(this.infobox_container_);
+
+ this.update_infobox();
+
+ parent.appendChild(this.svg_);
+
+ this.keydown_handler_ = function(e) {
+ if (e.target.nodeName === 'BODY') {
+ if (e.key === 'Escape') {
+ if (self.state_.active_room !== undefined) {
+ self.clear_room_();
+ }
+ } else if (e.key === 'Delete') {
+ if (self.state_.active_point !== undefined) {
+ self.remove_point_();
+ } else if (self.state_.active_room !== undefined) {
+ self.remove_room_();
+ }
+ } else if (e.key.toLowerCase() === 'r') {
+ if (e.ctrlKey === false) {
+ self.add_room_();
+ }
+ } else if (e.key.toLowerCase() === 's') {
+ self.toggle_snapping_();
+ }
+ }
+ };
+
+ window.addEventListener('keydown', this.keydown_handler_);
+
+ this.rendered_ = true;
+
+ return this;
+};
+
+/**
+ * Update the view box with the current values. Cap so the grid doesn't go out
+ * of view.
+ */
+beestat.component.floor_plan.prototype.update_view_box_ = function() {
+ // Cap x/y pan
+ const min_x = this.grid_pixels_ / -2;
+ const max_x = (this.grid_pixels_ / 2) - (this.width_ * this.get_scale());
+ this.view_box_.x = Math.min(Math.max(this.view_box_.x, min_x), max_x);
+
+ const min_y = this.grid_pixels_ / -2;
+ const max_y = (this.grid_pixels_ / 2) - (this.height_ * this.get_scale());
+ this.view_box_.y = Math.min(Math.max(this.view_box_.y, min_y), max_y);
+
+ this.svg_.setAttribute(
+ 'viewBox',
+ this.view_box_.x + ' ' + this.view_box_.y + ' ' + this.view_box_.width + ' ' + this.view_box_.height
+ );
+
+ this.state_.floor_plan_view_box = this.view_box_;
+};
+
+/**
+ * Add a helpful grid.
+ */
+beestat.component.floor_plan.prototype.add_grid_ = function() {
+ const pixels_per_small_grid = 12;
+ const small_grids_per_large_grid = 10;
+ const pixels_per_large_grid = pixels_per_small_grid * small_grids_per_large_grid;
+
+ const large_grid_repeat = 20;
+ this.grid_pixels_ = pixels_per_large_grid * large_grid_repeat;
+
+ const grid_small_pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
+ grid_small_pattern.setAttribute('id', 'grid_small');
+ grid_small_pattern.setAttribute('width', pixels_per_small_grid);
+ grid_small_pattern.setAttribute('height', pixels_per_small_grid);
+ grid_small_pattern.setAttribute('patternUnits', 'userSpaceOnUse');
+ this.defs_.appendChild(grid_small_pattern);
+
+ const grid_small_path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ grid_small_path.setAttribute('d', 'M ' + pixels_per_small_grid + ' 0 L 0 0 0 ' + pixels_per_small_grid);
+ grid_small_path.setAttribute('fill', 'none');
+ grid_small_path.setAttribute('stroke', beestat.style.color.bluegreen.dark);
+ grid_small_path.setAttribute('stroke-width', '0.5');
+ grid_small_pattern.appendChild(grid_small_path);
+
+ const grid_large_pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
+ grid_large_pattern.setAttribute('id', 'grid_large');
+ grid_large_pattern.setAttribute('width', pixels_per_large_grid);
+ grid_large_pattern.setAttribute('height', pixels_per_large_grid);
+ grid_large_pattern.setAttribute('patternUnits', 'userSpaceOnUse');
+ this.defs_.appendChild(grid_large_pattern);
+
+ const grid_large_rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+ grid_large_rect.setAttribute('width', pixels_per_large_grid);
+ grid_large_rect.setAttribute('height', pixels_per_large_grid);
+ grid_large_rect.setAttribute('fill', 'url("#grid_small")');
+ grid_large_pattern.appendChild(grid_large_rect);
+
+ const grid_large_path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ grid_large_path.setAttribute('d', 'M ' + pixels_per_large_grid + ' 0 L 0 0 0 ' + pixels_per_large_grid);
+ grid_large_path.setAttribute('fill', 'none');
+ grid_large_path.setAttribute('stroke', beestat.style.color.bluegreen.dark);
+ grid_large_path.setAttribute('stroke-width', '1');
+ grid_large_pattern.appendChild(grid_large_path);
+
+ const grid_rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+ grid_rect.setAttribute('id', 'grid_rect');
+ grid_rect.setAttribute('x', this.grid_pixels_ / -2);
+ grid_rect.setAttribute('y', this.grid_pixels_ / -2);
+ grid_rect.setAttribute('width', this.grid_pixels_);
+ grid_rect.setAttribute('height', this.grid_pixels_);
+ grid_rect.setAttribute('fill', 'url("#grid_large")');
+ this.g_.appendChild(grid_rect);
+};
+
+/**
+ * Make the SVG document zoomable.
+ */
+beestat.component.floor_plan.prototype.set_zoomable_ = function() {
+ const self = this;
+
+ this.wheel_handler_ = function(e) {
+ if (
+ e.ctrlKey === true &&
+ e.target.namespaceURI === 'http://www.w3.org/2000/svg'
+ ) {
+ e.preventDefault();
+
+ if (e.wheelDelta < 0) {
+ self.zoom_out_(e);
+ } else {
+ self.zoom_in_(e);
+ }
+ }
+ };
+
+ window.addEventListener('wheel', this.wheel_handler_, {'passive': false});
+};
+
+/**
+ * Make the SVG document draggabe.
+ */
+beestat.component.floor_plan.prototype.set_draggable_ = function() {
+ const self = this;
+
+ const mousedown_handler = function(e) {
+ // Prevent things underneath from also dragging.
+ e.stopPropagation();
+
+ self.drag_start_mouse_ = {
+ 'x': e.clientX,
+ 'y': e.clientY
+ };
+
+ self.drag_start_pan_ = {
+ 'x': self.view_box_.x,
+ 'y': self.view_box_.y
+ };
+
+ self.dragging_ = true;
+ };
+
+ this.mousemove_handler_ = function(e) {
+ if (self.dragging_ === true) {
+ const dx = (e.clientX - self.drag_start_mouse_.x);
+ const dy = (e.clientY - self.drag_start_mouse_.y);
+ self.view_box_.x = self.drag_start_pan_.x - (dx * self.get_scale());
+ self.view_box_.y = self.drag_start_pan_.y - (dy * self.get_scale());
+ self.update_view_box_();
+ }
+ };
+
+ this.mouseup_handler_ = function(e) {
+ // Deselect when clicking on the background.
+ if (
+ e.target.getAttribute('id') === 'grid_rect' &&
+ e.clientX === self.drag_start_mouse_.x &&
+ e.clientY === self.drag_start_mouse_.y
+ ) {
+ self.clear_room_();
+ }
+
+ self.dragging_ = false;
+ };
+
+ this.svg_.addEventListener('mousedown', mousedown_handler.bind(this));
+
+ window.addEventListener('mousemove', this.mousemove_handler_);
+ window.addEventListener('mouseup', this.mouseup_handler_);
+};
+
+/**
+ * Get the root group so other things can put stuff here.
+ *
+ * @return {SVGGElement} The root group.
+ */
+beestat.component.floor_plan.prototype.get_g = function() {
+ return this.g_;
+};
+
+/**
+ * Get the scale.
+ *
+ * @return {SVGGElement} The scale.
+ */
+beestat.component.floor_plan.prototype.get_scale = function() {
+ return this.view_box_.width / this.width_;
+};
+
+/**
+ * Get the grid pixels.
+ *
+ * @return {SVGGElement} The scale.
+ */
+beestat.component.floor_plan.prototype.get_grid_pixels = function() {
+ return this.grid_pixels_;
+};
+
+/**
+ * Convert from screen (global) coordinates to svg (local) coordinates.
+ *
+ * @param {Event} e
+ *
+ * @return {SVGPoint} A point in the SVG local coordinate space.
+ */
+beestat.component.floor_plan.prototype.get_local_point = function(e) {
+ const global_point = this.svg_.createSVGPoint();
+ global_point.x = e.clientX;
+ global_point.y = e.clientY;
+
+ return global_point.matrixTransform(
+ this.svg_.getScreenCTM().inverse()
+ );
+};
+
+/**
+ * Remove this component from the page.
+ */
+beestat.component.floor_plan.prototype.dispose = function() {
+ if (this.rendered_ === true) {
+ window.removeEventListener('keydown', this.keydown_handler_);
+ window.removeEventListener('wheel', this.wheel_handler_);
+ window.removeEventListener('mousemove', this.mousemove_handler_);
+ window.removeEventListener('mouseup', this.mouseup_handler_);
+ this.rendered_ = false;
+ }
+};
+
+/**
+ * Update the toolbar to match the current state.
+ */
+beestat.component.floor_plan.prototype.update_toolbar = function() {
+ const self = this;
+
+ if (this.button_group_ !== undefined) {
+ this.button_group_.dispose();
+ }
+
+ if (this.button_group_floors_ !== undefined) {
+ this.button_group_floors_.dispose();
+ }
+
+ this.button_group_ = new beestat.component.button_group();
+
+ // Add floor
+ this.button_group_.add_button(new beestat.component.button()
+ .set_icon('layers')
+ .set_text_color(beestat.style.color.lightblue.base)
+ );
+
+ // Add room
+ this.button_group_.add_button(new beestat.component.button()
+ .set_icon('card_plus_outline')
+ .set_title('Add Room [R]')
+ .set_text_color(beestat.style.color.gray.light)
+ .set_background_color(beestat.style.color.bluegray.base)
+ .set_background_hover_color(beestat.style.color.bluegray.light)
+ .addEventListener('click', this.add_room_.bind(this))
+ );
+
+ // Remove room
+ const remove_room_button = new beestat.component.button()
+ .set_icon('card_remove_outline')
+ .set_title('Remove Room [Delete]')
+ .set_background_color(beestat.style.color.bluegray.base);
+ this.button_group_.add_button(remove_room_button);
+
+ if (this.state_.active_room !== undefined) {
+ remove_room_button
+ .set_background_hover_color(beestat.style.color.bluegray.light)
+ .set_text_color(beestat.style.color.red.base)
+ .addEventListener('click', this.remove_room_.bind(this));
+ } else {
+ remove_room_button
+ .set_text_color(beestat.style.color.bluegray.dark);
+ }
+
+ // Add point
+ const add_point_button = new beestat.component.button()
+ .set_icon('vector_square_plus')
+ .set_title('Add Point [Double click]')
+ .set_background_color(beestat.style.color.bluegray.base);
+ this.button_group_.add_button(add_point_button);
+
+ if (this.state_.active_wall_entity !== undefined) {
+ add_point_button
+ .set_background_hover_color(beestat.style.color.bluegray.light)
+ .set_text_color(beestat.style.color.gray.light)
+ .addEventListener('click', this.add_point_.bind(this));
+ } else {
+ add_point_button
+ .set_text_color(beestat.style.color.bluegray.dark);
+ }
+
+ // Remove point
+ const remove_point_button = new beestat.component.button()
+ .set_background_color(beestat.style.color.bluegray.base)
+ .set_title('Remove Point [Delete]')
+ .set_icon('vector_square_remove');
+ this.button_group_.add_button(remove_point_button);
+
+ if (
+ this.state_.active_point !== undefined &&
+ this.state_.active_room.points.length > 3
+ ) {
+ remove_point_button
+ .set_background_hover_color(beestat.style.color.bluegray.light)
+ .set_text_color(beestat.style.color.red.base)
+ .addEventListener('click', this.remove_point_.bind(this));
+ } else {
+ remove_point_button
+ .set_text_color(beestat.style.color.bluegray.dark);
+ }
+
+ // Toggle snap to grid
+ let snapping_icon;
+ let snapping_title;
+ if (this.state_.snapping === true) {
+ snapping_icon = 'grid';
+ snapping_title = 'Disable Snapping [S]';
+ } else {
+ snapping_icon = 'grid_off';
+ snapping_title = 'Enable Snapping [S]';
+ }
+
+ this.button_group_.add_button(new beestat.component.button()
+ .set_icon(snapping_icon)
+ .set_title(snapping_title)
+ .set_text_color(beestat.style.color.gray.light)
+ .set_background_color(beestat.style.color.bluegray.base)
+ .set_background_hover_color(beestat.style.color.bluegray.light)
+ .addEventListener('click', this.toggle_snapping_.bind(this))
+ );
+
+ // Zoom in
+ const zoom_in_button = new beestat.component.button()
+ .set_icon('magnify_plus_outline')
+ .set_title('Zoom In')
+ .set_background_color(beestat.style.color.bluegray.base);
+ this.button_group_.add_button(zoom_in_button);
+
+ if (
+ this.can_zoom_in_() === true
+ ) {
+ zoom_in_button
+ .set_background_hover_color(beestat.style.color.bluegray.light)
+ .set_text_color(beestat.style.color.gray.light)
+ .addEventListener('click', function() {
+ self.zoom_in_();
+ });
+ } else {
+ zoom_in_button
+ .set_text_color(beestat.style.color.bluegray.dark);
+ }
+
+ // Zoom out
+ const zoom_out_button = new beestat.component.button()
+ .set_icon('magnify_minus_outline')
+ .set_title('Zoom out')
+ .set_background_color(beestat.style.color.bluegray.base);
+ this.button_group_.add_button(zoom_out_button);
+
+ if (
+ this.can_zoom_out_() === true
+ ) {
+ zoom_out_button
+ .set_background_hover_color(beestat.style.color.bluegray.light)
+ .set_text_color(beestat.style.color.gray.light)
+ .addEventListener('click', function() {
+ self.zoom_out_();
+ });
+ } else {
+ zoom_out_button
+ .set_text_color(beestat.style.color.bluegray.dark);
+ }
+
+ // Render
+ this.button_group_.render(this.toolbar_container_);
+
+ // FLOORS
+ this.button_group_floors_ = new beestat.component.button_group();
+
+ const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
+ floor_plan.data.groups.forEach(function(group) {
+ const button = new beestat.component.button()
+ .set_title(group.name)
+ .set_text_hover_color(beestat.style.color.lightblue.light)
+ .set_text_color(beestat.style.color.lightblue.base);
+
+ if (group === self.state_.active_group) {
+ button
+ .set_icon(group.icon + '_box');
+ } else {
+ button
+ .set_icon(group.icon)
+ .addEventListener('click', function() {
+ if (self.state_.active_room_entity !== undefined) {
+ self.state_.active_room_entity.set_active(false);
+ }
+ if (self.state_.active_wall_entity !== undefined) {
+ self.state_.active_wall_entity.set_active(false);
+ }
+ if (self.state_.active_point_entity !== undefined) {
+ self.state_.active_point_entity.set_active(false);
+ }
+
+ self.state_.active_group = group;
+ self.dispatchEvent('change_group');
+ });
+ }
+
+ self.button_group_floors_.add_button(button);
+ });
+
+ this.button_group_floors_.render(this.floors_container_);
+};
+
+/**
+ * Update the infobox to match the current state.
+ */
+beestat.component.floor_plan.prototype.update_infobox = function() {
+ const parts = [];
+ if (this.state_.active_room !== undefined) {
+ parts.push(this.state_.active_room.name || 'Unnamed Room');
+ parts.push(
+ Math.abs(
+ Math.round(
+ ClipperLib.Clipper.Area(this.state_.active_room.points) / 144
+ )
+ ).toLocaleString() + ' sqft'
+ );
+ } else {
+ parts.push(this.state_.active_group.name || 'Unnamed Floor');
+ let area = 0;
+ this.state_.active_group.rooms.forEach(function(room) {
+ area += Math.abs(ClipperLib.Clipper.Area(room.points));
+ });
+
+ parts.push(Math.round(area / 144).toLocaleString() + ' sqft');
+ }
+ this.infobox_container_.innerText(parts.join(' • '));
+};
+
+/**
+ * Toggle snapping.
+ */
+beestat.component.floor_plan.prototype.toggle_snapping_ = function() {
+ this.state_.snapping = !this.state_.snapping;
+ this.dispatchEvent('toggle_snapping');
+};
+
+/**
+ * Add a new room.
+ */
+beestat.component.floor_plan.prototype.add_room_ = function() {
+ const new_room_size = 120;
+ const svg_view_box = this.view_box_;
+ const new_room = {
+ 'x': svg_view_box.x + (svg_view_box.width / 2) - (new_room_size / 2),
+ 'y': svg_view_box.y + (svg_view_box.height / 2) - (new_room_size / 2),
+ 'points': [
+ {
+ 'x': 0,
+ 'y': 0
+ },
+ {
+ 'x': new_room_size,
+ 'y': 0
+ },
+ {
+ 'x': new_room_size,
+ 'y': new_room_size
+ },
+ {
+ 'x': 0,
+ 'y': new_room_size
+ }
+ ]
+ };
+ this.state_.active_group.rooms.push(new_room);
+ this.state_.active_room = new_room;
+
+ if (this.state_.active_point_entity !== undefined) {
+ this.state_.active_point_entity.set_active(false);
+ }
+
+ this.dispatchEvent('add_room');
+};
+
+/**
+ * Remove the currently active room.
+ */
+beestat.component.floor_plan.prototype.remove_room_ = function() {
+ const self = this;
+
+ const index = this.state_.active_group.rooms.findIndex(function(active_room) {
+ return active_room === self.state_.active_room;
+ });
+
+ if (this.state_.active_room_entity !== undefined) {
+ this.state_.active_room_entity.set_active(false);
+ }
+ if (this.state_.active_wall_entity !== undefined) {
+ this.state_.active_wall_entity.set_active(false);
+ }
+ if (this.state_.active_point_entity !== undefined) {
+ this.state_.active_point_entity.set_active(false);
+ }
+
+ this.state_.active_group.rooms.splice(index, 1);
+ this.dispatchEvent('remove_room');
+};
+
+/**
+ * Clear the currently active room.
+ */
+beestat.component.floor_plan.prototype.clear_room_ = function() {
+ if (this.state_.active_room_entity !== undefined) {
+ this.state_.active_room_entity.set_active(false);
+ }
+ if (this.state_.active_wall_entity !== undefined) {
+ this.state_.active_wall_entity.set_active(false);
+ }
+ if (this.state_.active_point_entity !== undefined) {
+ this.state_.active_point_entity.set_active(false);
+ }
+ this.dispatchEvent('clear_room');
+};
+
+/**
+ * Remove the currently active point.
+ */
+beestat.component.floor_plan.prototype.remove_point_ = function() {
+ if (this.state_.active_room.points.length > 3) {
+ for (let i = 0; i < this.state_.active_room.points.length; i++) {
+ if (this.state_.active_point === this.state_.active_room.points[i]) {
+ this.state_.active_room.points.splice(i, 1);
+ if (this.state_.active_point_entity !== undefined) {
+ this.state_.active_point_entity.set_active(false);
+ }
+ this.dispatchEvent('remove_point');
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * Add a new point to the active wall.
+ */
+beestat.component.floor_plan.prototype.add_point_ = function() {
+ this.state_.active_wall_entity.add_point();
+};
+
+/**
+ * Set the width of this component. Also updates the view box to the
+ * appropriate values according to the current zoom.
+ *
+ * @param {number} width
+ */
+beestat.component.floor_plan.prototype.set_width = function(width) {
+ this.view_box_.width = this.view_box_.width * width / this.width_;
+ this.width_ = width;
+ this.svg_.setAttribute('width', width);
+ this.update_view_box_();
+ this.state_.floor_plan_width = width;
+};
+
+/**
+ * Zoom
+ *
+ * @param {number} scale_delta
+ * @param {Event} e
+ */
+beestat.component.floor_plan.prototype.zoom_ = function(scale_delta, e) {
+ let local_point;
+ if (e === undefined) {
+ local_point = {
+ 'x': this.width_ / 2,
+ 'y': this.height_ / 2
+ };
+ } else {
+ local_point = this.get_local_point(e);
+ }
+
+ this.view_box_.x -= (local_point.x - this.view_box_.x) * (scale_delta - 1);
+ this.view_box_.y -= (local_point.y - this.view_box_.y) * (scale_delta - 1);
+ this.view_box_.width *= scale_delta;
+ this.view_box_.height *= scale_delta;
+
+ this.update_view_box_();
+
+ this.dispatchEvent('zoom');
+};
+
+/**
+ * Zoom in
+ *
+ * @param {Event} e Optional event when zooming to a specific mouse position.
+ */
+beestat.component.floor_plan.prototype.zoom_in_ = function(e) {
+ if (this.can_zoom_in_() === true) {
+ this.zoom_(0.9, e);
+ }
+};
+
+/**
+ * Zoom out
+ *
+ * @param {Event} e Optional event when zooming to a specific mouse position.
+ */
+beestat.component.floor_plan.prototype.zoom_out_ = function(e) {
+ if (this.can_zoom_out_() === true) {
+ this.zoom_(1.1, e);
+ }
+};
+
+/**
+ * Whether or not you can zoom in.
+ *
+ * @return {boolean} Whether or not you can zoom in.
+ */
+beestat.component.floor_plan.prototype.can_zoom_in_ = function() {
+ const min_width = this.width_ / 4;
+ const min_height = this.height_ / 4;
+
+ if (
+ this.view_box_.width * 0.9 < min_width ||
+ this.view_box_.height * 0.9 < min_height
+ ) {
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Whether or not you can zoom out
+ *
+ * @return {boolean} Whether or not you can zoom in.
+ */
+beestat.component.floor_plan.prototype.can_zoom_out_ = function() {
+ const max_width = this.width_ * 3;
+ const max_height = this.height_ * 3;
+
+ if (
+ this.view_box_.width * 1.1 > max_width ||
+ this.view_box_.height * 1.1 > max_height
+ ) {
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Get the group below the specified one.
+ *
+ * @param {object} group The current group.
+ *
+ * @return {object} The group below the current group.
+ */
+beestat.component.floor_plan.prototype.get_group_below = function(group) {
+ let closest_group;
+ let closest_elevation_diff = Infinity;
+
+ const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
+ floor_plan.data.groups.forEach(function(other_group) {
+ if (
+ other_group.elevation < group.elevation &&
+ group.elevation - other_group.elevation < closest_elevation_diff
+ ) {
+ closest_elevation_diff = group.elevation - other_group.elevation;
+ closest_group = other_group;
+ }
+ });
+
+ return closest_group;
+};
diff --git a/js/component/floor_plan_entity.js b/js/component/floor_plan_entity.js
new file mode 100644
index 0000000..4ea7826
--- /dev/null
+++ b/js/component/floor_plan_entity.js
@@ -0,0 +1,251 @@
+/**
+ * Base class for a high level element that exists on the SVG document.
+ *
+ * @param {object} floor_plan The SVG component this belongs to.
+ * @param {object} state Shared state.
+ */
+beestat.component.floor_plan_entity = function(floor_plan, state) {
+ this.floor_plan_ = floor_plan;
+
+ this.x_ = 0;
+ this.y_ = 0;
+
+ this.g_ = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+
+ beestat.component.apply(this, arguments);
+
+ /**
+ * Override this component's state with a state common to all floor plan
+ * entities.
+ */
+ this.state_ = state;
+};
+beestat.extend(beestat.component.floor_plan_entity, beestat.component);
+
+/**
+ * Render
+ *
+ * @param {SVGGElement} parent
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.prototype.render = function(parent) {
+ if (this.rendered_ === false) {
+ const self = this;
+
+ this.decorate_(this.g_);
+ parent.appendChild(this.g_);
+
+ this.apply_transform_();
+
+ // The element should now exist on the DOM.
+ window.setTimeout(function() {
+ self.dispatchEvent('render');
+ }, 0);
+
+ // The render function was called.
+ this.rendered_ = true;
+ }
+
+ return this;
+};
+
+/**
+ * Rerender
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.prototype.rerender = function() {
+ if (this.rendered_ === true) {
+ this.rendered_ = false;
+
+ var old_g = this.g_;
+ this.g_ = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+
+ this.decorate_(this.g_);
+ old_g.parentNode.replaceChild(this.g_, old_g);
+
+ this.apply_transform_();
+
+ var self = this;
+ window.setTimeout(function() {
+ self.dispatchEvent('render');
+ }, 0);
+
+ this.rendered_ = true;
+ }
+
+ return this;
+};
+
+/**
+ * Bring the current element to the front.
+ */
+beestat.component.floor_plan_entity.prototype.bring_to_front = function() {
+ if (this.rendered_ === true) {
+ this.g_.parentNode.appendChild(this.g_);
+ }
+};
+
+/**
+ * Make this draggable or not.
+ *
+ * @param {boolean} draggable Whether or not this is draggable.
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.prototype.set_draggable_ = function(draggable) {
+ if (draggable === true) {
+ this.g_.addEventListener('mousedown', this.mousedown_handler_.bind(this));
+ }
+
+ this.draggable_ = draggable;
+
+ return this;
+};
+
+/**
+ * Set the x and y positions of this entity.
+ *
+ * @param {number} x The x position of this entity.
+ * @param {number} y The y position of this entity.
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.prototype.set_xy = function(x, y) {
+ if (x !== null) {
+ this.x_ = Math.round(x);
+ }
+
+ if (y !== null) {
+ this.y_ = Math.round(y);
+ }
+
+ this.apply_transform_();
+ return this;
+};
+
+/**
+ * Apply all of the relevant transformations to the SVG document.
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.prototype.apply_transform_ = function() {
+ const x = this.x_ || 0;
+ const y = this.y_ || 0;
+
+ if (x !== 0 || y !== 0) {
+ this.g_.setAttribute(
+ 'transform',
+ 'translate(' + (this.x_ || 0) + ',' + (this.y_ || 0) + ')'
+ );
+ } else {
+ this.g_.removeAttribute('transform');
+ }
+
+ return this;
+};
+/**
+ * Drag start handler
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.prototype.mousedown_handler_ = function(e) {
+ // Don't propagate to things under me.
+ e.stopPropagation();
+
+ this.mousemove_handler_ = this.mousemove_handler_.bind(this);
+ window.addEventListener('mousemove', this.mousemove_handler_);
+
+ this.mouseup_handler_ = this.mouseup_handler_.bind(this);
+ window.addEventListener('mouseup', this.mouseup_handler_);
+
+ this.drag_start_mouse_ = {
+ 'x': e.clientX,
+ 'y': e.clientY
+ };
+
+ this.dragged_ = false;
+
+ this.after_mousedown_handler_(e);
+};
+
+/**
+ * After mousedown.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.prototype.after_mousedown_handler_ = function(e) {
+ // Stub
+};
+
+/**
+ * This handler gets added after mousedown, so it can be assumed that we want
+ * to drag at this point.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.prototype.mousemove_handler_ = function(e) {
+ if (this.dragged_ === false) {
+ this.dispatchEvent('drag_start');
+ this.dragged_ = true;
+ }
+
+ this.after_mousemove_handler_(e);
+};
+
+/**
+ * After mousemove.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.prototype.after_mousemove_handler_ = function(e) {
+ // Stub
+};
+
+/**
+ * Drag stop handler
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.prototype.mouseup_handler_ = function(e) {
+ window.removeEventListener('mousemove', this.mousemove_handler_);
+ window.removeEventListener('mouseup', this.mouseup_handler_);
+
+ delete this.drag_start_entity_;
+ delete this.drag_start_mouse_;
+
+ // If the mouse was actually moved at all then fire the drag stop event.
+ if (this.dragged_ === true) {
+ this.dispatchEvent('drag_stop');
+ }
+
+ this.after_mouseup_handler_(e);
+};
+
+/**
+ * After mouseup.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.prototype.after_mouseup_handler_ = function(e) {
+ // Stub
+};
+
+/**
+ * Get X
+ *
+ * @return {number} x
+ */
+beestat.component.floor_plan_entity.prototype.get_x = function() {
+ return this.x_;
+};
+
+/**
+ * Get Y
+ *
+ * @return {number} y
+ */
+beestat.component.floor_plan_entity.prototype.get_y = function() {
+ return this.y_;
+};
diff --git a/js/component/floor_plan_entity/point.js b/js/component/floor_plan_entity/point.js
new file mode 100644
index 0000000..7db6870
--- /dev/null
+++ b/js/component/floor_plan_entity/point.js
@@ -0,0 +1,353 @@
+/**
+ * Vertex drag point.
+ */
+beestat.component.floor_plan_entity.point = function() {
+ this.snap_lines_ = {};
+
+ beestat.component.floor_plan_entity.apply(this, arguments);
+};
+beestat.extend(beestat.component.floor_plan_entity.point, beestat.component.floor_plan_entity);
+
+/**
+ * Decorate
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.point.prototype.decorate_ = function(parent) {
+ this.decorate_rect_(parent);
+
+ this.set_draggable_(true);
+};
+
+/**
+ * Update the point position to match the current data.
+ */
+beestat.component.floor_plan_entity.point.prototype.update = function() {
+ this.update_rect_();
+};
+
+/**
+ * Decorate the rect point.
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.point.prototype.decorate_rect_ = function(parent) {
+ const self = this;
+
+ const size = 7;
+
+ this.rect_ = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+
+ // Transform slightly to center the rects on the point.
+ this.rect_.setAttribute(
+ 'transform',
+ 'translate(' + (size / -2) + ',' + (size / -2) + ')'
+ );
+
+ this.rect_.setAttribute('width', size);
+ this.rect_.setAttribute('height', size);
+ this.rect_.style.stroke = '#ffffff';
+ this.rect_.style.cursor = 'pointer';
+
+ this.update_rect_();
+
+ this.rect_.addEventListener('mousedown', function() {
+ self.dispatchEvent('mousedown');
+ });
+
+ this.rect_.addEventListener('mouseover', function() {
+ self.hover_ = true;
+ self.update_rect_();
+ });
+
+ this.rect_.addEventListener('mouseout', function() {
+ self.hover_ = false;
+ self.update_rect_();
+ });
+
+ parent.appendChild(this.rect_);
+};
+
+/**
+ * Update the rect to match the current data.
+ */
+beestat.component.floor_plan_entity.point.prototype.update_rect_ = function() {
+ this.rect_.setAttribute('x', this.point_.x);
+ this.rect_.setAttribute('y', this.point_.y);
+
+ if (
+ this.active_ === true ||
+ this.hover_ === true
+ ) {
+ this.rect_.style.fill = beestat.style.color.green.base;
+ } else {
+ this.rect_.style.fill = '#ffffff';
+ }
+};
+
+/**
+ * Set the point
+ *
+ * @param {object} point
+ *
+ * @return {beestat.component.floor_plan_entity.point} This.
+ */
+beestat.component.floor_plan_entity.point.prototype.set_point = function(point) {
+ this.point_ = point;
+
+ return this;
+};
+
+/**
+ * Get the point
+ *
+ * @return {object} The point.
+ */
+beestat.component.floor_plan_entity.point.prototype.get_point = function() {
+ return this.point_;
+};
+
+/**
+ * Set the room the point is part of.
+ *
+ * @param {beestat.component.floor_plan_entity.room} room
+ *
+ * @return {beestat.component.floor_plan_entity.point} This.
+ */
+beestat.component.floor_plan_entity.point.prototype.set_room = function(room) {
+ this.room_ = room;
+
+ return this;
+};
+
+/**
+ * Set the x and y positions of this entity.
+ *
+ * @param {number} x The x position of this entity.
+ * @param {number} y The y position of this entity.
+ *
+ * @return {beestat.component.floor_plan_entity.point} This.
+ */
+beestat.component.floor_plan_entity.point.prototype.set_xy = function(x, y) {
+ if (x !== null) {
+ this.point_.x = Math.round(x);
+ }
+
+ if (y !== null) {
+ this.point_.y = Math.round(y);
+ }
+
+ this.update_rect_();
+
+ return this;
+};
+
+/**
+ * Override to prevent this from happening if the ctrl key is pressed as that
+ * removes a point and should not start a drag.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.point.prototype.mousedown_handler_ = function(e) {
+ if (e.ctrlKey === true) {
+ const points = this.room_.get_room().points;
+ if (points.length > 3) {
+ for (let i = 0; i < points.length; i++) {
+ if (this.point_ === points[i]) {
+ points.splice(i, 1);
+ this.dispatchEvent('remove_point');
+ break;
+ }
+ }
+ }
+
+ e.stopPropagation();
+ return;
+ }
+
+ beestat.component.floor_plan_entity.prototype.mousedown_handler_.apply(this, arguments);
+};
+
+/**
+ * Set an appropriate drag_start_entity_ on mousedown.
+ */
+beestat.component.floor_plan_entity.point.prototype.after_mousedown_handler_ = function() {
+ this.drag_start_entity_ = {
+ 'x': this.point_.x,
+ 'y': this.point_.y
+ };
+};
+
+/**
+ * point dragging a point around. Snaps to X and Y of other points.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.point.prototype.after_mousemove_handler_ = function(e) {
+ const snap_distance = 6;
+
+ let desired_x = this.drag_start_entity_.x + ((e.clientX - this.drag_start_mouse_.x) * this.floor_plan_.get_scale());
+ let desired_y = this.drag_start_entity_.y + ((e.clientY - this.drag_start_mouse_.y) * this.floor_plan_.get_scale());
+
+ if (this.state_.snapping === true) {
+ // Vertical
+ const point_x = this.room_.get_x() + desired_x;
+
+ if (this.snap_line_x_ !== undefined) {
+ this.snap_line_x_.stxle.visibility = 'hidden';
+ }
+
+ // Snap x
+ const room_snap_x = this.room_.get_snap_x();
+ for (let i = 0; i < room_snap_x.length; i++) {
+ const snap_x = room_snap_x[i];
+ const distance = Math.abs(snap_x - point_x);
+ if (distance <= snap_distance) {
+ desired_x = snap_x - this.room_.get_x();
+ break;
+ }
+ }
+
+ // Horizontal
+ const point_y = this.room_.get_y() + desired_y;
+
+ if (this.snap_line_y_ !== undefined) {
+ this.snap_line_y_.style.visibility = 'hidden';
+ }
+
+ // Snap Y
+ const room_snap_y = this.room_.get_snap_y();
+ for (let i = 0; i < room_snap_y.length; i++) {
+ const snap_y = room_snap_y[i];
+ const distance = Math.abs(snap_y - point_y);
+ if (distance <= snap_distance) {
+ desired_y = snap_y - this.room_.get_y();
+ break;
+ }
+ }
+
+ this.update_snap_lines_();
+ } else {
+ this.clear_snap_lines_();
+ }
+
+ // Update
+ this.set_xy(
+ desired_x,
+ desired_y
+ );
+
+ this.dispatchEvent('update');
+};
+
+/**
+ * point what happens when you stop moving the point.
+ */
+beestat.component.floor_plan_entity.point.prototype.after_mouseup_handler_ = function() {
+ this.clear_snap_lines_();
+};
+
+/**
+ * Update snap lines to match the current data.
+ */
+beestat.component.floor_plan_entity.point.prototype.update_snap_lines_ = function() {
+ /**
+ * If the current x matches one of the room snap x positions, then
+ * add/update the current snap line. Otherwise remove it.
+ */
+ const point_x = this.room_.get_x() + this.point_.x;
+ if (this.room_.get_snap_x().includes(point_x) === true) {
+ if (this.snap_lines_.x === undefined) {
+ this.snap_lines_.x = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ this.snap_lines_.x.style.strokeDasharray = '7, 3';
+ this.snap_lines_.x.style.stroke = beestat.style.color.yellow.base;
+ this.snap_lines_.x.setAttribute('y1', this.floor_plan_.get_grid_pixels() / -2);
+ this.snap_lines_.x.setAttribute('y2', this.floor_plan_.get_grid_pixels() / 2);
+ this.floor_plan_.get_g().appendChild(this.snap_lines_.x);
+ }
+ this.snap_lines_.x.setAttribute('x1', point_x);
+ this.snap_lines_.x.setAttribute('x2', point_x);
+ } else if (this.snap_lines_.x !== undefined) {
+ this.snap_lines_.x.parentNode.removeChild(this.snap_lines_.x);
+ delete this.snap_lines_.x;
+ }
+
+ /**
+ * If the current x matches one of the room snap y positions, then
+ * add/update the current snap line. Otherwise remove it.
+ */
+ const point_y = this.room_.get_y() + this.point_.y;
+ if (this.room_.get_snap_y().includes(point_y) === true) {
+ if (this.snap_lines_.y === undefined) {
+ this.snap_lines_.y = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ this.snap_lines_.y.style.strokeDasharray = '7, 3';
+ this.snap_lines_.y.style.stroke = beestat.style.color.yellow.base;
+ this.snap_lines_.y.setAttribute('x1', this.floor_plan_.get_grid_pixels() / -2);
+ this.snap_lines_.y.setAttribute('x2', this.floor_plan_.get_grid_pixels() / 2);
+ this.floor_plan_.get_g().appendChild(this.snap_lines_.y);
+ }
+ this.snap_lines_.y.setAttribute('y1', point_y);
+ this.snap_lines_.y.setAttribute('y2', point_y);
+ } else if (this.snap_lines_.y !== undefined) {
+ this.snap_lines_.y.parentNode.removeChild(this.snap_lines_.y);
+ delete this.snap_lines_.y;
+ }
+};
+
+/**
+ * Clear all existing snap lines.
+ */
+beestat.component.floor_plan_entity.point.prototype.clear_snap_lines_ = function() {
+ if (this.snap_lines_.x !== undefined) {
+ this.snap_lines_.x.parentNode.removeChild(this.snap_lines_.x);
+ delete this.snap_lines_.x;
+ }
+ if (this.snap_lines_.y !== undefined) {
+ this.snap_lines_.y.parentNode.removeChild(this.snap_lines_.y);
+ delete this.snap_lines_.y;
+ }
+};
+
+/**
+ * Make this point active or not.
+ *
+ * @param {boolean} active Whether or not the point is active.
+ *
+ * @return {beestat.component.floor_plan_entity.point} This.
+ */
+beestat.component.floor_plan_entity.point.prototype.set_active = function(active) {
+ if (active !== this.active_) {
+ this.active_ = active;
+
+ if (this.active_ === true) {
+ // Inactivate any other active point.
+ if (
+ this.state_.active_point_entity !== undefined &&
+ this.state_.active_point !== this.point_
+ ) {
+ this.state_.active_point_entity.set_active(false);
+ }
+
+ // Inactivate any other active wall.
+ if (
+ this.state_.active_wall_entity !== undefined
+ ) {
+ this.state_.active_wall_entity.set_active(false);
+ }
+
+ this.state_.active_point = this.point_;
+ this.state_.active_point_entity = this;
+
+ this.dispatchEvent('activate');
+ } else {
+ delete this.state_.active_point;
+ delete this.state_.active_point_entity;
+ }
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+ }
+
+ return this;
+};
diff --git a/js/component/floor_plan_entity/room.js b/js/component/floor_plan_entity/room.js
new file mode 100644
index 0000000..6e20279
--- /dev/null
+++ b/js/component/floor_plan_entity/room.js
@@ -0,0 +1,614 @@
+/**
+ * Floor plan room.
+ *
+ * @param {object} room The room.
+ */
+beestat.component.floor_plan_entity.room = function() {
+ this.walls_ = [];
+ this.point_entities_ = [];
+ this.enabled_ = true;
+
+ this.snap_lines_ = {
+ 'x': {},
+ 'y': {}
+ };
+ beestat.component.floor_plan_entity.apply(this, arguments);
+};
+beestat.extend(beestat.component.floor_plan_entity.room, beestat.component.floor_plan_entity);
+
+/**
+ * Decorate
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.prototype.decorate_ = function(parent) {
+ this.decorate_polygon_(parent);
+ if (this.active_ === true) {
+ this.decorate_walls_(parent);
+ this.decorate_points_(parent);
+ this.update_snap_points_(parent);
+ }
+};
+
+/**
+ * Draw the polygon.
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.prototype.decorate_polygon_ = function(parent) {
+ const self = this;
+
+ this.polygon_ = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
+ parent.appendChild(this.polygon_);
+
+ this.polygon_.style.strokeWidth = '2';
+
+ if (this.active_ === true) {
+ this.set_draggable_(true);
+
+ this.polygon_.style.cursor = 'pointer';
+ this.polygon_.style.fillOpacity = '0.5';
+ this.polygon_.style.fill = beestat.style.color.green.light;
+ this.polygon_.style.stroke = '#ffffff';
+ this.polygon_.style.filter = 'drop-shadow(3px 3px 3px #000000)';
+ } else if (this.enabled_ === true) {
+ this.polygon_.style.cursor = 'pointer';
+ this.polygon_.style.fillOpacity = '0.5';
+ this.polygon_.style.fill = beestat.style.color.blue.base;
+ this.polygon_.style.stroke = beestat.style.color.gray.base;
+ } else {
+ this.polygon_.style.cursor = 'default';
+ this.polygon_.style.fillOpacity = '0.2';
+ this.polygon_.style.fill = beestat.style.color.gray.base;
+ this.polygon_.style.stroke = beestat.style.color.gray.dark;
+ }
+
+ // Activate room on click if the mouse didn't move.
+ if (this.enabled_ === true) {
+ this.polygon_.addEventListener('mousedown', function(e) {
+ self.mousedown_mouse_ = {
+ 'x': e.clientX,
+ 'y': e.clientY
+ };
+ });
+ this.polygon_.addEventListener('mouseup', function(e) {
+ if (
+ e.clientX === self.mousedown_mouse_.x &&
+ e.clientY === self.mousedown_mouse_.y
+ ) {
+ self.set_active(true);
+ }
+ });
+ }
+
+ this.update_polygon_();
+};
+
+/**
+ * Update the points attribute of the polygon to match the current data.
+ */
+beestat.component.floor_plan_entity.prototype.update_polygon_ = function() {
+ const points = [];
+ this.room_.points.forEach(function(point) {
+ points.push(point.x + ',' + point.y);
+ });
+ this.polygon_.setAttribute('points', points.join(' '));
+};
+
+/**
+ * Add drag pointx for each point.
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.prototype.decorate_points_ = function(parent) {
+ const self = this;
+
+ this.room_.points.forEach(function(point) {
+ const point_entity = new beestat.component.floor_plan_entity.point(self.floor_plan_, self.state_)
+ .set_room(self)
+ .set_point(point)
+ .render(parent);
+
+ // Update when a point is moved
+ point_entity.addEventListener('update', function() {
+ self.update_polygon_();
+ self.update_walls_();
+ // self.dispatchEvent('update');
+ });
+
+ // When a point is done moving normalize the points
+ point_entity.addEventListener('drag_stop', function() {
+ self.normalize_points_();
+ self.update_points_();
+ self.update_walls_();
+ self.update_polygon_();
+ self.update_snap_points_();
+ self.dispatchEvent('update');
+ });
+
+ // Activate on click
+ point_entity.addEventListener('mousedown', function() {
+ point_entity.set_active(true);
+ });
+
+ // Add toolbar button on activate.
+ point_entity.addEventListener('activate', function() {
+ self.floor_plan_.update_toolbar();
+ });
+
+ // Activate the currently active point (mostly for rerenders).
+ if (self.state_.active_point === point) {
+ point_entity.set_active(true);
+ }
+
+ self.point_entities_.push(point_entity);
+ });
+};
+
+/**
+ * Update the points to match the current data.
+ */
+beestat.component.floor_plan_entity.prototype.update_points_ = function() {
+ this.point_entities_.forEach(function(point_entity) {
+ point_entity.update();
+ });
+};
+
+/**
+ * Add walls between each point.
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.prototype.decorate_walls_ = function(parent) {
+ const self = this;
+
+ this.room_.points.forEach(function(point, i) {
+ const i_1 = i;
+ const i_2 = (i + 1) % self.room_.points.length;
+
+ const wall_entity = new beestat.component.floor_plan_entity.wall(self.floor_plan_, self.state_)
+ .set_room(self)
+ .set_point_1(self.room_.points[i_1])
+ .set_point_2(self.room_.points[i_2])
+ .render(parent);
+ self.walls_.push(wall_entity);
+
+ wall_entity.addEventListener('update', function() {
+ self.update_polygon_();
+ self.update_points_();
+ self.update_walls_();
+ });
+
+ // Clear any active points on drag start.
+ wall_entity.addEventListener('drag_start', function() {
+ if (self.active_point_entity_ !== undefined) {
+ self.active_point_entity_.set_active(false);
+ delete self.active_point_entity_;
+ }
+ });
+
+ wall_entity.addEventListener('drag_stop', function() {
+ self.normalize_points_();
+ self.update_polygon_();
+ self.update_points_();
+ self.update_walls_();
+ self.update_snap_points_();
+ self.dispatchEvent('update');
+ });
+
+ wall_entity.addEventListener('add_point', function() {
+ self.rerender();
+ self.dispatchEvent('update');
+ });
+
+ // Activate on mousedown
+ wall_entity.addEventListener('mousedown', function() {
+ wall_entity.set_active(true);
+ });
+
+ // Add toolbar button on activate.
+ wall_entity.addEventListener('activate', function() {
+ self.floor_plan_.update_toolbar();
+ });
+
+ // Activate the currently active wall (mostly for rerenders).
+ if (
+ self.state_.active_wall_entity !== undefined &&
+ point === self.state_.active_wall_entity.get_point_1()
+ ) {
+ wall_entity.set_active(true);
+ }
+ });
+};
+
+/**
+ * Update the walls to match the current data.
+ */
+beestat.component.floor_plan_entity.prototype.update_walls_ = function() {
+ this.walls_.forEach(function(wall) {
+ wall.update();
+ });
+};
+
+/**
+ * Make this room active or not.
+ *
+ * @param {boolean} active Whether or not the room is active.
+ *
+ * @return {beestat.component.floor_plan_entity.room} This.
+ */
+beestat.component.floor_plan_entity.room.prototype.set_active = function(active) {
+ if (active !== this.active_) {
+ this.active_ = active;
+
+ if (this.active_ === true) {
+ // Inactivate any other active room.
+ if (
+ this.state_.active_room_entity !== undefined &&
+ this.state_.active_room !== this.room_
+ ) {
+ this.state_.active_room_entity.set_active(false);
+ }
+
+ this.state_.active_room = this.room_;
+ this.state_.active_room_entity = this;
+
+ this.dispatchEvent('activate');
+ this.update_snap_points_();
+ this.bring_to_front();
+ } else {
+ // throw 'foo';
+ delete this.state_.active_room;
+ delete this.state_.active_room_entity;
+
+ if (this.state_.active_wall_entity !== undefined) {
+ this.state_.active_wall_entity.set_active(false);
+ }
+
+ if (this.state_.active_point_entity !== undefined) {
+ this.state_.active_point_entity.set_active(false);
+ }
+ }
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ this.floor_plan_.update_toolbar();
+ }
+
+ return this;
+};
+
+/**
+ * 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) {
+ room.points.forEach(function(point) {
+ snap_x[point.x + room.x] = true;
+ snap_y[point.y + room.y] = true;
+ });
+ });
+
+ // Snap to rooms in the group under this one.
+ const group_below = this.floor_plan_.get_group_below(this.group_);
+ if (group_below !== undefined) {
+ group_below.rooms.forEach(function(room) {
+ room.points.forEach(function(point) {
+ snap_x[point.x + room.x] = true;
+ snap_y[point.y + room.y] = true;
+ });
+ });
+ }
+
+ 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);
+ });
+};
+
+/**
+ * Get the room.
+ *
+ * @return {object} The room.
+ */
+beestat.component.floor_plan_entity.room.prototype.get_room = function() {
+ return this.room_;
+};
+
+/**
+ * Set the room.
+ *
+ * @param {object} room
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.room.prototype.set_room = function(room) {
+ this.room_ = room;
+ this.x_ = room.x;
+ this.y_ = room.y;
+
+ return this;
+};
+
+/**
+ * Set the group this room is part of. Used so the room can look at other
+ * points in the group for snapping.
+ *
+ * @param {object} group
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.room.prototype.set_group = function(group) {
+ this.group_ = group;
+
+ return this;
+};
+
+/**
+ * Set the x and y positions of this entity.
+ *
+ * @param {number} x The x position of this entity.
+ * @param {number} y The y position of this entity.
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.room.prototype.set_xy = function(x, y) {
+ this.room_.x = Math.round(x);
+ this.room_.y = Math.round(y);
+
+ return beestat.component.floor_plan_entity.prototype.set_xy.apply(this, arguments);
+};
+
+/**
+ * Normalize the points so that they always fill the top and left. This
+ * prevents weird stuff like a polygon existing at 0,0 but the points all
+ * being shifted really far off which causes other issues.
+ */
+beestat.component.floor_plan_entity.room.prototype.normalize_points_ = function() {
+ const x_values = [];
+ const y_values = [];
+
+ this.room_.points.forEach(function(point) {
+ x_values.push(point.x);
+ y_values.push(point.y);
+ });
+
+ const min_x = Math.min.apply(null, x_values);
+ const min_y = Math.min.apply(null, y_values);
+
+ this.room_.points.forEach(function(point) {
+ point.x -= min_x;
+ point.y -= min_y;
+ });
+
+ this.set_xy(this.x_ + min_x, this.y_ + min_y);
+};
+
+/**
+ * Set an appropriate drag_start_entity_ on mousedown.
+ */
+beestat.component.floor_plan_entity.room.prototype.after_mousedown_handler_ = function() {
+ this.drag_start_entity_ = {
+ 'x': this.x_,
+ 'y': this.y_
+ };
+};
+
+/**
+ * point dragging the room around. Snaps to X and Y of other points.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.room.prototype.after_mousemove_handler_ = function(e) {
+ const self = this;
+
+ let desired_x = this.drag_start_entity_.x + ((e.clientX - this.drag_start_mouse_.x) * this.floor_plan_.get_scale());
+ let desired_y = this.drag_start_entity_.y + ((e.clientY - this.drag_start_mouse_.y) * this.floor_plan_.get_scale());
+
+ // Snap
+ if (this.state_.snapping === true) {
+ const snap_distance = 6;
+ this.room_.points.forEach(function(point) {
+ const point_x = point.x + desired_x;
+ const point_y = point.y + desired_y;
+
+ // Snap X
+ for (let i = 0; i < self.snap_x_.length; i++) {
+ const snap_x = self.snap_x_[i];
+ const distance = Math.abs(snap_x - point_x);
+ if (distance <= snap_distance) {
+ desired_x = snap_x - point.x;
+ break;
+ }
+ }
+
+ // Snap Y
+ for (let i = 0; i < self.snap_y_.length; i++) {
+ const snap_y = self.snap_y_[i];
+ const distance = Math.abs(snap_y - point_y);
+ if (distance <= snap_distance) {
+ desired_y = snap_y - point.y;
+ break;
+ }
+ }
+ });
+
+ this.update_snap_lines_();
+ } else {
+ this.clear_snap_lines_();
+ }
+
+ this.set_xy(
+ desired_x,
+ desired_y
+ );
+};
+
+/**
+ * What happens when you stop moving the room.
+ */
+beestat.component.floor_plan_entity.room.prototype.after_mouseup_handler_ = function() {
+ if (this.dragged_ === true) {
+ this.clear_snap_lines_();
+ this.update_snap_points_();
+ this.dispatchEvent('update');
+ }
+};
+
+/**
+ * Update snap lines to match the current data.
+ */
+beestat.component.floor_plan_entity.room.prototype.update_snap_lines_ = function() {
+ const self = this;
+
+ let current_snap_x = {};
+ this.room_.points.forEach(function(point) {
+ current_snap_x[point.x + self.room_.x] = true;
+ });
+
+ // Remove any snap lines that no longer exist.
+ for (let x in this.snap_lines_.x) {
+ if (current_snap_x[x] === undefined) {
+ this.snap_lines_.x[x].parentNode.removeChild(this.snap_lines_.x[x]);
+ delete this.snap_lines_.x[x];
+ }
+ }
+
+ current_snap_x = Object.keys(current_snap_x).map(function(key) {
+ return Number(key);
+ });
+
+ const intersected_snap_x = this.snap_x_.filter(function(x) {
+ return current_snap_x.includes(x) === true;
+ });
+
+ // Add any new snap lines.
+ intersected_snap_x.forEach(function(x) {
+ if (self.snap_lines_.x[x] === undefined) {
+ self.snap_lines_.x[x] = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ self.snap_lines_.x[x].style.strokeDasharray = '7, 3';
+ self.snap_lines_.x[x].style.stroke = beestat.style.color.yellow.base;
+ self.snap_lines_.x[x].setAttribute('x1', x);
+ self.snap_lines_.x[x].setAttribute('x2', x);
+ self.snap_lines_.x[x].setAttribute('y1', self.floor_plan_.get_grid_pixels() / -2);
+ self.snap_lines_.x[x].setAttribute('y2', self.floor_plan_.get_grid_pixels() / 2);
+ self.floor_plan_.get_g().appendChild(self.snap_lines_.x[x]);
+ }
+ });
+
+ let current_snap_y = {};
+ this.room_.points.forEach(function(point) {
+ current_snap_y[point.y + self.room_.y] = true;
+ });
+
+ // Remove any snap lines that no longer exist.
+ for (let y in this.snap_lines_.y) {
+ if (current_snap_y[y] === undefined) {
+ this.snap_lines_.y[y].parentNode.removeChild(this.snap_lines_.y[y]);
+ delete this.snap_lines_.y[y];
+ }
+ }
+
+ current_snap_y = Object.keys(current_snap_y).map(function(key) {
+ return Number(key);
+ });
+
+ const intersected_snap_y = this.snap_y_.filter(function(y) {
+ return current_snap_y.includes(y) === true;
+ });
+
+ // Add any new snap lines.
+ intersected_snap_y.forEach(function(y) {
+ if (self.snap_lines_.y[y] === undefined) {
+ self.snap_lines_.y[y] = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ self.snap_lines_.y[y].style.strokeDasharray = '7, 3';
+ self.snap_lines_.y[y].style.stroke = beestat.style.color.yellow.base;
+ self.snap_lines_.y[y].setAttribute('y1', y);
+ self.snap_lines_.y[y].setAttribute('y2', y);
+ self.snap_lines_.y[y].setAttribute('x1', self.floor_plan_.get_grid_pixels() / -2);
+ self.snap_lines_.y[y].setAttribute('x2', self.floor_plan_.get_grid_pixels() / 2);
+ self.floor_plan_.get_g().appendChild(self.snap_lines_.y[y]);
+ }
+ });
+};
+
+/**
+ * Clear all existing snap lines.
+ */
+beestat.component.floor_plan_entity.room.prototype.clear_snap_lines_ = function() {
+ for (var x in this.snap_lines_.x) {
+ this.snap_lines_.x[x].parentNode.removeChild(this.snap_lines_.x[x]);
+ delete this.snap_lines_.x[x];
+ }
+ for (var y in this.snap_lines_.y) {
+ this.snap_lines_.y[y].parentNode.removeChild(this.snap_lines_.y[y]);
+ delete this.snap_lines_.y[y];
+ }
+};
+
+/**
+ * Get Snap X
+ *
+ * @return {array} Snap X
+ */
+beestat.component.floor_plan_entity.room.prototype.get_snap_x = function() {
+ return this.snap_x_;
+};
+
+/**
+ * Get Snap Y
+ *
+ * @return {array} Snap Y
+ */
+beestat.component.floor_plan_entity.room.prototype.get_snap_y = function() {
+ return this.snap_y_;
+};
+
+/**
+ * Set enabled (different than active)
+ *
+ * @param {boolean} enabled
+ *
+ * @return {beestat.component.floor_plan_entity} This.
+ */
+beestat.component.floor_plan_entity.room.prototype.set_enabled = function(enabled) {
+ this.enabled_ = enabled;
+
+ return this;
+};
+
+// Keeping this around as I may use it.
+// https://stackoverflow.com/a/16283349
+/*beestat.component.floor_plan_entity.room.prototype.get_centroid_ = function () {
+ var x = 0,
+ y = 0,
+ i,
+ j,
+ f,
+ point1,
+ point2;
+
+ for (i = 0, j = this.room_.points.length - 1; i < this.room_.points.length; j=i,i++) {
+ point1 = this.room_.points[i];
+ point2 = this.room_.points[j];
+ f = point1.x * point2.y - point2.x * point1.y;
+ x += (point1.x + point2.x) * f;
+ y += (point1.y + point2.y) * f;
+ }
+
+ const area = Math.abs(
+ ClipperLib.Clipper.Area(this.room_.points)
+ );
+
+ f = area * 6;
+
+ return {'x': x / f, 'y': y / f};
+};*/
diff --git a/js/component/floor_plan_entity/wall.js b/js/component/floor_plan_entity/wall.js
new file mode 100644
index 0000000..6dbbe94
--- /dev/null
+++ b/js/component/floor_plan_entity/wall.js
@@ -0,0 +1,570 @@
+/**
+ * Wall.
+ */
+beestat.component.floor_plan_entity.wall = function() {
+ this.snap_lines_ = {};
+
+ beestat.component.floor_plan_entity.apply(this, arguments);
+};
+beestat.extend(beestat.component.floor_plan_entity.wall, beestat.component.floor_plan_entity);
+
+/**
+ * Decorate
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.wall.prototype.decorate_ = function(parent) {
+ this.decorate_line_(parent);
+
+ this.set_draggable_(true);
+
+ parent.appendChild(this.path_);
+};
+
+/**
+ * Decorate path.
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.wall.prototype.decorate_line_ = function(parent) {
+ const self = this;
+
+ this.path_ = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+
+ this.path_id_ = String(Math.random());
+ this.path_.setAttribute('id', this.path_id_);
+ this.path_.style.strokeWidth = '6';
+
+ if (this.active_ === true) {
+ this.path_.style.stroke = beestat.style.color.green.base;
+ this.path_.style.opacity = 0.5;
+ } else {
+ this.path_.style.stroke = '#ffffff';
+ this.path_.style.opacity = 0.2;
+ }
+
+ this.path_.addEventListener('mousedown', function() {
+ self.dispatchEvent('mousedown');
+ });
+
+ this.decorate_text_(parent);
+
+ this.update_line_();
+
+ // Update the line if the ctrl key is pressed.
+ this.floor_plan_.addEventListener('ctrl_key', function() {
+ self.update_line_();
+ });
+
+ this.path_.addEventListener('dblclick', this.add_point.bind(this));
+};
+
+/**
+ * Add a point to the room along this wall.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.wall.prototype.add_point = function(e) {
+ const room = this.room_.get_room();
+ for (let i = 0; i < room.points.length; i++) {
+ if (this.point_1_ === room.points[i]) {
+ /**
+ * First convert the window coordinate space into SVG coordinate space.
+ * Then project the clicked point onto the line so the line stays nice
+ * and straight.
+ */
+ let local_point;
+ if (e !== undefined) {
+ local_point = this.floor_plan_.get_local_point(e);
+ } else {
+ local_point = {
+ 'x': room.x + ((this.point_1_.x + this.point_2_.x) / 2),
+ 'y': room.y + ((this.point_1_.y + this.point_2_.y) / 2)
+ };
+ }
+
+ const projected_point = this.project_point_(
+ {
+ 'x': (local_point.x - room.x),
+ 'y': (local_point.y - room.y)
+ },
+ this.point_1_,
+ this.point_2_
+ );
+
+ room.points.splice(
+ i + 1,
+ 0,
+ projected_point
+ );
+
+ this.state_.active_point = projected_point;
+ if (this.state_.active_wall_entity !== undefined) {
+ this.state_.active_wall_entity.set_active(false);
+ }
+
+ this.dispatchEvent('add_point');
+ break;
+ }
+ }
+};
+
+/**
+ * Update everything to match current data.
+ */
+beestat.component.floor_plan_entity.wall.prototype.update = function() {
+ this.update_line_();
+ this.update_text_();
+};
+
+/**
+ * Update line to match current data.
+ */
+beestat.component.floor_plan_entity.wall.prototype.update_line_ = function() {
+ // Draw the path in a specific direction so the text attached is consistent.
+ const x_distance = Math.abs(this.point_1_.x - this.point_2_.x);
+ const y_distance = Math.abs(this.point_1_.y - this.point_2_.y);
+
+ let from_point;
+ let to_point;
+ if (x_distance > y_distance) {
+ if (this.point_1_.x < this.point_2_.x) {
+ from_point = this.point_1_;
+ to_point = this.point_2_;
+ } else {
+ from_point = this.point_2_;
+ to_point = this.point_1_;
+ }
+ } else {
+ if (this.point_2_.y < this.point_1_.y) {
+ from_point = this.point_1_;
+ to_point = this.point_2_;
+ } else {
+ from_point = this.point_2_;
+ to_point = this.point_1_;
+ }
+ }
+
+ const path_parts = [];
+ path_parts.push('M' + from_point.x + ',' + from_point.y);
+ path_parts.push('L' + to_point.x + ',' + to_point.y);
+ this.path_.setAttribute('d', path_parts.join(' '));
+
+ if (this.is_horizontal_() === true) {
+ this.path_.style.cursor = 'n-resize';
+ } else if (this.is_vertical_() === true) {
+ this.path_.style.cursor = 'e-resize';
+ } else {
+ this.path_.style.cursor = 'copy';
+ }
+};
+
+/**
+ * Project a point onto a line.
+ *
+ * @link https://jsfiddle.net/soulwire/UA6H5/
+ * @link https://stackoverflow.com/q/32281168
+ *
+ * @param {object} p The point.
+ * @param {object} a The first point of the line.
+ * @param {object} b The second point of the line.
+ *
+ * @return {object} The projected point.
+ */
+beestat.component.floor_plan_entity.wall.prototype.project_point_ = function(p, a, b) {
+ var atob = {'x': b.x - a.x,
+ 'y': b.y - a.y};
+ var atop = {'x': p.x - a.x,
+ 'y': p.y - a.y};
+ var len = (atob.x * atob.x) + (atob.y * atob.y);
+ var dot = (atop.x * atob.x) + (atop.y * atob.y);
+ var t = Math.min(1, Math.max(0, dot / len));
+
+ dot = ((b.x - a.x) * (p.y - a.y)) - ((b.y - a.y) * (p.x - a.x));
+
+ return {
+ 'x': a.x + (atob.x * t),
+ 'y': a.y + (atob.y * t)
+ };
+};
+
+/**
+ * Decorate the wall length.
+ *
+ * @param {SVGGElement} parent
+ */
+beestat.component.floor_plan_entity.wall.prototype.decorate_text_ = function(parent) {
+ this.text_ = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+ this.text_.style.fontFamily = 'Montserrat';
+ this.text_.style.fontWeight = '300';
+ this.text_.style.fill = '#ffffff';
+ this.text_.style.textAnchor = 'middle';
+ this.text_.style.letterSpacing = '-0.5px';
+ this.text_.style.dominantBaseline = 'hanging';
+ this.text_.setAttribute('dy', '5');
+
+ this.text_path_ = document.createElementNS('http://www.w3.org/2000/svg', 'textPath');
+ this.text_path_.setAttribute('href', '#' + this.path_id_);
+ this.text_path_.setAttribute('startOffset', '50%');
+ this.text_.appendChild(this.text_path_);
+
+ this.update_text_();
+
+ parent.appendChild(this.text_);
+};
+
+/**
+ * Update the wall length to match the current data.
+ */
+beestat.component.floor_plan_entity.wall.prototype.update_text_ = function() {
+ // Set the string content
+ const length = this.get_length_();
+
+ // Shrink the font slightly for short walls.
+ if (length < 24) {
+ this.text_.style.fontSize = '6px';
+ } else if (length < 48) {
+ this.text_.style.fontSize = '8px';
+ } else {
+ this.text_.style.fontSize = '11px';
+ }
+
+ const length_feet = Math.floor(length / 12);
+ const length_inches = length % 12;
+
+ let length_parts = [];
+ length_parts.push(length_feet + '\'');
+ length_parts.push(length_inches + '"');
+
+ const length_string = length_parts.join(' ');
+ this.text_path_.textContent = length_string;
+};
+
+/**
+ * Set the first point. Assume this is the position of the wall.
+ *
+ * @param {object} point_1
+ *
+ * @return {beestat.component.floor_plan_entity.wall} This.
+ */
+beestat.component.floor_plan_entity.wall.prototype.set_point_1 = function(point_1) {
+ this.point_1_ = point_1;
+
+ return this;
+};
+
+/**
+ * Get point_1.
+ *
+ * @return {object} point_1
+ */
+beestat.component.floor_plan_entity.wall.prototype.get_point_1 = function() {
+ return this.point_1_;
+};
+
+/**
+ * Set the second point
+ *
+ * @param {object} point_2
+ *
+ * @return {beestat.component.floor_plan_entity.wall} This.
+ */
+beestat.component.floor_plan_entity.wall.prototype.set_point_2 = function(point_2) {
+ this.point_2_ = point_2;
+
+ return this;
+};
+
+/**
+ * Get point_2.
+ *
+ * @return {object} point_2
+ */
+beestat.component.floor_plan_entity.wall.prototype.get_point_2 = function() {
+ return this.point_2_;
+};
+
+/**
+ * Set the room the wall is part of.
+ *
+ * @param {beestat.component.floor_plan_entity.room} room
+ *
+ * @return {beestat.component.floor_plan_entity.wall} This.
+ */
+beestat.component.floor_plan_entity.wall.prototype.set_room = function(room) {
+ this.room_ = room;
+
+ return this;
+};
+
+/**
+ * Set the x and y positions of this entity. This just updates the points as
+ * the entity itself is not translated.
+ *
+ * @param {number} x The x position of this entity.
+ * @param {number} y The y position of this entity.
+ *
+ * @return {beestat.component.floor_plan_entity.wall} This.
+ */
+beestat.component.floor_plan_entity.wall.prototype.set_xy = function(x, y) {
+ if (x !== null) {
+ this.point_1_.x = Math.round(x);
+ this.point_2_.x = Math.round(x);
+ }
+
+ if (y !== null) {
+ this.point_1_.y = Math.round(y);
+ this.point_2_.y = Math.round(y);
+ }
+
+ return this;
+};
+
+/**
+ * Handle dragging a wall around. Snaps to X and Y of other handles.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.wall.prototype.after_mousemove_handler_ = function(e) {
+ const snap_distance = 6;
+
+ if (this.is_vertical_() === true) {
+ let desired_x = this.drag_start_entity_.x + ((e.clientX - this.drag_start_mouse_.x) * this.floor_plan_.get_scale());
+
+ if (this.state_.snapping === true) {
+ const point_x = this.room_.get_x() + desired_x;
+
+ if (this.snap_line_x_ !== undefined) {
+ this.snap_line_x_.stxle.visibility = 'hidden';
+ }
+
+ // Snap x
+ const room_snap_x = this.room_.get_snap_x();
+ for (let i = 0; i < room_snap_x.length; i++) {
+ const snap_x = room_snap_x[i];
+ const distance = Math.abs(snap_x - point_x);
+ if (distance <= snap_distance) {
+ desired_x = snap_x - this.room_.get_x();
+ break;
+ }
+ }
+ this.update_snap_lines_();
+ } else {
+ this.clear_snap_lines_();
+ }
+
+ this.set_xy(
+ desired_x,
+ null
+ );
+ } else if (this.is_horizontal_() === true) {
+ let desired_y = this.drag_start_entity_.y + ((e.clientY - this.drag_start_mouse_.y) * this.floor_plan_.get_scale());
+
+ if (this.state_.snapping === true) {
+ const point_y = this.room_.get_y() + desired_y;
+
+ if (this.snap_line_y_ !== undefined) {
+ this.snap_line_y_.style.visibility = 'hidden';
+ }
+
+ // Snap Y
+ const room_snap_y = this.room_.get_snap_y();
+ for (let i = 0; i < room_snap_y.length; i++) {
+ const snap_y = room_snap_y[i];
+ const distance = Math.abs(snap_y - point_y);
+ if (distance <= snap_distance) {
+ desired_y = snap_y - this.room_.get_y();
+ break;
+ }
+ }
+ this.update_snap_lines_();
+ } else {
+ this.clear_snap_lines_();
+ }
+
+ this.set_xy(
+ null,
+ desired_y
+ );
+ }
+
+ this.dispatchEvent('update');
+};
+
+/**
+ * Handle what happens when you stop moving the wall.
+ */
+beestat.component.floor_plan_entity.wall.prototype.after_mouseup_handler_ = function() {
+ this.clear_snap_lines_();
+};
+
+/**
+ * Override to prevent this from happening if the ctrl key is pressed as that
+ * adds a point and should not start a drag.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.wall.prototype.mousedown_handler_ = function(e) {
+ if (e.ctrlKey === true) {
+ e.stopPropagation();
+ return;
+ }
+
+ beestat.component.floor_plan_entity.prototype.mousedown_handler_.apply(this, arguments);
+};
+
+/**
+ * Set an appropriate drag_start_entity_ on mousedown.
+ *
+ * @param {Event} e
+ */
+beestat.component.floor_plan_entity.wall.prototype.after_mousedown_handler_ = function() {
+ this.drag_start_entity_ = {
+ 'x': this.point_1_.x,
+ 'y': this.point_1_.y
+ };
+};
+
+/**
+ * Update snap lines to match the current data.
+ */
+beestat.component.floor_plan_entity.wall.prototype.update_snap_lines_ = function() {
+ /**
+ * If the current x matches one of the room snap x positions, then
+ * add/update the current snap line. Otherwise remove it.
+ */
+ if (this.is_vertical_() === true) {
+ const point_x = this.room_.get_x() + this.point_1_.x;
+ if (this.room_.get_snap_x().includes(point_x) === true) {
+ if (this.snap_lines_.x === undefined) {
+ this.snap_lines_.x = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ this.snap_lines_.x.style.strokeDasharray = '7, 3';
+ this.snap_lines_.x.style.stroke = beestat.style.color.yellow.base;
+ this.snap_lines_.x.setAttribute('y1', this.floor_plan_.get_grid_pixels() / -2);
+ this.snap_lines_.x.setAttribute('y2', this.floor_plan_.get_grid_pixels() / 2);
+ this.floor_plan_.get_g().appendChild(this.snap_lines_.x);
+ }
+ this.snap_lines_.x.setAttribute('x1', point_x);
+ this.snap_lines_.x.setAttribute('x2', point_x);
+ } else if (this.snap_lines_.x !== undefined) {
+ this.snap_lines_.x.parentNode.removeChild(this.snap_lines_.x);
+ delete this.snap_lines_.x;
+ }
+ }
+
+ /**
+ * If the current x matches one of the room snap y positions, then
+ * add/update the current snap line. Otherwise remove it.
+ */
+ if (this.is_horizontal_() === true) {
+ const point_y = this.room_.get_y() + this.point_1_.y;
+ if (this.room_.get_snap_y().includes(point_y) === true) {
+ if (this.snap_lines_.y === undefined) {
+ this.snap_lines_.y = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ this.snap_lines_.y.style.strokeDasharray = '7, 3';
+ this.snap_lines_.y.style.stroke = beestat.style.color.yellow.base;
+ this.snap_lines_.y.setAttribute('x1', this.floor_plan_.get_grid_pixels() / -2);
+ this.snap_lines_.y.setAttribute('x2', this.floor_plan_.get_grid_pixels() / 2);
+ this.floor_plan_.get_g().appendChild(this.snap_lines_.y);
+ }
+ this.snap_lines_.y.setAttribute('y1', point_y);
+ this.snap_lines_.y.setAttribute('y2', point_y);
+ } else if (this.snap_lines_.y !== undefined) {
+ this.snap_lines_.y.parentNode.removeChild(this.snap_lines_.y);
+ delete this.snap_lines_.y;
+ }
+ }
+};
+
+/**
+ * Clear all existing snap lines.
+ */
+beestat.component.floor_plan_entity.wall.prototype.clear_snap_lines_ = function() {
+ if (this.snap_lines_.x !== undefined) {
+ this.snap_lines_.x.parentNode.removeChild(this.snap_lines_.x);
+ delete this.snap_lines_.x;
+ }
+ if (this.snap_lines_.y !== undefined) {
+ this.snap_lines_.y.parentNode.removeChild(this.snap_lines_.y);
+ delete this.snap_lines_.y;
+ }
+};
+
+/**
+ * Get the midpoint of the wall.
+ *
+ * @return {number} The midpoint of the wall.
+ */
+beestat.component.floor_plan_entity.wall.prototype.get_midpoint_ = function() {
+ return {
+ 'x': (this.point_1_.x + this.point_2_.x) / 2,
+ 'y': (this.point_1_.y + this.point_2_.y) / 2
+ };
+};
+
+/**
+ * Get the midpoint of the wall.
+ *
+ * @return {number} The midpoint of the wall.
+ */
+beestat.component.floor_plan_entity.wall.prototype.get_length_ = function() {
+ return Math.round(Math.hypot(
+ this.point_2_.x - this.point_1_.x,
+ this.point_2_.y - this.point_1_.y
+ ));
+};
+
+/**
+ * Get whether or not this wall is horizontal.
+ *
+ * @return {boolean} Whether or not this wall is horizontal.
+ */
+beestat.component.floor_plan_entity.wall.prototype.is_horizontal_ = function() {
+ return Math.abs(this.point_1_.y - this.point_2_.y) === 0;
+};
+
+/**
+ * Get whether or not this wall is vertical.
+ *
+ * @return {boolean} Whether or not this wall is vertical.
+ */
+beestat.component.floor_plan_entity.wall.prototype.is_vertical_ = function() {
+ return Math.abs(this.point_1_.x - this.point_2_.x) === 0;
+};
+
+/**
+ * Make this wall active or not.
+ *
+ * @param {boolean} active Whether or not the wall is active.
+ *
+ * @return {beestat.component.floor_plan_entity.wall} This.
+ */
+beestat.component.floor_plan_entity.wall.prototype.set_active = function(active) {
+ if (active !== this.active_) {
+ this.active_ = active;
+
+ if (this.active_ === true) {
+ // Inactivate any other active wall.
+ if (this.state_.active_wall_entity !== undefined) {
+ this.state_.active_wall_entity.set_active(false);
+ }
+
+ this.state_.active_wall_entity = this;
+
+ // Deactivate the active point.
+ if (this.state_.active_point !== undefined) {
+ this.state_.active_point_entity.set_active(false);
+ }
+
+ this.dispatchEvent('activate');
+ } else {
+ delete this.state_.active_wall_entity;
+ }
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+ }
+
+ return this;
+};
diff --git a/js/component/header.js b/js/component/header.js
index 41a9a52..cc6d993 100644
--- a/js/component/header.js
+++ b/js/component/header.js
@@ -41,14 +41,15 @@ beestat.component.header.prototype.decorate_ = function(parent) {
'layer': 'compare',
'text': 'Compare',
'icon': 'earth'
+ },
+ {
+ 'layer': 'air_quality',
+ 'text': 'Air Quality',
+ 'icon': 'weather_windy'
}
];
- pages.push({
- 'layer': 'air_quality',
- 'text': 'Air Quality',
- 'icon': 'weather_windy'
- });
+ pages.push();
var gutter = beestat.style.size.gutter;
@@ -197,6 +198,15 @@ beestat.component.header.prototype.decorate_ = function(parent) {
(new beestat.layer.settings()).render();
}));
+ if (beestat.user.has_early_access() === true) {
+ menu.add_menu_item(new beestat.component.menu_item()
+ .set_text('Visualize (Early Access)')
+ .set_icon('floor_plan')
+ .set_callback(function() {
+ (new beestat.layer.visualize()).render();
+ }));
+ }
+
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Log Out')
.set_icon('exit_to_app')
diff --git a/js/component/input.js b/js/component/input.js
index 38657a6..119e6a2 100644
--- a/js/component/input.js
+++ b/js/component/input.js
@@ -2,47 +2,34 @@
* Input parent class.
*/
beestat.component.input = function() {
- this.uuid_ = this.generate_uuid_();
+ this.uuid_ = window.crypto.randomUUID();
+
beestat.component.apply(this, arguments);
};
beestat.extend(beestat.component.input, beestat.component);
/**
- * Decorate
- *
- * @param {rocket.Elements} parent
+ * Focus an input.
*
* @return {beestat.component.input} This.
*/
-beestat.component.input.prototype.decorate_ = function(parent) {};
-
beestat.component.input.prototype.focus = function() {
this.input_.focus();
- this.input_[0].setSelectionRange(0, this.input_.value().length);
- return this;
-};
+ this.input_.setSelectionRange(0, this.input_.value().length);
-beestat.component.input.prototype.disable = function() {
- this.input_[0].disabled = true;
- return this;
-};
-
-beestat.component.input.prototype.enable = function() {
- this.input_[0].disabled = false;
return this;
};
/**
- * Generic setter that sets a key to a value, rerenders if necessary, and
- * returns this.
+ * Enable or disable an input.
*
- * @param {string} key
- * @param {string} value
+ * @param {boolean} enabled Whether or not the input should be enabled.
*
* @return {beestat.component.input} This.
*/
-beestat.component.input.prototype.set_ = function(key, value) {
- this[key + '_'] = value;
+beestat.component.input.prototype.set_enabled = function(enabled) {
+ this.input_.disabled = !enabled;
+
if (this.rendered_ === true) {
this.rerender();
}
@@ -51,13 +38,78 @@ beestat.component.input.prototype.set_ = function(key, value) {
};
/**
- * Generate a UUID to uniquely identify an input.
+ * Generic setter that sets a key to a value, rerenders if necessary.
*
- * @link https://stackoverflow.com/a/2117523
- * @return {string} The UUID;
+ * @param {string} key
+ * @param {string} value
+ *
+ * @return {beestat.component.input} This.
*/
-beestat.component.input.prototype.generate_uuid_ = function() {
- return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
- (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
- );
+beestat.component.input.prototype.set_ = function(key, value) {
+ this[key + '_'] = value;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
+
+/**
+ * Set the requirements for this input to be valid.
+ *
+ * @param {object} requirements
+ *
+ * @return {beestat.component.input.text} This.
+ */
+beestat.component.input.prototype.set_requirements = function(requirements) {
+ this.requirements_ = requirements;
+
+ return this;
+};
+
+/**
+ * Check whether or not this input meets the requirements.
+ *
+ * @return {boolean} Whether or not this input meets the requirements.
+ */
+beestat.component.input.prototype.meets_requirements = function() {
+ if (this.requirements_ !== undefined) {
+ switch (this.requirements_.type) {
+ case 'integer':
+ this.requirements_.regexp = /^-?\d+$/;
+ break;
+ }
+
+ if (
+ this.requirements_.required === true &&
+ this.get_value() === undefined
+ ) {
+ return false;
+ }
+
+ if (
+ this.requirements_.min_value !== undefined &&
+ this.get_value() < this.requirements_.min_value
+ ) {
+ return false;
+ }
+
+ if (
+ this.requirements_.max_value !== undefined &&
+ this.get_value() > this.requirements_.max_value
+ ) {
+ return false;
+ }
+
+ if (
+ this.get_value() !== undefined &&
+ this.requirements_.regexp !== undefined &&
+ this.requirements_.regexp.test(this.get_value()) === false
+ ) {
+ return false;
+ }
+ }
+
+ return true;
};
diff --git a/js/component/input/checkbox.js b/js/component/input/checkbox.js
index 1feea20..8264520 100644
--- a/js/component/input/checkbox.js
+++ b/js/component/input/checkbox.js
@@ -1,8 +1,15 @@
/**
- * Checkbox parent class.
+ * Checkbox input.
*/
beestat.component.input.checkbox = function() {
- this.input_ = $.createElement('input');
+ const self = this;
+
+ this.input_ = document.createElement('input');
+ this.input_.setAttribute('type', 'checkbox');
+
+ this.input_.addEventListener('change', function() {
+ self.dispatchEvent('change');
+ });
beestat.component.input.apply(this, arguments);
};
@@ -14,60 +21,51 @@ beestat.extend(beestat.component.input.checkbox, beestat.component.input);
* @param {rocket.Elements} parent
*/
beestat.component.input.checkbox.prototype.decorate_ = function(parent) {
- var self = this;
+ const self = this;
+
+ const div = document.createElement('div');
+ div.className = 'checkbox';
+
+ this.input_.setAttribute('id', this.uuid_);
- const div = $.createElement('div').addClass('checkbox');
- this.input_
- .setAttribute('id', this.uuid_)
- .setAttribute('type', 'checkbox');
div.appendChild(this.input_);
- const label = $.createElement('label');
+ const label = document.createElement('label');
label.setAttribute('for', this.uuid_);
div.appendChild(label);
- const text_label = $.createElement('span')
- .style({
- 'cursor': 'pointer',
- 'margin-left': (beestat.style.size.gutter / 2)
- })
- .innerText(this.label_)
- .addEventListener('click', function() {
- self.input_[0].click();
- });
- div.appendChild(text_label);
-
- this.input_.addEventListener('change', function() {
- self.dispatchEvent('change');
+ const span = document.createElement('span');
+ span.style.cursor = 'pointer';
+ span.style.marginLeft = (beestat.style.size.gutter / 2) + 'px';
+ span.innerText = this.label_;
+ span.addEventListener('click', function() {
+ self.input_.click();
});
+ div.appendChild(span);
parent.appendChild(div);
};
/**
- * Set the value in the input field. This bypasses the set_ function to avoid
- * rerendering when the input value is set. It's unnecessary and can also
- * cause minor issues if you try to set the value, then do something else with
- * the input immediately after.
+ * Set whether or not this checkbox is selected.
*
- * This will not fire off the change event listener.
- *
- * @param {string} value
+ * @param {boolean} checked
*
* @return {beestat.component.input.checkbox} This.
*/
-beestat.component.input.checkbox.prototype.set_value = function(value) {
- this.input_.checked(value);
+beestat.component.input.checkbox.prototype.set_checked = function(checked) {
+ this.input_.checked = checked;
+
return this;
};
/**
- * Get the value in the input field.
+ * Get whether or not this checkbox is selected.
*
- * @return {string} The value in the input field.
+ * @return {string} Whether or not this checkbox is selected.
*/
-beestat.component.input.checkbox.prototype.get_value = function() {
- return this.input_.checked();
+beestat.component.input.checkbox.prototype.get_checked = function() {
+ return this.input_.checked;
};
/**
@@ -79,5 +77,10 @@ beestat.component.input.checkbox.prototype.get_value = function() {
*/
beestat.component.input.checkbox.prototype.set_label = function(label) {
this.label_ = label;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
return this;
};
diff --git a/js/component/input/radio.js b/js/component/input/radio.js
new file mode 100644
index 0000000..adfeaa8
--- /dev/null
+++ b/js/component/input/radio.js
@@ -0,0 +1,127 @@
+/**
+ * Radio input.
+ */
+beestat.component.input.radio = function() {
+ const self = this;
+
+ this.input_ = document.createElement('input');
+ this.input_.setAttribute('type', 'radio');
+
+ this.input_.addEventListener('change', function() {
+ self.dispatchEvent('change');
+ });
+
+ beestat.component.input.apply(this, arguments);
+};
+beestat.extend(beestat.component.input.radio, beestat.component.input);
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.input.radio.prototype.decorate_ = function(parent) {
+ const self = this;
+
+ const div = document.createElement('div');
+ div.className = 'radio';
+
+ this.input_.setAttribute('id', this.uuid_);
+ this.input_.setAttribute('name', this.name_);
+
+ div.appendChild(this.input_);
+
+ const label = document.createElement('label');
+ label.setAttribute('for', this.uuid_);
+ div.appendChild(label);
+
+ const span = document.createElement('span');
+ span.style.cursor = 'pointer';
+ span.style.marginLeft = (beestat.style.size.gutter / 2) + 'px';
+ span.innerText = this.label_;
+ span.addEventListener('click', function() {
+ self.input_.click();
+ });
+ div.appendChild(span);
+
+ parent.appendChild(div);
+};
+
+/**
+ * Set the value of the radio button that is returned when calling
+ * get_value().
+ *
+ * @param {string} value
+ *
+ * @return {beestat.component.input.radio} This.
+ */
+beestat.component.input.radio.prototype.set_value = function(value) {
+ this.value_ = value;
+
+ return this;
+};
+
+/**
+ * Get the value of the radio button.
+ *
+ * @return {string} The value in the input field.
+ */
+beestat.component.input.radio.prototype.get_value = function() {
+ return this.value_;
+};
+
+/**
+ * Set whether or not this radio is selected.
+ *
+ * @param {boolean} checked
+ *
+ * @return {beestat.component.input.radio} This.
+ */
+beestat.component.input.radio.prototype.set_checked = function(checked) {
+ this.input_.checked = checked;
+
+ return this;
+};
+
+/**
+ * Get whether or not this radio is selected.
+ *
+ * @return {string} Whether or not this radio is selected.
+ */
+beestat.component.input.radio.prototype.get_checked = function() {
+ return this.input_.checked;
+};
+
+/**
+ * Set the radio label.
+ *
+ * @param {string} label
+ *
+ * @return {beestat.component.input.radio} This.
+ */
+beestat.component.input.radio.prototype.set_label = function(label) {
+ this.label_ = label;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
+
+/**
+ * Set the radio name. Required to group radio elements together.
+ *
+ * @param {string} name
+ *
+ * @return {beestat.component.input.radio} This.
+ */
+beestat.component.input.radio.prototype.set_name = function(name) {
+ this.name_ = name;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
diff --git a/js/component/input/select.js b/js/component/input/select.js
new file mode 100644
index 0000000..d89c00b
--- /dev/null
+++ b/js/component/input/select.js
@@ -0,0 +1,155 @@
+/**
+ * Select input.
+ */
+beestat.component.input.select = function() {
+ const self = this;
+
+ this.options_ = [];
+
+ this.input_ = document.createElement('select');
+
+ this.input_.addEventListener('focus', function() {
+ self.dispatchEvent('focus');
+ self.input_.style.background = beestat.style.color.bluegray.dark;
+ });
+
+ this.input_.addEventListener('blur', function() {
+ self.dispatchEvent('blur');
+ self.input_.style.background = beestat.style.color.bluegray.light;
+ });
+
+ this.input_.addEventListener('change', function() {
+ self.dispatchEvent('change');
+ });
+
+ beestat.component.input.apply(this, arguments);
+};
+beestat.extend(beestat.component.input.select, beestat.component.input);
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.input.select.prototype.decorate_ = function(parent) {
+ const self = this;
+
+ this.input_.style.border = 'none';
+ this.input_.style.background = beestat.style.color.bluegray.light;
+ this.input_.style.borderRadius = beestat.style.size.border_radius + 'px';
+ this.input_.style.padding = (beestat.style.size.gutter / 2) + 'px';
+ this.input_.style.color = '#fff';
+ this.input_.style.outline = 'none';
+ this.input_.style.transition = 'background 200ms ease';
+ this.input_.style.marginBottom = beestat.style.size.gutter + 'px';
+ this.input_.style.borderBottom = '2px solid ' + beestat.style.color.lightblue.base;
+
+ // Set input width; interpret string widths literally (ex: 100%)
+ if (this.width_ !== undefined) {
+ if (isNaN(this.width_) === true) {
+ this.input_.style.width = this.width_;
+ } else {
+ this.input_.style.width = this.width_ + 'px';
+ }
+ }
+
+ if (this.label_ !== undefined) {
+ const label_container = document.createElement('div');
+ label_container.innerText = this.label_;
+ label_container.style.fontSize = beestat.style.font_size.normal;
+ parent[0].appendChild(label_container);
+ }
+
+ let group;
+ let option_parent = this.input_;
+ this.options_.forEach(function(option) {
+ if (option.group !== group) {
+ group = option.group;
+ option_parent = document.createElement('optgroup');
+ option_parent.setAttribute('label', group);
+ self.input_.appendChild(option_parent);
+ }
+ const option_element = document.createElement('option');
+ option_element.setAttribute('value', option.value);
+ option_element.innerText = option.label;
+ option_parent.appendChild(option_element);
+ });
+
+ parent[0].appendChild(this.input_);
+};
+
+/**
+ * Add an option.
+ *
+ * @param {string} option
+ *
+ * @return {beestat.component.input.select} This.
+ */
+beestat.component.input.select.prototype.add_option = function(option) {
+ this.options_.push(option);
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
+
+/**
+ * Set the value in the input field. Do not rerender; it's unnecessary.
+ *
+ * @param {string} value
+ *
+ * @return {beestat.component.input.select} This.
+ */
+beestat.component.input.select.prototype.set_value = function(value) {
+ console.log('set select value to ' + value);
+ this.input_.value = value;
+
+ this.dispatchEvent('change');
+
+ return this;
+};
+
+/**
+ * Get the value in the input field.
+ *
+ * @return {string} The value in the input field. Undefined if not set.
+ */
+beestat.component.input.select.prototype.get_value = function() {
+ return this.input_.value;
+};
+
+/**
+ * Set the label of the input field.
+ *
+ * @param {string} label
+ *
+ * @return {beestat.component.input.select} This.
+ */
+beestat.component.input.select.prototype.set_label = function(label) {
+ this.label_ = label;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
+
+/**
+ * Set the width of the input field.
+ *
+ * @param {string} width
+ *
+ * @return {beestat.component.input.select} This.
+ */
+beestat.component.input.select.prototype.set_width = function(width) {
+ this.width_ = width;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
diff --git a/js/component/input/text.js b/js/component/input/text.js
index b44339e..c4b433f 100644
--- a/js/component/input/text.js
+++ b/js/component/input/text.js
@@ -1,29 +1,31 @@
/**
- * Input parent class.
+ * Text input.
*/
beestat.component.input.text = function() {
- var self = this;
+ const self = this;
- this.input_ = $.createElement('input');
+ this.input_ = document.createElement('input');
+ this.input_.setAttribute('type', 'text');
- // Add these up top so they don't get re-added on rerender.
+ // Add event listeners in the constructor, not the render. Avoids duplicating.
this.input_.addEventListener('focus', function() {
- self.input_.style({
- 'background': beestat.style.color.bluegray.dark
- });
+ self.dispatchEvent('focus');
+ self.input_.style.background = beestat.style.color.bluegray.dark;
});
this.input_.addEventListener('blur', function() {
self.dispatchEvent('blur');
- self.input_.style({
- 'background': beestat.style.color.bluegray.light
- });
+ self.input_.style.background = beestat.style.color.bluegray.light;
});
this.input_.addEventListener('change', function() {
self.dispatchEvent('change');
});
+ this.input_.addEventListener('input', function() {
+ self.dispatchEvent('input');
+ });
+
beestat.component.input.apply(this, arguments);
};
beestat.extend(beestat.component.input.text, beestat.component.input);
@@ -34,97 +36,104 @@ beestat.extend(beestat.component.input.text, beestat.component.input);
* @param {rocket.Elements} parent
*/
beestat.component.input.text.prototype.decorate_ = function(parent) {
- this.input_
- .setAttribute('type', 'text')
- .style({
- 'border': 'none',
- 'background': beestat.style.color.bluegray.light,
- 'border-radius': beestat.style.size.border_radius,
- 'padding': (beestat.style.size.gutter / 2),
- 'color': '#fff',
- 'outline': 'none',
- 'transition': 'background 200ms ease'
- });
+ this.input_.style.border = 'none';
+ this.input_.style.background = beestat.style.color.bluegray.light;
+ this.input_.style.borderRadius = beestat.style.size.border_radius + 'px';
+ this.input_.style.padding = (beestat.style.size.gutter / 2) + 'px';
+ this.input_.style.color = '#fff';
+ this.input_.style.outline = 'none';
+ this.input_.style.transition = 'background 200ms ease';
+ this.input_.style.marginBottom = beestat.style.size.gutter + 'px';
+ this.input_.style.borderBottom = '2px solid ' + beestat.style.color.lightblue.base;
- if (this.style_ !== undefined) {
- this.input_.style(this.style_);
+ // Set input width; interpret string widths literally (ex: 100%)
+ if (this.width_ !== undefined) {
+ if (isNaN(this.width_) === true) {
+ this.input_.style.width = this.width_;
+ } else {
+ this.input_.style.width = this.width_ + 'px';
+ }
}
- if (this.attribute_ !== undefined) {
- this.input_.setAttribute(this.attribute_);
+ if (this.label_ !== undefined) {
+ const label_container = document.createElement('div');
+ label_container.innerText = this.label_;
+ label_container.style.fontSize = beestat.style.font_size.normal;
+ parent[0].appendChild(label_container);
}
// If we want an icon just drop one on top of the input and add some padding.
if (this.icon_ !== undefined) {
- var icon_container = $.createElement('div')
- .style({
- 'position': 'absolute',
- 'top': '7px',
- 'left': '6px'
- });
- parent.appendChild(icon_container);
+ const icon_container = document.createElement('div');
- this.input_.style({
- 'padding-left': '28px'
- });
+ icon_container.style.position = 'absolute';
+ icon_container.style.top = (this.label_ !== undefined) ? '25px' : '7px';
+ icon_container.style.left = '6px';
+
+ parent[0].appendChild(icon_container);
+
+ this.input_.style.paddingLeft = '28px';
(new beestat.component.icon(this.icon_).set_size(16)
.set_color('#fff'))
- .render(icon_container);
+ .render($(icon_container));
}
- if (this.value_ !== undefined) {
- this.input_.value(this.value_);
- }
-
- parent.appendChild(this.input_);
+ parent[0].appendChild(this.input_);
};
/**
- * Set the value in the input field. This bypasses the set_ function to avoid
- * rerendering when the input value is set. It's unnecessary and can also
- * cause minor issues if you try to set the value, then do something else with
- * the input immediately after.
+ * Set the value in the input field. Do not rerender; it's unnecessary.
*
* @param {string} value
*
* @return {beestat.component.input.text} This.
*/
beestat.component.input.text.prototype.set_value = function(value) {
- this.value_ = value;
- this.input_.value(value);
+ this.input_.value = value;
+
+ this.dispatchEvent('change');
+
+ return this;
+};
+
+/**
+ * Set the placeholder. Do not rerender; it's unnecessary.
+ *
+ * @param {string} placeholder
+ *
+ * @return {beestat.component.input.text} This.
+ */
+beestat.component.input.text.prototype.set_placeholder = function(placeholder) {
+ this.input_.setAttribute('placeholder', placeholder);
+
return this;
};
/**
* Get the value in the input field.
*
- * @return {string} The value in the input field.
+ * @return {string} The value in the input field. Undefined if not set.
*/
beestat.component.input.text.prototype.get_value = function() {
- return this.input_.value();
+ return this.input_.value.trim() === '' ? undefined : this.input_.value.trim();
};
/**
- * Set the style of the input field. Overrides any default styles.
+ * Set the label of the input field.
*
- * @param {object} style
+ * @param {string} label
*
* @return {beestat.component.input.text} This.
*/
-beestat.component.input.text.prototype.set_style = function(style) {
- return this.set_('style', style);
-};
+beestat.component.input.text.prototype.set_label = function(label) {
+ this.label_ = label;
-/**
- * Set the attributes of the input field. Overrides any default attributes.
- *
- * @param {object} attribute
- *
- * @return {beestat.component.input.text} This.
- */
-beestat.component.input.text.prototype.set_attribute = function(attribute) {
- return this.set_('attribute', attribute);
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
};
/**
@@ -135,5 +144,41 @@ beestat.component.input.text.prototype.set_attribute = function(attribute) {
* @return {beestat.component.input.text} This.
*/
beestat.component.input.text.prototype.set_icon = function(icon) {
- return this.set_('icon', icon);
+ this.icon_ = icon;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
+
+/**
+ * Set the width of the input field.
+ *
+ * @param {string} width
+ *
+ * @return {beestat.component.input.text} This.
+ */
+beestat.component.input.text.prototype.set_width = function(width) {
+ this.width_ = width;
+
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+
+ return this;
+};
+
+/**
+ * Set the max length attribute of the input field.
+ *
+ * @param {number} maxlength
+ *
+ * @return {beestat.component.input.text} This.
+ */
+beestat.component.input.text.prototype.set_maxlength = function(maxlength) {
+ this.input_.setAttribute('maxlength', maxlength);
+
+ return this;
};
diff --git a/js/component/modal/air_quality_detail_custom.js b/js/component/modal/air_quality_detail_custom.js
index 5a8f7f0..18301fc 100644
--- a/js/component/modal/air_quality_detail_custom.js
+++ b/js/component/modal/air_quality_detail_custom.js
@@ -124,14 +124,8 @@ beestat.component.modal.air_quality_detail_custom.prototype.decorate_range_stati
};
air_quality_detail_static_range_begin = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_maxlength(10)
+ .set_width(110)
.set_icon('calendar')
.set_value(this.state_.air_quality_detail_range_static_begin);
@@ -161,14 +155,8 @@ beestat.component.modal.air_quality_detail_custom.prototype.decorate_range_stati
});
air_quality_detail_static_range_end = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_maxlength(10)
+ .set_width(110)
.set_icon('calendar')
.set_value(this.state_.air_quality_detail_range_static_end);
@@ -231,14 +219,8 @@ beestat.component.modal.air_quality_detail_custom.prototype.decorate_range_dynam
var self = this;
var air_quality_detail_range_dynamic = new beestat.component.input.text()
- .set_style({
- 'width': 75,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 1
- })
+ .set_maxlength(2)
+ .set_width(75)
.set_icon('pound')
.set_value(beestat.setting('air_quality_detail_range_dynamic'));
diff --git a/js/component/modal/change_floor_plan.js b/js/component/modal/change_floor_plan.js
new file mode 100644
index 0000000..b668799
--- /dev/null
+++ b/js/component/modal/change_floor_plan.js
@@ -0,0 +1,122 @@
+/**
+ * Change floor plan
+ */
+beestat.component.modal.change_floor_plan = function() {
+ beestat.component.modal.apply(this, arguments);
+};
+beestat.extend(beestat.component.modal.change_floor_plan, beestat.component.modal);
+
+beestat.component.modal.change_floor_plan.prototype.decorate_contents_ = function(parent) {
+ var self = this;
+
+ var container = $.createElement('div')
+ .style({
+ 'display': 'grid',
+ 'grid-template-columns': 'repeat(auto-fill, minmax(200px, 1fr))',
+ 'margin': '0 0 16px -16px'
+ });
+ parent.appendChild(container);
+
+ var sorted_floor_plans = $.values(beestat.cache.floor_plan)
+ .sort(function(a, b) {
+ return a.name > b.name;
+ });
+
+ sorted_floor_plans.forEach(function(floor_plan) {
+ var div = $.createElement('div')
+ .style({
+ 'padding': '16px 0 0 16px'
+ });
+ container.appendChild(div);
+
+ self.decorate_floor_plan_(div, floor_plan.floor_plan_id);
+ });
+};
+
+/**
+ * Decorate the floor plan.
+ *
+ * @param {rocket.Elements} parent
+ * @param {number} floor_plan_id
+ */
+beestat.component.modal.change_floor_plan.prototype.decorate_floor_plan_ = function(parent, floor_plan_id) {
+ const self = this;
+
+ var floor_plan = beestat.cache.floor_plan[floor_plan_id];
+
+ var container_height = 60;
+ var gutter = beestat.style.size.gutter / 2;
+ var floor_plan_height = container_height - (gutter * 2);
+
+ var container = $.createElement('div')
+ .style({
+ 'height': container_height,
+ 'border-radius': container_height,
+ 'padding-right': (beestat.style.size.gutter / 2),
+ 'transition': 'background 200ms ease',
+ 'user-select': 'none'
+ });
+
+ if (floor_plan_id == beestat.cache.floor_plan[beestat.setting('floor_plan_id')].floor_plan_id) {
+ container.style({
+ 'background': '#4b6584',
+ 'color': '#fff'
+ });
+ } else {
+ container.style({
+ 'cursor': 'pointer'
+ });
+
+ container
+ .addEventListener('mouseover', function() {
+ container.style('background', beestat.style.color.gray.base);
+ })
+ .addEventListener('mouseout', function() {
+ container.style('background', '');
+ })
+ .addEventListener('click', function() {
+ container.removeEventListener();
+ self.dispose();
+ beestat.setting('floor_plan_id', floor_plan_id);
+ });
+ }
+
+ parent.appendChild(container);
+
+ var left = $.createElement('div')
+ .style({
+ 'background': beestat.style.color.bluegray.dark,
+ 'font-weight': beestat.style.font_weight.light,
+ 'border-radius': '50%',
+ 'width': floor_plan_height,
+ 'height': floor_plan_height,
+ 'line-height': floor_plan_height,
+ 'color': '#fff',
+ 'font-size': '20px',
+ 'text-align': 'center',
+ 'float': 'left',
+ 'margin': gutter
+ })
+ .innerHTML('');
+ container.appendChild(left);
+
+ var right = $.createElement('div')
+ .style({
+ 'line-height': container_height,
+ 'font-weight': beestat.style.font_weight.bold,
+ 'white-space': 'nowrap',
+ 'overflow': 'hidden',
+ 'text-overflow': 'ellipsis'
+ })
+ .innerHTML(floor_plan.name);
+ container.appendChild(right);
+};
+
+/**
+ * Get title.
+ *
+ * @return {string} Title.
+ */
+beestat.component.modal.change_floor_plan.prototype.get_title_ = function() {
+ return 'Change Floor Plan';
+};
diff --git a/js/component/modal/create_floor_plan.js b/js/component/modal/create_floor_plan.js
new file mode 100644
index 0000000..fb5a82e
--- /dev/null
+++ b/js/component/modal/create_floor_plan.js
@@ -0,0 +1,310 @@
+/**
+ * Create a floor plan.
+ *
+ * @param {integer} thermostat_id
+ */
+beestat.component.modal.create_floor_plan = function(thermostat_id) {
+ this.thermostat_id_ = thermostat_id;
+
+ beestat.component.modal.apply(this, arguments);
+
+ this.state_.error = {};
+};
+beestat.extend(beestat.component.modal.create_floor_plan, beestat.component.modal);
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = function(parent) {
+ const self = this;
+ const thermostat = beestat.cache.thermostat[this.thermostat_id_];
+
+ parent.appendChild($.createElement('p').innerHTML('Describe your home to help create this floor plan. You can change these values later.'));
+
+ // Name
+ (new beestat.component.title('Give your floor plan a name')).render(parent);
+
+ const name_input = new beestat.component.input.text()
+ .set_icon('label')
+ .set_maxlength(255)
+ .set_requirements({
+ 'required': true
+ })
+ .render(parent);
+
+ name_input.addEventListener('change', function() {
+ self.state_.name = name_input.get_value();
+ self.state_.error.name = !name_input.meets_requirements();
+ });
+
+ if (self.state_.name !== undefined) {
+ name_input.set_value(self.state_.name);
+ } else if (self.state_.error.name !== true) {
+ name_input.set_value('Home');
+ }
+
+ // Floor count
+ (new beestat.component.title('How many floors does your home have?')).render(parent);
+
+ const floor_count_input = new beestat.component.input.text()
+ .set_icon('layers')
+ .set_maxlength(1)
+ .set_requirements({
+ 'min_value': 1,
+ 'max_value': 8,
+ 'type': 'integer',
+ 'required': true
+ })
+ .render(parent);
+
+ floor_count_input.addEventListener('change', function() {
+ self.state_.floor_count = floor_count_input.get_value();
+ self.state_.error.floor_count = !floor_count_input.meets_requirements();
+ });
+
+ if (self.state_.floor_count !== undefined) {
+ floor_count_input.set_value(self.state_.floor_count);
+ } else if (
+ thermostat.property.stories !== null &&
+ self.state_.error.floor_count !== true
+ ) {
+ floor_count_input.set_value(thermostat.property.stories);
+ }
+
+ // Basement
+ const basement_checkbox = new beestat.component.input.checkbox()
+ .set_label('One of these floors is a basement')
+ .render(parent);
+
+ basement_checkbox.addEventListener('change', function() {
+ self.state_.basement = basement_checkbox.get_checked();
+ });
+
+ if (self.state_.basement !== undefined) {
+ basement_checkbox.set_value(self.state_.basement);
+ }
+
+ // Ceiling height
+ (new beestat.component.title('How tall are your ceilings (feet)?')).render(parent);
+
+ const height_input = new beestat.component.input.text()
+ .set_icon('arrow_expand_vertical')
+ .set_maxlength(2)
+ .set_requirements({
+ 'min_value': 1,
+ 'max_value': 24,
+ 'type': 'integer',
+ 'required': true
+ })
+ .render(parent);
+
+ height_input.addEventListener('change', function() {
+ self.state_.height = height_input.get_value();
+ self.state_.error.height = !height_input.meets_requirements();
+ });
+
+ if (self.state_.height !== undefined) {
+ height_input.set_value(self.state_.height);
+ } else if (self.state_.error.height !== true) {
+ height_input.set_value(9);
+ }
+
+ // Address
+ (new beestat.component.title('What is the address for this home?')).render(parent);
+ parent.appendChild($.createElement('p').innerHTML('Addresses are pulled directly from your ecobee data.'));
+
+ const radio_group = new beestat.component.radio_group();
+ const addresses = $.values(beestat.cache.address);
+ addresses.forEach(function(address) {
+ if (
+ address.normalized !== null &&
+ address.normalized.metadata !== undefined &&
+ address.normalized.metadata.latitude !== undefined &&
+ address.normalized.metadata.latitude !== null &&
+ address.normalized.metadata.longitude !== undefined &&
+ address.normalized.metadata.longitude !== null
+ ) {
+ const address_parts = [
+ address.normalized.components.primary_number,
+ address.normalized.components.street_predirection,
+ address.normalized.components.street_name,
+ address.normalized.components.street_suffix,
+ address.normalized.components.city_name + ',',
+ address.normalized.components.state_abbreviation,
+ address.normalized.components.zipcode
+ ];
+
+ let radio = new beestat.component.input.radio()
+ .set_label(address_parts.join(' '))
+ .set_value(address.address_id);
+
+ if (addresses.length === 1) {
+ radio.set_checked(true);
+ self.state_.address_id = Number(address.address_id);
+ }
+
+ radio_group.add_radio(radio);
+ }
+ });
+
+ radio_group.add_radio(
+ new beestat.component.input.radio()
+ .set_label('Not Listed')
+ .set_checked(addresses.length === 0)
+ );
+
+ radio_group.addEventListener('change', function() {
+ if (radio_group.get_value() === undefined) {
+ delete self.state_.address_id;
+ } else {
+ self.state_.address_id = Number(radio_group.get_value());
+ }
+ });
+
+ radio_group.render(parent);
+
+ this.decorate_error_(parent);
+};
+
+/**
+ * Get title.
+ *
+ * @return {string} The title.
+ */
+beestat.component.modal.create_floor_plan.prototype.get_title_ = function() {
+ return 'Create Floor Plan';
+};
+
+/**
+ * Get the buttons that go on the bottom of this modal.
+ *
+ * @return {[beestat.component.button]} The buttons.
+ */
+beestat.component.modal.create_floor_plan.prototype.get_buttons_ = function() {
+ const self = this;
+
+ const cancel = new beestat.component.button()
+ .set_background_color('#fff')
+ .set_text_color(beestat.style.color.gray.base)
+ .set_text_hover_color(beestat.style.color.red.base)
+ .set_text('Cancel')
+ .addEventListener('click', function() {
+ self.dispose();
+ });
+
+ const save = new beestat.component.button()
+ .set_background_color(beestat.style.color.green.base)
+ .set_background_hover_color(beestat.style.color.green.light)
+ .set_text_color('#fff')
+ .set_text('Create Floor Plan')
+ .addEventListener('click', function() {
+ // Fail if there are errors.
+ if (
+ self.state_.error.floor_count === true ||
+ self.state_.error.height === true ||
+ self.state_.error.name === true
+ ) {
+ self.rerender();
+ return;
+ }
+
+ const attributes = {
+ 'name': self.state_.name
+ };
+ if (self.state_.address_id !== undefined) {
+ attributes.address_id = self.state_.address_id;
+ }
+ attributes.data = {
+ 'groups': []
+ };
+ let elevation = (self.state_.basement === true) ? (self.state_.height * -12) : 0;
+ let floor = (self.state_.basement === true) ? 0 : 1;
+ const ordinals = [
+ 'First',
+ 'Second',
+ 'Third',
+ 'Fourth',
+ 'Fifth',
+ 'Sixth',
+ 'Seventh',
+ 'Eighth',
+ 'Ninth'
+ ];
+ for (let i = 0; i < self.state_.floor_count; i++) {
+ attributes.data.groups.push({
+ 'icon': floor === 0 ? 'alpha_b' : ('numeric_' + floor),
+ 'name': floor === 0 ? 'Basement' : (ordinals[floor - 1] + ' Floor'),
+ 'elevation': elevation,
+ 'height': self.state_.height * 12,
+ 'rooms': []
+ });
+
+ floor++;
+ elevation += (self.state_.height * 12);
+ }
+
+ self.dispose();
+ new beestat.api()
+ .add_call(
+ 'floor_plan',
+ 'create',
+ {
+ 'attributes': attributes
+ },
+ 'new_floor_plan'
+ )
+ .add_call(
+ 'floor_plan',
+ 'read_id',
+ {},
+ 'floor_plan'
+ )
+ .set_callback(function(response) {
+ beestat.setting('floor_plan_id', response.new_floor_plan.floor_plan_id);
+ beestat.cache.set('floor_plan', response.floor_plan);
+ })
+ .send();
+ });
+
+ return [
+ cancel,
+ save
+ ];
+};
+
+/**
+ * Decorate the error area.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.modal.create_floor_plan.prototype.decorate_error_ = function(parent) {
+ let has_error = false;
+
+ var div = $.createElement('div').style({
+ 'background': beestat.style.color.red.base,
+ 'color': '#fff',
+ 'border-radius': beestat.style.size.border_radius,
+ 'padding': beestat.style.size.gutter
+ });
+
+ if (this.state_.error.name === true) {
+ div.appendChild($.createElement('div').innerText('Name is required.'));
+ has_error = true;
+ }
+
+ if (this.state_.error.floor_count === true) {
+ div.appendChild($.createElement('div').innerText('Number of floors is invalid.'));
+ has_error = true;
+ }
+
+ if (this.state_.error.height === true) {
+ div.appendChild($.createElement('div').innerText('Ceiling height is invalid.'));
+ has_error = true;
+ }
+
+ if (has_error === true) {
+ parent.appendChild(div);
+ }
+};
diff --git a/js/component/modal/delete_floor_plan.js b/js/component/modal/delete_floor_plan.js
new file mode 100644
index 0000000..bb5ebcb
--- /dev/null
+++ b/js/component/modal/delete_floor_plan.js
@@ -0,0 +1,101 @@
+/**
+ * Delete a floor plan.
+ *
+ * @param {number} floor_plan_id
+ */
+beestat.component.modal.delete_floor_plan = function(floor_plan_id) {
+ this.floor_plan_id_ = floor_plan_id;
+
+ beestat.component.modal.apply(this, arguments);
+};
+beestat.extend(beestat.component.modal.delete_floor_plan, beestat.component.modal);
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.modal.delete_floor_plan.prototype.decorate_contents_ = function(parent) {
+ parent.appendChild(
+ $.createElement('p').innerHTML(
+ 'Are you sure you want to delete this floor plan?'
+ )
+ );
+
+ const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
+ floor_plan.data.groups.forEach(function(group) {
+ parent.appendChild(
+ $.createElement('div')
+ .innerHTML(
+ group.name + ': ' + group.rooms.length + ' room' + (group.rooms.length === 1 ? '' : 's')
+ )
+ );
+ });
+};
+
+/**
+ * Get title.
+ *
+ * @return {string} The title.
+ */
+beestat.component.modal.delete_floor_plan.prototype.get_title_ = function() {
+ return 'Delete Floor Plan';
+};
+
+/**
+ * Get the buttons that go on the bottom of this modal.
+ *
+ * @return {[beestat.component.button]} The buttons.
+ */
+beestat.component.modal.delete_floor_plan.prototype.get_buttons_ = function() {
+ const self = this;
+
+ const cancel_button = new beestat.component.button()
+ .set_background_color('#fff')
+ .set_text_color(beestat.style.color.gray.base)
+ .set_text_hover_color(beestat.style.color.red.base)
+ .set_text('Cancel')
+ .addEventListener('click', function() {
+ self.dispose();
+ });
+
+ const delete_button = new beestat.component.button()
+ .set_background_color(beestat.style.color.red.base)
+ .set_background_hover_color(beestat.style.color.red.light)
+ .set_text_color('#fff')
+ .set_text('Delete Floor Plan')
+ .addEventListener('click', function() {
+ self.dispose();
+ new beestat.api()
+ .add_call(
+ 'floor_plan',
+ 'delete',
+ {
+ 'id': self.floor_plan_id_
+ },
+ 'delete_floor_plan'
+ )
+ .add_call(
+ 'floor_plan',
+ 'read_id',
+ {},
+ 'floor_plan'
+ )
+ .set_callback(function(response) {
+ console.log('deleted fp');
+ console.log(response);
+ if (Object.keys(response.floor_plan).length > 0) {
+ beestat.setting('floor_plan_id', Object.values(response.floor_plan)[0].floor_plan_id);
+ } else {
+ beestat.setting('floor_plan_id', null);
+ }
+ beestat.cache.set('floor_plan', response.floor_plan);
+ })
+ .send();
+ });
+
+ return [
+ cancel_button,
+ delete_button
+ ];
+};
diff --git a/js/component/modal/download_data.js b/js/component/modal/download_data.js
index bfcf6ec..5c01c50 100644
--- a/js/component/modal/download_data.js
+++ b/js/component/modal/download_data.js
@@ -37,14 +37,8 @@ beestat.component.modal.download_data.prototype.decorate_range_ = function(paren
(new beestat.component.title('Date Range')).render(parent);
var range_begin = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_width(110)
+ .set_maxlength(10)
.set_icon('calendar')
.set_value(this.state_.range_begin.format('M/D/YYYY'));
@@ -54,14 +48,8 @@ beestat.component.modal.download_data.prototype.decorate_range_ = function(paren
});
var range_end = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_width(110)
+ .set_maxlength(10)
.set_icon('calendar')
.set_value(this.state_.range_end.format('M/D/YYYY'));
diff --git a/js/component/modal/newsletter.js b/js/component/modal/newsletter.js
index 62f7d91..7bad0f7 100644
--- a/js/component/modal/newsletter.js
+++ b/js/component/modal/newsletter.js
@@ -15,15 +15,11 @@ beestat.component.modal.newsletter.prototype.decorate_contents_ = function(paren
parent.appendChild($.createElement('p').innerHTML('Interested in following beestat development? Subscribe to the newsletter for an updates. Emails are sparse; only a few every year.'));
this.email_address_ = new beestat.component.input.text()
- .set_style({
- 'width': '100%',
- 'max-width': '300px',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- });
+ .set_width('50%');
if (this.subscribed_ === true) {
this.email_address_.set_icon('check');
- this.email_address_.disable();
+ this.email_address_.set_enabled(false);
this.email_address_.set_value(this.state_.email_address_);
} else if (this.subscribed_ === false) {
this.email_address_.set_icon('email');
diff --git a/js/component/modal/runtime_sensor_detail_custom.js b/js/component/modal/runtime_sensor_detail_custom.js
index 1cdaeb2..757e4b0 100644
--- a/js/component/modal/runtime_sensor_detail_custom.js
+++ b/js/component/modal/runtime_sensor_detail_custom.js
@@ -124,14 +124,8 @@ beestat.component.modal.runtime_sensor_detail_custom.prototype.decorate_range_st
};
runtime_sensor_detail_static_range_begin = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_width(110)
+ .set_maxlength(10)
.set_icon('calendar')
.set_value(this.state_.runtime_sensor_detail_range_static_begin);
@@ -161,14 +155,8 @@ beestat.component.modal.runtime_sensor_detail_custom.prototype.decorate_range_st
});
runtime_sensor_detail_static_range_end = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_width(110)
+ .set_maxlength(10)
.set_icon('calendar')
.set_value(this.state_.runtime_sensor_detail_range_static_end);
@@ -231,14 +219,8 @@ beestat.component.modal.runtime_sensor_detail_custom.prototype.decorate_range_dy
var self = this;
var runtime_sensor_detail_range_dynamic = new beestat.component.input.text()
- .set_style({
- 'width': 75,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 1
- })
+ .set_width(75)
+ .set_maxlength(1)
.set_icon('pound')
.set_value(beestat.setting('runtime_sensor_detail_range_dynamic'));
diff --git a/js/component/modal/runtime_thermostat_detail_custom.js b/js/component/modal/runtime_thermostat_detail_custom.js
index d3e1d34..8f45457 100644
--- a/js/component/modal/runtime_thermostat_detail_custom.js
+++ b/js/component/modal/runtime_thermostat_detail_custom.js
@@ -124,14 +124,8 @@ beestat.component.modal.runtime_thermostat_detail_custom.prototype.decorate_rang
};
runtime_thermostat_detail_static_range_begin = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_width(110)
+ .set_maxlength(10)
.set_icon('calendar')
.set_value(this.state_.runtime_thermostat_detail_range_static_begin);
@@ -161,14 +155,8 @@ beestat.component.modal.runtime_thermostat_detail_custom.prototype.decorate_rang
});
runtime_thermostat_detail_static_range_end = new beestat.component.input.text()
- .set_style({
- 'width': 110,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_width(110)
+ .set_maxlength(10)
.set_icon('calendar')
.set_value(this.state_.runtime_thermostat_detail_range_static_end);
@@ -231,14 +219,8 @@ beestat.component.modal.runtime_thermostat_detail_custom.prototype.decorate_rang
var self = this;
var runtime_thermostat_detail_range_dynamic = new beestat.component.input.text()
- .set_style({
- 'width': 75,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 2
- })
+ .set_width(75)
+ .set_maxlength(2)
.set_icon('pound')
.set_value(beestat.setting('runtime_thermostat_detail_range_dynamic'));
diff --git a/js/component/modal/runtime_thermostat_summary_custom.js b/js/component/modal/runtime_thermostat_summary_custom.js
index 05e0d33..28cbd29 100644
--- a/js/component/modal/runtime_thermostat_summary_custom.js
+++ b/js/component/modal/runtime_thermostat_summary_custom.js
@@ -13,14 +13,8 @@ beestat.component.modal.runtime_thermostat_summary_custom.prototype.decorate_con
// Time count
var time_count = new beestat.component.input.text()
- .set_style({
- 'width': 75,
- 'text-align': 'center',
- 'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
- })
- .set_attribute({
- 'maxlength': 10
- })
+ .set_width(75)
+ .set_maxlength(10)
.set_icon('pound')
.set_value(beestat.setting('runtime_thermostat_summary_time_count'));
@@ -75,11 +69,11 @@ beestat.component.modal.runtime_thermostat_summary_custom.prototype.decorate_con
.addEventListener('click', function() {
if (key === 'runtime_thermostat_summary_time_period') {
if (value === 'all') {
- time_count.set_value('∞').disable();
+ time_count.set_value('∞').set_enabled(false);
} else if (time_count.get_value() === '∞') {
time_count
.set_value(self.state_.runtime_thermostat_summary_time_count || '1')
- .enable();
+ .set_enabled(true);
time_count.dispatchEvent('blur');
}
}
diff --git a/js/component/radio_group.js b/js/component/radio_group.js
new file mode 100644
index 0000000..29ad93c
--- /dev/null
+++ b/js/component/radio_group.js
@@ -0,0 +1,71 @@
+/**
+ * A group of radio input elements.
+ */
+beestat.component.radio_group = function() {
+ this.radios_ = [];
+ this.name_ = Math.random();
+ beestat.component.apply(this, arguments);
+};
+beestat.extend(beestat.component.radio_group, beestat.component);
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.radio_group.prototype.decorate_ = function(parent) {
+ const self = this;
+
+ const container = $.createElement('div');
+ container.style('margin-bottom', beestat.style.size.gutter);
+
+ this.radios_.forEach(function(radio) {
+ radio.set_name('name', this.name_);
+
+ radio.addEventListener('change', function() {
+ self.value_ = radio.get_value();
+ self.dispatchEvent('change');
+ });
+
+ radio.render(container);
+ });
+
+ parent.appendChild(container);
+};
+
+/**
+ * Add a radio to this group.
+ *
+ * @param {beestat.component.radio} radio The radio to add.
+ */
+beestat.component.radio_group.prototype.add_radio = function(radio) {
+ this.radios_.push(radio);
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+};
+
+/**
+ * Remove this component from the page. Disposes the radios first.
+ */
+beestat.component.radio_group.prototype.dispose = function() {
+ this.radios_.forEach(function(radio) {
+ radio.dispose();
+ });
+ beestat.component.prototype.dispose.apply(this, arguments);
+};
+
+/**
+ * Get the selected radio button's value.
+ *
+ * @return {string} The value.
+ */
+beestat.component.radio_group.prototype.get_value = function() {
+ for (let i = 0; i < this.radios_.length; i++) {
+ if (this.radios_[i].get_checked() === true) {
+ return this.radios_[i].get_value();
+ }
+ }
+
+ return null;
+};
diff --git a/js/component/scene.js b/js/component/scene.js
new file mode 100644
index 0000000..7bde710
--- /dev/null
+++ b/js/component/scene.js
@@ -0,0 +1,692 @@
+/**
+ * Home Scene
+ */
+beestat.component.scene = function(thermostat_id) {
+ this.thermostat_id_ = thermostat_id;
+
+ beestat.component.apply(this, arguments);
+};
+beestat.extend(beestat.component.scene, beestat.component);
+
+beestat.component.scene.sun_light_intensity = 1;
+beestat.component.scene.moon_light_intensity = 0.3;
+
+beestat.component.scene.ambient_light_intensity_base = 0.3;
+beestat.component.scene.ambient_light_intensity_sky = 0.4;
+beestat.component.scene.turbidity = 10;
+beestat.component.scene.rayleigh = 0.5;
+beestat.component.scene.mie_coefficient = 0.001;
+beestat.component.scene.mie_directional_g = 0.95;
+beestat.component.scene.moon_opacity = 0.9;
+
+beestat.component.scene.shadow_map_size = 4096;
+
+/**
+ * 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.scene_.remove(this.group_);
+ this.add_group_();
+ this.add_floor_plan_();
+};
+
+/**
+ * Decorate
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.scene.prototype.decorate_ = function(parent) {
+ const self = this;
+
+ this.debug_ = {
+ 'axes': false,
+ 'moon_light_helper': false,
+ 'sun_light_helper': false,
+ 'grid': false,
+ 'watcher': false
+ };
+
+
+ this.add_scene_(parent);
+ this.add_renderer_(parent);
+ this.add_camera_();
+ this.add_controls_(parent);
+ this.add_sky_();
+ this.add_moon_();
+ this.add_moon_light_();
+ this.add_sun_light_();
+ this.add_ambient_light_();
+ this.add_ground_();
+
+ this.add_group_();
+ this.add_floor_plan_();
+
+ /**
+ * Example of how to do a roof
+ *
+ * const geometry = new THREE.ConeGeometry(1.5, 1, 4, 1, false, Math.PI/4);
+ * const material = new THREE.MeshPhongMaterial({
+ * 'color': new THREE.Color(beestat.style.color.gray.dark)
+ * });
+ * const roof = new THREE.Mesh(
+ * geometry,
+ * material
+ * );
+ * roof.position.set(
+ * 1, 2.5, 1
+ * );
+ * roof.castShadow = true;
+ * roof.receiveShadow = true;
+ *
+ * this.scene_.add(roof);
+ */
+
+ const animate = function() {
+ requestAnimationFrame(animate);
+ self.renderer_.render(self.scene_, self.camera_);
+ };
+ animate();
+};
+
+/**
+ * Add the scene. Everything gets added to the scene.
+ *
+ * @param {rocket.Elements} parent Parent
+ */
+beestat.component.scene.prototype.add_scene_ = function(parent) {
+ 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_);
+ }
+};
+
+/**
+ * Add the renderer.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.scene.prototype.add_renderer_ = function(parent) {
+ this.renderer_ = new THREE.WebGLRenderer({
+ 'antialias': true
+ });
+ this.renderer_.setSize(window.innerWidth / 2.2, window.innerHeight / 2.2);
+ this.renderer_.shadowMap.enabled = true;
+ this.renderer_.shadowMap.autoUpdate = false;
+
+ /*
+ * Added these to make the sky not look like crap.
+ * https://threejs.org/examples/webgl_shaders_sky.html
+ */
+ this.renderer_.toneMapping = THREE.ACESFilmicToneMapping;
+ this.renderer_.toneMappingExposure = 0.5;
+
+ parent[0].appendChild(this.renderer_.domElement);
+};
+
+/**
+ * Add a camera and point it at the scene.
+ */
+beestat.component.scene.prototype.add_camera_ = function() {
+ const field_of_view = 75;
+ const aspect_ratio = window.innerWidth / window.innerHeight;
+ const near_plane = 1;
+ const far_plane = 100000;
+
+ this.camera_ = new THREE.PerspectiveCamera(
+ field_of_view,
+ aspect_ratio,
+ near_plane,
+ far_plane
+ );
+
+ this.camera_.position.x = 400;
+ this.camera_.position.y = 400;
+ this.camera_.position.z = 400;
+};
+
+/**
+ * Add camera controls.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.scene.prototype.add_controls_ = function(parent) {
+ new THREE.OrbitControls(this.camera_, parent[0]); // eslint-disable-line no-new
+};
+
+/**
+ * Add the sky to the scene. This is a shader that draws on the inside of a
+ * box really far away. The size of the sun can be tweaked with the
+ * sunAngularDiameterCos shader parameter in threejs.js.
+ *
+ * The sky material uniforms are configured to make the sky look generally
+ * nice. They are tweaked for the eclipse simulation to darken the sky.
+ */
+beestat.component.scene.prototype.add_sky_ = function() {
+ this.sky_ = new THREE.Sky();
+
+ // Makes the sky box really big.
+ this.sky_.scale.setScalar(4500000);
+
+ this.sky_.material.uniforms.turbidity.value =
+ beestat.component.scene.turbidity;
+
+ this.sky_.material.uniforms.rayleigh.value =
+ beestat.component.scene.rayleigh;
+
+ this.sky_.material.uniforms.mieCoefficient.value =
+ beestat.component.scene.mie_coefficient;
+
+ this.sky_.material.uniforms.mieDirectionalG.value =
+ beestat.component.scene.mie_directional_g;
+
+ this.scene_.add(this.sky_);
+};
+
+/**
+ * Adds a moon sprite to the scene. The scale is set arbitrarily to make it
+ * roughly the size of the sun.
+ */
+beestat.component.scene.prototype.add_moon_ = function() {
+ const map = new THREE.TextureLoader().load('img/moon.png');
+ const material = new THREE.SpriteMaterial({'map': map});
+ const scale = 700;
+
+ this.moon_ = new THREE.Sprite(material);
+ this.moon_.scale.set(scale, scale, scale);
+ // this.scene_.add(this.moon_);
+};
+
+/**
+ * Adds a faint moon light so the moon can cast shadows at night.
+ */
+beestat.component.scene.prototype.add_moon_light_ = function() {
+ this.directional_light_moon_ = new THREE.DirectionalLight(
+ 0xfffbab,
+ 0.2
+ );
+ this.directional_light_moon_.castShadow = true;
+ this.directional_light_moon_.shadow.mapSize.width = beestat.component.scene.shadow_map_size;
+ this.directional_light_moon_.shadow.mapSize.height = beestat.component.scene.shadow_map_size;
+ this.directional_light_moon_.shadow.camera.left = -1000;
+ this.directional_light_moon_.shadow.camera.right = 1000;
+ this.directional_light_moon_.shadow.camera.top = 1000;
+ this.directional_light_moon_.shadow.camera.bottom = -1000;
+ this.directional_light_moon_.shadow.camera.far = 10000;
+ // this.scene_.add(this.directional_light_moon_);
+
+ if (this.debug_.moon_light_helper === true) {
+ this.directional_light_moon_helper_ = new THREE.DirectionalLightHelper(
+ this.directional_light_moon_
+ );
+ this.scene_.add(this.directional_light_moon_helper_);
+
+ this.directional_light_moon_camera_helper_ = new THREE.CameraHelper(
+ this.directional_light_moon_.shadow.camera
+ );
+ this.scene_.add(this.directional_light_moon_camera_helper_);
+ }
+};
+
+/**
+ * Add a strong sun light to the scene.
+ */
+beestat.component.scene.prototype.add_sun_light_ = function() {
+ // Directional light to cast shadows.
+ this.directional_light_sun_ = new THREE.DirectionalLight(
+ 0xffffff,
+ beestat.component.scene.sun_light_intensity
+ );
+ this.directional_light_sun_.castShadow = true;
+ this.directional_light_sun_.shadow.bias = -0.00009;
+ this.directional_light_sun_.shadow.mapSize.width = beestat.component.scene.shadow_map_size;
+ this.directional_light_sun_.shadow.mapSize.height = beestat.component.scene.shadow_map_size;
+ this.directional_light_sun_.shadow.camera.left = -1000;
+ this.directional_light_sun_.shadow.camera.right = 1000;
+ this.directional_light_sun_.shadow.camera.top = 1000;
+ this.directional_light_sun_.shadow.camera.bottom = -1000;
+ this.directional_light_sun_.shadow.camera.far = 10000;
+ this.scene_.add(this.directional_light_sun_);
+
+ if (this.debug_.sun_light_helper === true) {
+ this.directional_light_sun_helper_ = new THREE.DirectionalLightHelper(
+ this.directional_light_sun_
+ );
+ this.scene_.add(this.directional_light_sun_helper_);
+
+ this.directional_light_sun_camera_helper_ = new THREE.CameraHelper(
+ this.directional_light_sun_.shadow.camera
+ );
+ this.scene_.add(this.directional_light_sun_camera_helper_);
+ }
+};
+
+/**
+ * Add ambient lighting so everything is always somewhat visible.
+ */
+beestat.component.scene.prototype.add_ambient_light_ = function() {
+ /**
+ * Base ambient light to keep everything visible (mostly at night). The
+ * intensity of this light does not change.
+ */
+ this.scene_.add(new THREE.AmbientLight(
+ 0xffffff,
+ beestat.component.scene.ambient_light_intensity_base
+ ));
+
+ /**
+ * Ambient light from the sun/moon. Ths intensity of this light changes
+ * based on the time of day.
+ */
+ this.ambient_light_sky_ = new THREE.AmbientLight(
+ 0xffffff,
+ beestat.component.scene.ambient_light_intensity_sky
+ );
+ this.scene_.add(this.ambient_light_sky_);
+};
+
+/**
+ * Set the date and thus the position of the sun/moon.
+ *
+ * @param {Date} date
+ */
+beestat.component.scene.prototype.set_date = function(date) {
+ const thermostat = beestat.cache.thermostat[this.thermostat_id_];
+ const address = beestat.cache.address[thermostat.address_id];
+
+ // After sunset may need to hide the sun light source...or maybe wean it's brightness off or something.
+
+ const sun_object_vector = new THREE.Vector3();
+ const moon_object_vector = new THREE.Vector3();
+
+ const sun_light_vector = new THREE.Vector3();
+ const moon_light_vector = new THREE.Vector3();
+
+ // Get sun and moon positions.
+ const sun_position = SunCalc.getPosition(
+ date,
+ address.normalized.metadata.latitude,
+ address.normalized.metadata.longitude
+ );
+ const moon_position = SunCalc.getMoonPosition(
+ date,
+ address.normalized.metadata.latitude,
+ address.normalized.metadata.longitude
+ );
+ const moon_illumination = SunCalc.getMoonIllumination(date);
+
+ // Set the position of the vectors.
+ sun_object_vector.setFromSphericalCoords(
+ 10000,
+ sun_position.altitude - (Math.PI / 2),
+ sun_position.azimuth
+ );
+ moon_object_vector.setFromSphericalCoords(
+ 10000,
+ moon_position.altitude - (Math.PI / 2),
+ moon_position.azimuth
+ );
+ // Set the position of the vectors.
+ sun_light_vector.setFromSphericalCoords(
+ 5000,
+ sun_position.altitude - (Math.PI / 2),
+ sun_position.azimuth
+ );
+ moon_light_vector.setFromSphericalCoords(
+ 5000,
+ moon_position.altitude - (Math.PI / 2),
+ moon_position.azimuth
+ );
+
+ // TODO This will change based on size distance etc
+ const eclipse_begins_distance = 660;
+ const sun_moon_distance = sun_object_vector.distanceTo(moon_object_vector);
+ const eclipse_percentage = Math.max(
+ 0,
+ (1 - (sun_moon_distance / eclipse_begins_distance))
+ );
+
+ /*
+ * this.ambient_light_sky_.intensity =
+ * beestat.component.scene.ambient_light_intensity_sky * eclipse_multiplier;
+ */
+
+ /*
+ * this.directional_light_sun_.intensity =
+ * this.directional_light_sun_.intensity * eclipse_multiplier;
+ */
+
+ // Set light intensities by altitude and eclipse percentage.
+ this.ambient_light_sky_.intensity = Math.max(
+ 0,
+ beestat.component.scene.ambient_light_intensity_sky * Math.sin(sun_position.altitude) * (1 - eclipse_percentage)
+ );
+
+ this.moon_.material.opacity = 0.2;
+
+ /**
+ * Mess up the sky during an eclipse
+ */
+
+ // Turn down to 0
+ this.sky_.material.uniforms.rayleigh.value =
+ beestat.component.scene.rayleigh * (1 - eclipse_percentage);
+
+ // Turn down to almost 0
+ this.sky_.material.uniforms.mieCoefficient.value =
+ Math.max(
+ 0.00001,
+ beestat.component.scene.mie_coefficient * (1 - eclipse_percentage)
+ );
+
+ // Increase to almost 1
+ this.sky_.material.uniforms.mieDirectionalG.value =
+ Math.max(
+ beestat.component.scene.mie_directional_g,
+ 0.9999 * eclipse_percentage
+ );
+
+ /*
+ * this.renderer_.toneMappingExposure = Math.max(
+ * 0.1, // Minimum exposure
+ * beestat.component.scene.tone_mapping_exposure * eclipse_multiplier
+ * );
+ */
+
+ // Set the brightness of the sun
+ this.directional_light_sun_.intensity =
+ beestat.component.scene.sun_light_intensity * Math.sin(sun_position.altitude) * (1 - eclipse_percentage);
+
+ // Set the brightness of the moon
+ this.directional_light_moon_.intensity =
+ beestat.component.scene.moon_light_intensity * moon_illumination.fraction;
+
+ // TODO size of moon based on distance? Might not be worth it haha.
+
+ /*
+ * this.directional_light_moon_.intensity = 0;
+ * this.directional_light_sun_.intensity = 0;
+ */
+
+ // Update the directional light positions
+ this.directional_light_sun_.position.copy(sun_light_vector);
+ this.directional_light_moon_.position.copy(moon_light_vector);
+
+ // Update the position of the sun and moon in the sky
+ this.sky_.material.uniforms.sunPosition.value.copy(sun_object_vector);
+ this.moon_.position.copy(moon_object_vector);
+ // this.sky2_.material.uniforms.sunPosition.value.copy(moon_object_vector);
+
+ // Update shadows
+ this.renderer_.shadowMap.needsUpdate = true;
+
+ if (this.debug_.moon_light_helper === true) {
+ this.directional_light_moon_helper_.update();
+ this.directional_light_moon_camera_helper_.update();
+ }
+
+ if (this.debug_.sun_light_helper === true) {
+ this.directional_light_sun_helper_.update();
+ this.directional_light_sun_camera_helper_.update();
+ }
+
+
+
+ // Update debug watcher
+ if (this.debug_.watcher === true) {
+ this.debug_info_.date = date;
+ this.update_debug_();
+ }
+};
+
+/**
+ * Add some type of ground for the house to sit on.
+ */
+beestat.component.scene.prototype.add_ground_ = function() {
+ const size = 40000;
+
+ const texture = new THREE.TextureLoader().load('img/grass.jpg');
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(1000, 1000);
+
+ const geometry = new THREE.PlaneGeometry(size, size);
+ const material = new THREE.MeshLambertMaterial({
+ // 'map': texture,
+ 'color': new THREE.Color(beestat.style.color.green.dark),
+ 'side': THREE.DoubleSide
+ });
+ const plane = new THREE.Mesh(geometry, material);
+ plane.rotation.x += Math.PI / 2;
+ plane.receiveShadow = true;
+
+
+
+ this.scene_.add(plane);
+
+ if (this.debug_.grid === true) {
+ const grid_size = 100;
+ const grid_divisions = grid_size;
+ const grid_color_center_line = new THREE.Color(beestat.style.color.lightblue.base);
+ const grid_color_grid = new THREE.Color(beestat.style.color.green.base);
+ const grid_helper = new THREE.GridHelper(
+ grid_size,
+ grid_divisions,
+ grid_color_center_line,
+ grid_color_grid
+ );
+ this.scene_.add(grid_helper);
+ }
+};
+
+
+
+
+
+
+
+/**
+ * Add a background.
+ */
+/*
+ * beestat.component.scene.prototype.add_background_ = function() {
+ * this.scene_.background = new THREE.Color(beestat.style.color.bluegray.dark);
+ * }
+ */
+
+
+
+
+
+/**
+ * Add a room. Room coordinates are absolute.
+ *
+ * @param {object} group The group the room belongs to.
+ * @param {object} room The room to add.
+ */
+beestat.component.scene.prototype.add_room_ = function(group, room) {
+ const color = new THREE.Color(beestat.style.color.blue.base);
+
+ var clipper_offset = new ClipperLib.ClipperOffset();
+
+ clipper_offset.AddPath(room.points, ClipperLib.JoinType.jtMiter, ClipperLib.EndType.etClosedPolygon);
+ var clipper_hole = new ClipperLib.Path();
+ // var offsetted_paths = new ClipperLib.Path();
+ clipper_offset.Execute(clipper_hole, -5);
+
+ room.height = 12 * 1;
+
+ // Create a shape using the points of the room.
+ const shape = new THREE.Shape();
+ const first_point = room.points[0];
+ shape.moveTo(first_point.x, first_point.y);
+ room.points.forEach(function(point) {
+ shape.lineTo(point.x, point.y);
+ });
+
+
+ // FLOOR
+ const floor = shape.clone();
+ const floor_geometry = new THREE.ShapeGeometry(floor);
+ const floor_material = new THREE.MeshLambertMaterial({
+ 'color': color,
+ 'side': THREE.DoubleSide
+ });
+ const floor_mesh = new THREE.Mesh(floor_geometry, floor_material);
+ floor_mesh.position.z = ((room.elevation || group.elevation) + room.height) * -1;
+
+ // Translate the floor_mesh to the room x/y position.
+ floor_mesh.translateX(room.x);
+ floor_mesh.translateY(room.y);
+ floor_mesh.translateZ(room.height - 1);
+
+ // floor.rotation.x += Math.PI/2;
+
+ // Shadows are neat.
+ floor_mesh.castShadow = true;
+ floor_mesh.receiveShadow = true;
+
+ // Add the mesh to the group.
+ this.group_.add(floor_mesh);
+
+
+
+
+
+ // Create a hole
+ const hole = new THREE.Shape();
+ const hole_first_point = clipper_hole[0].shift();
+ hole.moveTo(hole_first_point.x, hole_first_point.y);
+ clipper_hole[0].forEach(function(point) {
+ hole.lineTo(point.x, point.y);
+ });
+
+ // Hole
+ // const hole = shape.clone();
+ shape.holes.push(hole);
+
+/* var paths = [[{x:30,y:30},{x:130,y:30},{x:130,y:130},{x:30,y:130}],
+ [{x:60,y:60},{x:60,y:100},{x:100,y:100},{x:100,y:60}]];
+ var scale = 100;
+ ClipperLib.JS.ScaleUpPaths(paths, scale);
+ // Possibly ClipperLib.Clipper.SimplifyPolygons() here
+ // Possibly ClipperLib.Clipper.CleanPolygons() here
+ var co = new ClipperLib.ClipperOffset(2, 0.25);
+
+ // ClipperLib.EndType = {etOpenSquare: 0, etOpenRound: 1, etOpenButt: 2, etClosedPolygon: 3, etClosedLine : 4 };
+ co.AddPaths(paths, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);
+ var offsetted_paths = new ClipperLib.Paths();
+ co.Execute(offsetted_paths, -10 * scale);*/
+
+
+
+ // Extrude the shape and create the mesh.
+ const extrude_settings = {
+ 'depth': room.height,
+ 'bevelEnabled': false
+ };
+
+ const geometry = new THREE.ExtrudeGeometry(
+ shape,
+ extrude_settings
+ );
+ const material = new THREE.MeshPhongMaterial({
+ // 'color': new THREE.Color(beestat.style.color.red.base)
+ 'color': color
+ });
+ const mesh = new THREE.Mesh(geometry, material);
+ mesh.position.z = ((room.elevation || group.elevation) + room.height) * -1;
+
+ // Translate the mesh to the room x/y position.
+ mesh.translateX(room.x);
+ mesh.translateY(room.y);
+
+ // Shadows are neat.
+ mesh.castShadow = true;
+ mesh.receiveShadow = true;
+
+ // Add the mesh to the group.
+ this.group_.add(mesh);
+};
+
+/**
+ * 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 a group containing all of the extruded geometry.
+ */
+beestat.component.scene.prototype.add_group_ = function() {
+ this.group_ = new THREE.Group();
+ // this.group_.rotation.x = -Math.PI / 2;
+ //
+ this.group_.rotation.x = Math.PI / 2;
+ this.scene_.add(this.group_);
+};
+
+/**
+ * Add the floor plan to the scene.
+ */
+beestat.component.scene.prototype.add_floor_plan_ = function() {
+ const self = this;
+ const floor_plan = beestat.cache.floor_plan[1];
+ floor_plan.data.groups.forEach(function(group) {
+ group.rooms.forEach(function(room) {
+ self.add_room_(group, room);
+ });
+ });
+};
diff --git a/js/js.php b/js/js.php
index 88cd83e..45a36b8 100755
--- a/js/js.php
+++ b/js/js.php
@@ -13,6 +13,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
// Beestat
echo '' . PHP_EOL;
@@ -47,6 +50,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
// Component
echo '' . PHP_EOL;
@@ -70,7 +74,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
@@ -88,6 +94,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
@@ -104,12 +113,21 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
diff --git a/js/layer/air_quality.js b/js/layer/air_quality.js
index 18db509..94990cf 100644
--- a/js/layer/air_quality.js
+++ b/js/layer/air_quality.js
@@ -60,5 +60,13 @@ beestat.layer.air_quality.prototype.decorate_ = function(parent) {
}
]);
+ // Footer
+ cards.push([
+ {
+ 'card': new beestat.component.card.footer(),
+ 'size': 12
+ }
+ ]);
+
(new beestat.component.layout(cards)).render(parent);
};
diff --git a/js/layer/load.js b/js/layer/load.js
index 532befe..bb9e51d 100644
--- a/js/layer/load.js
+++ b/js/layer/load.js
@@ -106,6 +106,13 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
'address'
);
+ api.add_call(
+ 'floor_plan',
+ 'read_id',
+ {},
+ 'floor_plan'
+ );
+
api.add_call(
'announcement',
'read_id',
@@ -135,6 +142,7 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
beestat.cache.set('ecobee_thermostat', response.ecobee_thermostat);
beestat.cache.set('ecobee_sensor', response.ecobee_sensor);
beestat.cache.set('address', response.address);
+ beestat.cache.set('floor_plan', response.floor_plan);
beestat.cache.set('announcement', response.announcement);
beestat.cache.set('runtime_thermostat_summary', response.runtime_thermostat_summary);
diff --git a/js/layer/visualize.js b/js/layer/visualize.js
new file mode 100644
index 0000000..59f982d
--- /dev/null
+++ b/js/layer/visualize.js
@@ -0,0 +1,59 @@
+/**
+ * Visualize layer.
+ */
+beestat.layer.visualize = function() {
+ beestat.layer.apply(this, arguments);
+};
+beestat.extend(beestat.layer.visualize, beestat.layer);
+
+beestat.layer.visualize.prototype.decorate_ = function(parent) {
+ /*
+ * Set the overflow on the body so the scrollbar is always present so
+ * highcharts graphs render properly.
+ */
+ $('body').style({
+ 'overflow-y': 'scroll',
+ 'background': beestat.style.color.bluegray.light,
+ 'padding': '0 ' + beestat.style.size.gutter + 'px'
+ });
+
+ (new beestat.component.header('visualize')).render(parent);
+
+ // All the cards
+ var cards = [];
+
+ if (window.is_demo === true) {
+ cards.push([
+ {
+ 'card': new beestat.component.card.demo(),
+ 'size': 12
+ }
+ ]);
+ }
+
+ cards.push([
+ {
+ 'card': new beestat.component.card.early_access(),
+ 'size': 12
+ }
+ ]);
+
+ cards.push([
+ {
+ 'card': new beestat.component.card.floor_plan_editor(
+ beestat.setting('thermostat_id')
+ ),
+ 'size': 12
+ }
+ ]);
+
+ // Footer
+ cards.push([
+ {
+ 'card': new beestat.component.card.footer(),
+ 'size': 12
+ }
+ ]);
+
+ (new beestat.component.layout(cards)).render(parent);
+};
diff --git a/js/lib/clipper/clipper.js b/js/lib/clipper/clipper.js
new file mode 100644
index 0000000..a0ad3ee
--- /dev/null
+++ b/js/lib/clipper/clipper.js
@@ -0,0 +1,257 @@
+/* eslint-disable */
+
+/*******************************************************************************
+ * *
+ * Author : Angus Johnson *
+ * Version : 6.4.2 *
+ * Date : 27 February 2017 *
+ * Website : http://www.angusj.com *
+ * Copyright : Angus Johnson 2010-2017 *
+ * *
+ * License: *
+ * Use, modification & distribution is subject to Boost Software License Ver 1. *
+ * http://www.boost.org/LICENSE_1_0.txt *
+ * *
+ * Attributions: *
+ * The code in this library is an extension of Bala Vatti's clipping algorithm: *
+ * "A generic solution to polygon clipping" *
+ * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
+ * http://portal.acm.org/citation.cfm?id=129906 *
+ * *
+ * Computer graphics and geometric modeling: implementation and algorithms *
+ * By Max K. Agoston *
+ * Springer; 1 edition (January 4, 2005) *
+ * http://books.google.com/books?q=vatti+clipping+agoston *
+ * *
+ * See also: *
+ * "Polygon Offsetting by Computing Winding Numbers" *
+ * Paper no. DETC2005-85513 pp. 565-575 *
+ * ASME 2005 International Design Engineering Technical Conferences *
+ * and Computers and Information in Engineering Conference (IDETC/CIE2005) *
+ * September 24-28, 2005 , Long Beach, California, USA *
+ * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
+ * *
+ *******************************************************************************/
+/*******************************************************************************
+ * *
+ * Author : Timo *
+ * Version : 6.4.2.2 *
+ * Date : 8 September 2017 *
+ * *
+ * This is a translation of the C# Clipper library to Javascript. *
+ * Int128 struct of C# is implemented using JSBN of Tom Wu. *
+ * Because Javascript lacks support for 64-bit integers, the space *
+ * is a little more restricted than in C# version. *
+ * *
+ * C# version has support for coordinate space: *
+ * +-4611686018427387903 ( sqrt(2^127 -1)/2 ) *
+ * while Javascript version has support for space: *
+ * +-4503599627370495 ( sqrt(2^106 -1)/2 ) *
+ * *
+ * Tom Wu's JSBN proved to be the fastest big integer library: *
+ * http://jsperf.com/big-integer-library-test *
+ * *
+ * This class can be made simpler when (if ever) 64-bit integer support comes *
+ * or floating point Clipper is released. *
+ * *
+ *******************************************************************************/
+/*******************************************************************************
+ * *
+ * Basic JavaScript BN library - subset useful for RSA encryption. *
+ * http://www-cs-students.stanford.edu/~tjw/jsbn/ *
+ * Copyright (c) 2005 Tom Wu *
+ * All Rights Reserved. *
+ * See "LICENSE" for details: *
+ * http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE *
+ * *
+ *******************************************************************************/
+(function(){function k(a,b,c){d.biginteger_used=1;null!=a&&("number"==typeof a&&"undefined"==typeof b?this.fromInt(a):"number"==typeof a?this.fromNumber(a,b,c):null==b&&"string"!=typeof a?this.fromString(a,256):this.fromString(a,b))}function q(){return new k(null,void 0,void 0)}function R(a,b,c,e,d,g){for(;0<=--g;){var f=b*this[a++]+c[e]+d;d=Math.floor(f/67108864);c[e++]=f&67108863}return d}function S(a,b,c,e,d,g){var f=b&32767;for(b>>=15;0<=--g;){var m=this[a]&32767,k=this[a++]>>15,n=b*m+k*f;m=f*
+m+((n&32767)<<15)+c[e]+(d&1073741823);d=(m>>>30)+(n>>>15)+b*k+(d>>>30);c[e++]=m&1073741823}return d}function T(a,b,c,e,d,g){var f=b&16383;for(b>>=14;0<=--g;){var m=this[a]&16383,k=this[a++]>>14,n=b*m+k*f;m=f*m+((n&16383)<<14)+c[e]+d;d=(m>>28)+(n>>14)+b*k;c[e++]=m&268435455}return d}function M(a,b){var c=E[a.charCodeAt(b)];return null==c?-1:c}function y(a){var b=q();b.fromInt(a);return b}function F(a){var b=1,c;0!=(c=a>>>16)&&(a=c,b+=16);0!=(c=a>>8)&&(a=c,b+=8);0!=(c=a>>4)&&(a=c,b+=4);0!=(c=a>>2)&&
+(a=c,b+=2);0!=a>>1&&(b+=1);return b}function z(a){this.m=a}function B(a){this.m=a;this.mp=a.invDigit();this.mpl=this.mp&32767;this.mph=this.mp>>15;this.um=(1<=x;++x)E[v++]=x;v=97;for(x=10;36>
+x;++x)E[v++]=x;v=65;for(x=10;36>x;++x)E[v++]=x;z.prototype.convert=function(a){return 0>a.s||0<=a.compareTo(this.m)?a.mod(this.m):a};z.prototype.revert=function(a){return a};z.prototype.reduce=function(a){a.divRemTo(this.m,null,a)};z.prototype.mulTo=function(a,b,c){a.multiplyTo(b,c);this.reduce(c)};z.prototype.sqrTo=function(a,b){a.squareTo(b);this.reduce(b)};B.prototype.convert=function(a){var b=q();a.abs().dlShiftTo(this.m.t,b);b.divRemTo(this.m,null,b);0>a.s&&0>15)*this.mpl&this.um)<<15)&a.DM;c=b+this.m.t;for(a[c]+=this.m.am(0,e,a,b,0,this.m.t);a[c]>=a.DV;)a[c]-=a.DV,a[++c]++}a.clamp();a.drShiftTo(this.m.t,a);0<=a.compareTo(this.m)&&a.subTo(this.m,a)};B.prototype.mulTo=function(a,b,c){a.multiplyTo(b,c);this.reduce(c)};B.prototype.sqrTo=
+function(a,b){a.squareTo(b);this.reduce(b)};k.prototype.copyTo=function(a){for(var b=this.t-1;0<=b;--b)a[b]=this[b];a.t=this.t;a.s=this.s};k.prototype.fromInt=function(a){this.t=1;this.s=0>a?-1:0;0a?this[0]=a+this.DV:this.t=0};k.prototype.fromString=function(a,b){if(16==b)var c=4;else if(8==b)c=3;else if(256==b)c=8;else if(2==b)c=1;else if(32==b)c=5;else if(4==b)c=2;else{this.fromRadix(a,b);return}this.s=this.t=0;for(var e=a.length,d=!1,g=0;0<=--e;){var h=8==c?a[e]&255:M(a,e);0>h?
+"-"==a.charAt(e)&&(d=!0):(d=!1,0==g?this[this.t++]=h:g+c>this.DB?(this[this.t-1]|=(h&(1<>this.DB-g):this[this.t-1]|=h<=this.DB&&(g-=this.DB))}8==c&&0!=(a[0]&128)&&(this.s=-1,0>e|h,h=(this[m]&d)<=this.t)b.t=0;else{var e=a%this.DB,
+d=this.DB-e,g=(1<>e;for(var h=c+1;h>e;0>=this.DB;if(a.t>=this.DB;e+=this.s}else{for(e+=this.s;c>=this.DB;e-=a.s}b.s=0>e?-1:0;-1>e?b[c++]=this.DV+e:0=b.DV&&(a[c+b.t]-=b.DV,a[c+b.t+1]=1)}0=e.t)){var d=this.abs();if(d.t>this.F2:0),n=this.FV/l;l=(1<h&&k.ZERO.subTo(c,c)}}}};k.prototype.invDigit=function(){if(1>this.t)return 0;var a=this[0];if(0==(a&1))return 0;var b=a&3;b=b*(2-(a&15)*b)&15;b=b*(2-(a&255)*b)&255;b=b*(2-((a&65535)*b&65535))&65535;b=b*(2-a*b%this.DV)%this.DV;return 0a)return k.ONE;var c=q(),e=q(),d=b.convert(this),g=F(a)-1;for(d.copyTo(c);0<=--g;)if(b.sqrTo(c,e),0<(a&1<this.s)return"-"+this.negate().toString(a);if(16==a)a=4;else if(8==a)a=3;else if(2==a)a=1;else if(32==a)a=5;else if(4==a)a=2;else return this.toRadix(a);var b=(1<>h)&&(e=!0,d="0123456789abcdefghijklmnopqrstuvwxyz".charAt(c));0<=g;)h>(h+=this.DB-a)):(c=this[g]>>(h-=a)&b,0>=h&&(h+=this.DB,--g)),0this.s?this.negate():this};k.prototype.compareTo=function(a){var b=
+this.s-a.s;if(0!=b)return b;var c=this.t;b=c-a.t;if(0!=b)return 0>this.s?-b:b;for(;0<=--c;)if(0!=(b=this[c]-a[c]))return b;return 0};k.prototype.bitLength=function(){return 0>=this.t?0:this.DB*(this.t-1)+F(this[this.t-1]^this.s&this.DM)};k.prototype.mod=function(a){var b=q();this.abs().divRemTo(a,null,b);0>this.s&&0a||b.isEven()?new z(b):new B(b);return this.exp(a,c)};k.ZERO=y(0);k.ONE=y(1);D.prototype.convert=
+P;D.prototype.revert=P;D.prototype.mulTo=function(a,b,c){a.multiplyTo(b,c)};D.prototype.sqrTo=function(a,b){a.squareTo(b)};C.prototype.convert=function(a){if(0>a.s||a.t>2*this.m.t)return a.mod(this.m);if(0>a.compareTo(this.m))return a;var b=q();a.copyTo(b);this.reduce(b);return b};C.prototype.revert=function(a){return a};C.prototype.reduce=function(a){a.drShiftTo(this.m.t-1,this.r2);a.t>this.m.t+1&&(a.t=this.m.t+1,a.clamp());this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);for(this.m.multiplyLowerTo(this.q3,
+this.m.t+1,this.r2);0>a.compareTo(this.r2);)a.dAddOffset(1,this.m.t+1);for(a.subTo(this.r2,a);0<=a.compareTo(this.m);)a.subTo(this.m,a)};C.prototype.mulTo=function(a,b,c){a.multiplyTo(b,c);this.reduce(c)};C.prototype.sqrTo=function(a,b){a.squareTo(b);this.reduce(b)};var w=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,
+313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997],W=67108864/w[w.length-1];k.prototype.chunkSize=function(a){return Math.floor(Math.LN2*
+this.DB/Math.log(a))};k.prototype.toRadix=function(a){null==a&&(a=10);if(0==this.signum()||2>a||36l?"-"==a.charAt(m)&&0==this.signum()&&
+(d=!0):(h=b*h+l,++g>=c&&(this.dMultiply(e),this.dAddOffset(h,0),h=g=0))}0a)this.fromInt(1);else for(this.fromNumber(a,c),this.testBit(a-1)||this.bitwiseTo(k.ONE.shiftLeft(a-1),H,this),this.isEven()&&this.dAddOffset(1,0);!this.isProbablePrime(b);)this.dAddOffset(2,0),this.bitLength()>a&&this.subTo(k.ONE.shiftLeft(a-1),this);else{c=[];var e=a&7;c.length=
+(a>>3)+1;b.nextBytes(c);c[0]=0>=this.DB;if(a.t>=this.DB;e+=this.s}else{for(e+=this.s;c>=this.DB;e+=a.s}b.s=0>e?-1:0;0e&&(b[c++]=this.DV+e);b.t=c;b.clamp()};k.prototype.dMultiply=function(a){this[this.t]=this.am(0,a-1,this,0,0,this.t);++this.t;this.clamp()};k.prototype.dAddOffset=function(a,b){if(0!=a){for(;this.t<=b;)this[this.t++]=0;for(this[b]+=a;this[b]>=this.DV;)this[b]-=
+this.DV,++b>=this.t&&(this[this.t++]=0),++this[b]}};k.prototype.multiplyLowerTo=function(a,b,c){var e=Math.min(this.t+a.t,b);c.s=0;for(c.t=e;0=a)return 0;var b=this.DV%a,c=0>this.s?a-1:0;if(0=c)return!1;var e=b.shiftRight(c);a=a+1>>1;a>w.length&&(a=w.length);for(var d=q(),g=0;gthis.s){if(1==this.t)return this[0]-this.DV;if(0==this.t)return-1}else{if(1==this.t)return this[0];if(0==this.t)return 0}return(this[1]&(1<<32-this.DB)-1)<>24};k.prototype.shortValue=function(){return 0==this.t?this.s:this[0]<<
+16>>16};k.prototype.signum=function(){return 0>this.s?-1:0>=this.t||1==this.t&&0>=this[0]?0:1};k.prototype.toByteArray=function(){var a=this.t,b=[];b[0]=this.s;var c=this.DB-a*this.DB%8,e,d=0;if(0>c)!=(this.s&this.DM)>>c&&(b[d++]=e|this.s<c?(e=(this[a]&(1<>(c+=this.DB-8)):(e=this[a]>>(c-=8)&255,0>=c&&(c+=this.DB,--a)),0!=(e&128)&&(e|=-256),0==d&&(this.s&128)!=(e&128)&&++d,0this.compareTo(a)?this:a};k.prototype.max=function(a){return 0a?this.rShiftTo(-a,b):this.lShiftTo(a,b);return b};k.prototype.shiftRight=function(a){var b=q();0>a?this.lShiftTo(-a,b):this.rShiftTo(a,b);return b};k.prototype.getLowestSetBit=function(){for(var a=0;a>=16,c+=16);0==(a&255)&&(a>>=8,c+=8);0==(a&15)&&(a>>=4,c+=4);0==(a&3)&&(a>>=2,c+=2);0==(a&1)&&++c;a=c}return b+
+a}return 0>this.s?this.t*this.DB:-1};k.prototype.bitCount=function(){for(var a=0,b=this.s&this.DM,c=0;c=this.t?0!=this.s:0!=(this[b]&1<=c)return e;var d=18>
+c?1:48>c?3:144>c?4:768>c?5:6;var g=8>c?new z(b):b.isEven()?new C(b):new B(b);var h=[],m=3,k=d-1,n=(1<=k)var u=a[r]>>c-k&n;else u=(a[r]&(1<>this.DB+c-k);for(m=d;0==(u&1);)u>>=1,--m;0>(c-=m)&&(c+=this.DB,--r);if(p)h[u].copyTo(e),p=!1;else{for(;1--c&&(c=this.DB-1,--r)}return g.revert(e)};k.prototype.modInverse=function(a){var b=a.isEven();if(this.isEven()&&b||0==a.signum())return k.ZERO;for(var c=a.clone(),e=this.clone(),d=y(1),g=y(0),h=y(0),m=y(1);0!=c.signum();){for(;c.isEven();)c.rShiftTo(1,c),b?(d.isEven()&&g.isEven()||(d.addTo(this,d),g.subTo(a,g)),d.rShiftTo(1,d)):g.isEven()||g.subTo(a,g),g.rShiftTo(1,g);for(;e.isEven();)e.rShiftTo(1,e),b?(h.isEven()&&m.isEven()||(h.addTo(this,
+h),m.subTo(a,m)),h.rShiftTo(1,h)):m.isEven()||m.subTo(a,m),m.rShiftTo(1,m);0<=c.compareTo(e)?(c.subTo(e,c),b&&d.subTo(h,d),g.subTo(m,g)):(e.subTo(c,e),b&&h.subTo(d,h),m.subTo(g,m))}if(0!=e.compareTo(k.ONE))return k.ZERO;if(0<=m.compareTo(a))return m.subtract(a);if(0>m.signum())m.addTo(a,m);else return m;return 0>m.signum()?m.add(a):m};k.prototype.pow=function(a){return this.exp(a,new D)};k.prototype.gcd=function(a){var b=0>this.s?this.negate():this.clone();a=0>a.s?a.negate():a.clone();if(0>b.compareTo(a)){var c=
+b;b=a;a=c}c=b.getLowestSetBit();var e=a.getLowestSetBit();if(0>e)return b;c
+a.compareTo(b)?!0:!1};k.op_Addition=function(a,b){return(new k(a,void 0,void 0)).add(new k(b,void 0,void 0))};k.op_Subtraction=function(a,b){return(new k(a,void 0,void 0)).subtract(new k(b,void 0,void 0))};k.Int128Mul=function(a,b){return(new k(a,void 0,void 0)).multiply(new k(b,void 0,void 0))};k.op_Division=function(a,b){return a.divide(b)};k.prototype.ToDouble=function(){return parseFloat(this.toString())};v=function(a,b){var c;if("undefined"===typeof Object.getOwnPropertyNames)for(c in b.prototype){if("undefined"===
+typeof a.prototype[c]||a.prototype[c]===Object.prototype[c])a.prototype[c]=b.prototype[c]}else for(var e=Object.getOwnPropertyNames(b.prototype),d=0;da||2147483647a?Math.ceil(a):Math.floor(a):~~a}:J&&"function"===typeof Number.toInteger?function(a){return Number.toInteger(a)}:V||L?function(a){return parseInt(a,10)}:u?function(a){return-2147483648>a||2147483647a?Math.ceil(a):Math.floor(a):a|0}:function(a){return 0>a?Math.ceil(a):
+Math.floor(a)};d.Clear=function(a){a.length=0};d.PI=3.141592653589793;d.PI2=6.283185307179586;d.IntPoint=function(){var a=arguments;var b=a.length;this.y=this.x=0;d.use_xyz?(this.Z=0,3===b?(this.x=a[0],this.y=a[1],this.Z=a[2]):2===b?(this.x=a[0],this.y=a[1],this.Z=0):1===b?a[0]instanceof d.DoublePoint?(a=a[0],this.x=d.Clipper.Round(a.x),this.y=d.Clipper.Round(a.y),this.Z=0):(a=a[0],"undefined"===typeof a.Z&&(a.Z=0),this.x=a.x,this.y=a.y,this.Z=a.Z):this.Z=this.y=this.x=0):2===b?(this.x=a[0],this.y=
+a[1]):1===b?a[0]instanceof d.DoublePoint?(a=a[0],this.x=d.Clipper.Round(a.x),this.y=d.Clipper.Round(a.y)):(a=a[0],this.x=a.x,this.y=a.y):this.y=this.x=0};d.IntPoint.op_Equality=function(a,b){return a.x===b.x&&a.y===b.y};d.IntPoint.op_Inequality=function(a,b){return a.x!==b.x||a.y!==b.y};d.IntPoint0=function(){this.y=this.x=0;d.use_xyz&&(this.Z=0)};d.IntPoint0.prototype=d.IntPoint.prototype;d.IntPoint1=function(a){this.x=a.x;this.y=a.y;d.use_xyz&&(this.Z="undefined"===typeof a.Z?0:a.Z)};d.IntPoint1.prototype=
+d.IntPoint.prototype;d.IntPoint1dp=function(a){this.x=d.Clipper.Round(a.x);this.y=d.Clipper.Round(a.y);d.use_xyz&&(this.Z=0)};d.IntPoint1dp.prototype=d.IntPoint.prototype;d.IntPoint2=function(a,b,c){this.x=a;this.y=b;d.use_xyz&&(this.Z="undefined"===typeof c?0:c)};d.IntPoint2.prototype=d.IntPoint.prototype;d.IntRect=function(){var a=arguments,b=a.length;4===b?(this.left=a[0],this.top=a[1],this.right=a[2],this.bottom=a[3]):1===b?(a=a[0],this.left=a.left,this.top=a.top,this.right=a.right,this.bottom=
+a.bottom):this.bottom=this.right=this.top=this.left=0};d.IntRect0=function(){this.bottom=this.right=this.top=this.left=0};d.IntRect0.prototype=d.IntRect.prototype;d.IntRect1=function(a){this.left=a.left;this.top=a.top;this.right=a.right;this.bottom=a.bottom};d.IntRect1.prototype=d.IntRect.prototype;d.IntRect4=function(a,b,c,e){this.left=a;this.top=b;this.right=c;this.bottom=e};d.IntRect4.prototype=d.IntRect.prototype;d.ClipType={ctIntersection:0,ctUnion:1,ctDifference:2,ctXor:3};d.PolyType={ptSubject:0,
+ptClip:1};d.PolyFillType={pftEvenOdd:0,pftNonZero:1,pftPositive:2,pftNegative:3};d.JoinType={jtSquare:0,jtRound:1,jtMiter:2};d.EndType={etOpenSquare:0,etOpenRound:1,etOpenButt:2,etClosedLine:3,etClosedPolygon:4};d.EdgeSide={esLeft:0,esRight:1};d.Direction={dRightToLeft:0,dLeftToRight:1};d.TEdge=function(){this.Bot=new d.IntPoint0;this.Curr=new d.IntPoint0;this.Top=new d.IntPoint0;this.Delta=new d.IntPoint0;this.Dx=0;this.PolyTyp=d.PolyType.ptSubject;this.Side=d.EdgeSide.esLeft;this.OutIdx=this.WindCnt2=
+this.WindCnt=this.WindDelta=0;this.PrevInSEL=this.NextInSEL=this.PrevInAEL=this.NextInAEL=this.NextInLML=this.Prev=this.Next=null};d.IntersectNode=function(){this.Edge2=this.Edge1=null;this.Pt=new d.IntPoint0};d.MyIntersectNodeSort=function(){};d.MyIntersectNodeSort.Compare=function(a,b){var c=b.Pt.y-a.Pt.y;return 0c?-1:0};d.LocalMinima=function(){this.y=0;this.Next=this.RightBound=this.LeftBound=null};d.Scanbeam=function(){this.y=0;this.Next=null};d.Maxima=function(){this.x=0;this.Prev=this.Next=
+null};d.OutRec=function(){this.Idx=0;this.IsOpen=this.IsHole=!1;this.PolyNode=this.BottomPt=this.Pts=this.FirstLeft=null};d.OutPt=function(){this.Idx=0;this.Pt=new d.IntPoint0;this.Prev=this.Next=null};d.Join=function(){this.OutPt2=this.OutPt1=null;this.OffPt=new d.IntPoint0};d.ClipperBase=function(){this.m_CurrentLM=this.m_MinimaList=null;this.m_edges=[];this.PreserveCollinear=this.m_HasOpenPaths=this.m_UseFullRange=!1;this.m_ActiveEdges=this.m_PolyOuts=this.m_Scanbeam=null};d.ClipperBase.horizontal=
+-9007199254740992;d.ClipperBase.Skip=-2;d.ClipperBase.Unassigned=-1;d.ClipperBase.tolerance=1E-20;d.ClipperBase.loRange=47453132;d.ClipperBase.hiRange=0xfffffffffffff;d.ClipperBase.near_zero=function(a){return a>-d.ClipperBase.tolerance&&ab.x===a.xb.y===a.yb.x===a.xb.y===a.yd.ClipperBase.hiRange||a.y>d.ClipperBase.hiRange||-a.x>d.ClipperBase.hiRange||-a.y>d.ClipperBase.hiRange)&&d.Error("Coordinate outside allowed range in RangeTest().");else if(a.x>d.ClipperBase.loRange||a.y>d.ClipperBase.loRange||-a.x>d.ClipperBase.loRange||-a.y>d.ClipperBase.loRange)b.Value=!0,this.RangeTest(a,
+b)};d.ClipperBase.prototype.InitEdge=function(a,b,c,e){a.Next=b;a.Prev=c;a.Curr.x=e.x;a.Curr.y=e.y;d.use_xyz&&(a.Curr.Z=e.Z);a.OutIdx=-1};d.ClipperBase.prototype.InitEdge2=function(a,b){a.Curr.y>=a.Next.Curr.y?(a.Bot.x=a.Curr.x,a.Bot.y=a.Curr.y,d.use_xyz&&(a.Bot.Z=a.Curr.Z),a.Top.x=a.Next.Curr.x,a.Top.y=a.Next.Curr.y,d.use_xyz&&(a.Top.Z=a.Next.Curr.Z)):(a.Top.x=a.Curr.x,a.Top.y=a.Curr.y,d.use_xyz&&(a.Top.Z=a.Curr.Z),a.Bot.x=a.Next.Curr.x,a.Bot.y=a.Next.Curr.y,d.use_xyz&&(a.Bot.Z=a.Next.Curr.Z));this.SetDx(a);
+a.PolyTyp=b};d.ClipperBase.prototype.FindNextLocMin=function(a){for(var b;;){for(;d.IntPoint.op_Inequality(a.Bot,a.Prev.Bot)||d.IntPoint.op_Equality(a.Curr,a.Top);)a=a.Next;if(a.Dx!==d.ClipperBase.horizontal&&a.Prev.Dx!==d.ClipperBase.horizontal)break;for(;a.Prev.Dx===d.ClipperBase.horizontal;)a=a.Prev;for(b=a;a.Dx===d.ClipperBase.horizontal;)a=a.Next;if(a.Top.y!==a.Prev.Bot.y){b.Prev.Bot.xc.Next.Top.x&&(c=e.Prev)}for(;a!==c;)a.NextInLML=a.Next,a.Dx===d.ClipperBase.horizontal&&a!==f&&a.Bot.x!==a.Prev.Top.x&&this.ReverseHorizontal(a),a=a.Next;a.Dx===d.ClipperBase.horizontal&&
+a!==f&&a.Bot.x!==a.Prev.Top.x&&this.ReverseHorizontal(a);c=c.Next}else{for(;c.Top.y===c.Prev.Bot.y&&c.Prev.OutIdx!==d.ClipperBase.Skip;)c=c.Prev;if(c.Dx===d.ClipperBase.horizontal&&c.Prev.OutIdx!==d.ClipperBase.Skip){for(e=c;e.Next.Dx===d.ClipperBase.horizontal;)e=e.Next;if(e.Next.Top.x===c.Prev.Top.x||e.Next.Top.x>c.Prev.Top.x)c=e.Next}for(;a!==c;)a.NextInLML=a.Prev,a.Dx===d.ClipperBase.horizontal&&a!==f&&a.Bot.x!==a.Next.Top.x&&this.ReverseHorizontal(a),a=a.Prev;a.Dx===d.ClipperBase.horizontal&&
+a!==f&&a.Bot.x!==a.Next.Top.x&&this.ReverseHorizontal(a);c=c.Prev}return c};d.ClipperBase.prototype.AddPath=function(a,b,c){d.use_lines?c||b!==d.PolyType.ptClip||d.Error("AddPath: Open paths must be subject."):c||d.Error("AddPath: Open paths have been disabled.");var e=a.length-1;if(c)for(;0e||!c&&1>e)return!1;for(var f=[],g=0;g<=e;g++)f.push(new d.TEdge);var h=!0;f[1].Curr.x=a[1].x;f[1].Curr.y=a[1].y;
+d.use_xyz&&(f[1].Curr.Z=a[1].Z);var m={Value:this.m_UseFullRange};this.RangeTest(a[0],m);this.m_UseFullRange=m.Value;m.Value=this.m_UseFullRange;this.RangeTest(a[e],m);this.m_UseFullRange=m.Value;this.InitEdge(f[0],f[1],f[e],a[0]);this.InitEdge(f[e],f[0],f[e-1],a[e]);for(g=e-1;1<=g;--g)m.Value=this.m_UseFullRange,this.RangeTest(a[g],m),this.m_UseFullRange=m.Value,this.InitEdge(f[g],f[g+1],f[g-1],a[g]);for(g=a=e=f[0];;)if(a.Curr!==a.Next.Curr||!c&&a.Next===e){if(a.Prev===a.Next)break;else if(c&&d.ClipperBase.SlopesEqual4(a.Prev.Curr,
+a.Curr,a.Next.Curr,this.m_UseFullRange)&&(!this.PreserveCollinear||!this.Pt2IsBetweenPt1AndPt3(a.Prev.Curr,a.Curr,a.Next.Curr))){a===e&&(e=a.Next);a=this.RemoveEdge(a);g=a=a.Prev;continue}a=a.Next;if(a===g||!c&&a.Next===e)break}else{if(a===a.Next)break;a===e&&(e=a.Next);g=a=this.RemoveEdge(a)}if(!c&&a===a.Next||c&&a.Prev===a.Next)return!1;c||(this.m_HasOpenPaths=!0,e.Prev.OutIdx=d.ClipperBase.Skip);a=e;do this.InitEdge2(a,b),a=a.Next,h&&a.Curr.y!==e.Curr.y&&(h=!1);while(a!==e);if(h){if(c)return!1;
+a.Prev.OutIdx=d.ClipperBase.Skip;b=new d.LocalMinima;b.Next=null;b.y=a.Bot.y;b.LeftBound=null;b.RightBound=a;b.RightBound.Side=d.EdgeSide.esRight;for(b.RightBound.WindDelta=0;;){a.Bot.x!==a.Prev.Top.x&&this.ReverseHorizontal(a);if(a.Next.OutIdx===d.ClipperBase.Skip)break;a=a.NextInLML=a.Next}this.InsertLocalMinima(b);this.m_edges.push(f);return!0}this.m_edges.push(f);h=null;d.IntPoint.op_Equality(a.Prev.Bot,a.Prev.Top)&&(a=a.Next);for(;;){a=this.FindNextLocMin(a);if(a===h)break;else null===h&&(h=
+a);b=new d.LocalMinima;b.Next=null;b.y=a.Bot.y;a.Dxa.x===b.xa.y===b.y=this.m_MinimaList.y)a.Next=this.m_MinimaList,this.m_MinimaList=a;else{for(var b=this.m_MinimaList;null!==b.Next&&a.ythis.m_Scanbeam.y){var b=new d.Scanbeam;b.y=a;b.Next=
+this.m_Scanbeam;this.m_Scanbeam=b}else{for(b=this.m_Scanbeam;null!==b.Next&&a<=b.Next.y;)b=b.Next;if(a!==b.y){var c=new d.Scanbeam;c.y=a;c.Next=b.Next;b.Next=c}}};d.ClipperBase.prototype.PopScanbeam=function(a){if(null===this.m_Scanbeam)return a.v=0,!1;a.v=this.m_Scanbeam.y;this.m_Scanbeam=this.m_Scanbeam.Next;return!0};d.ClipperBase.prototype.LocalMinimaPending=function(){return null!==this.m_CurrentLM};d.ClipperBase.prototype.CreateOutRec=function(){var a=new d.OutRec;a.Idx=d.ClipperBase.Unassigned;
+a.IsHole=!1;a.IsOpen=!1;a.FirstLeft=null;a.Pts=null;a.BottomPt=null;a.PolyNode=null;this.m_PolyOuts.push(a);a.Idx=this.m_PolyOuts.length-1;return a};d.ClipperBase.prototype.DisposeOutRec=function(a){this.m_PolyOuts[a].Pts=null;this.m_PolyOuts[a]=null};d.ClipperBase.prototype.UpdateEdgeIntoAEL=function(a){null===a.NextInLML&&d.Error("UpdateEdgeIntoAEL: invalid call");var b=a.PrevInAEL,c=a.NextInAEL;a.NextInLML.OutIdx=a.OutIdx;null!==b?b.NextInAEL=a.NextInLML:this.m_ActiveEdges=a.NextInLML;null!==c&&
+(c.PrevInAEL=a.NextInLML);a.NextInLML.Side=a.Side;a.NextInLML.WindDelta=a.WindDelta;a.NextInLML.WindCnt=a.WindCnt;a.NextInLML.WindCnt2=a.WindCnt2;a=a.NextInLML;a.Curr.x=a.Bot.x;a.Curr.y=a.Bot.y;a.PrevInAEL=b;a.NextInAEL=c;d.ClipperBase.IsHorizontal(a)||this.InsertScanbeam(a.Top.y);return a};d.ClipperBase.prototype.SwapPositionsInAEL=function(a,b){if(a.NextInAEL!==a.PrevInAEL&&b.NextInAEL!==b.PrevInAEL){if(a.NextInAEL===b){var c=b.NextInAEL;null!==c&&(c.PrevInAEL=a);var e=a.PrevInAEL;null!==e&&(e.NextInAEL=
+b);b.PrevInAEL=e;b.NextInAEL=a;a.PrevInAEL=b;a.NextInAEL=c}else b.NextInAEL===a?(c=a.NextInAEL,null!==c&&(c.PrevInAEL=b),e=b.PrevInAEL,null!==e&&(e.NextInAEL=a),a.PrevInAEL=e,a.NextInAEL=b,b.PrevInAEL=a,b.NextInAEL=c):(c=a.NextInAEL,e=a.PrevInAEL,a.NextInAEL=b.NextInAEL,null!==a.NextInAEL&&(a.NextInAEL.PrevInAEL=a),a.PrevInAEL=b.PrevInAEL,null!==a.PrevInAEL&&(a.PrevInAEL.NextInAEL=a),b.NextInAEL=c,null!==b.NextInAEL&&(b.NextInAEL.PrevInAEL=b),b.PrevInAEL=e,null!==b.PrevInAEL&&(b.PrevInAEL.NextInAEL=
+b));null===a.PrevInAEL?this.m_ActiveEdges=a:null===b.PrevInAEL&&(this.m_ActiveEdges=b)}};d.ClipperBase.prototype.DeleteFromAEL=function(a){var b=a.PrevInAEL,c=a.NextInAEL;if(null!==b||null!==c||a===this.m_ActiveEdges)null!==b?b.NextInAEL=c:this.m_ActiveEdges=c,null!==c&&(c.PrevInAEL=b),a.NextInAEL=null,a.PrevInAEL=null};d.Clipper=function(a){"undefined"===typeof a&&(a=0);this.m_PolyOuts=null;this.m_ClipType=d.ClipType.ctIntersection;this.m_IntersectNodeComparer=this.m_IntersectList=this.m_SortedEdges=
+this.m_ActiveEdges=this.m_Maxima=this.m_Scanbeam=null;this.m_ExecuteLocked=!1;this.m_SubjFillType=this.m_ClipFillType=d.PolyFillType.pftEvenOdd;this.m_GhostJoins=this.m_Joins=null;this.StrictlySimple=this.ReverseSolution=this.m_UsingPolyTree=!1;d.ClipperBase.call(this);this.m_SortedEdges=this.m_ActiveEdges=this.m_Maxima=this.m_Scanbeam=null;this.m_IntersectList=[];this.m_IntersectNodeComparer=d.MyIntersectNodeSort.Compare;this.m_UsingPolyTree=this.m_ExecuteLocked=!1;this.m_PolyOuts=[];this.m_Joins=
+[];this.m_GhostJoins=[];this.ReverseSolution=0!==(1&a);this.StrictlySimple=0!==(2&a);this.PreserveCollinear=0!==(4&a);d.use_xyz&&(this.ZFillFunction=null)};d.Clipper.ioReverseSolution=1;d.Clipper.ioStrictlySimple=2;d.Clipper.ioPreserveCollinear=4;d.Clipper.prototype.Clear=function(){0!==this.m_edges.length&&(this.DisposeAllPolyPts(),d.ClipperBase.prototype.Clear.call(this))};d.Clipper.prototype.InsertMaxima=function(a){var b=new d.Maxima;b.x=a;if(null===this.m_Maxima)this.m_Maxima=b,this.m_Maxima.Next=
+null,this.m_Maxima.Prev=null;else if(a=c.Next.x;)c=c.Next;a!==c.x&&(b.Next=c.Next,b.Prev=c,null!==c.Next&&(c.Next.Prev=b),c.Next=b)}};d.Clipper.prototype.Execute=function(){var a;var b=arguments;var c=b.length;var e=b[1]instanceof d.PolyTree;if(4!==c||e){if(4===c&&e){c=b[0];var f=b[1];e=b[2];b=b[3];if(this.m_ExecuteLocked)return!1;this.m_ExecuteLocked=!0;this.m_SubjFillType=e;this.m_ClipFillType=
+b;this.m_ClipType=c;this.m_UsingPolyTree=!0;try{(a=this.ExecuteInternal())&&this.BuildResult2(f)}finally{this.DisposeAllPolyPts(),this.m_ExecuteLocked=!1}return a}if(2===c&&!e||2===c&&e)return c=b[0],f=b[1],this.Execute(c,f,d.PolyFillType.pftEvenOdd,d.PolyFillType.pftEvenOdd)}else{c=b[0];f=b[1];e=b[2];b=b[3];if(this.m_ExecuteLocked)return!1;this.m_HasOpenPaths&&d.Error("Error: PolyTree struct is needed for open path clipping.");this.m_ExecuteLocked=!0;d.Clear(f);this.m_SubjFillType=e;this.m_ClipFillType=
+b;this.m_ClipType=c;this.m_UsingPolyTree=!1;try{(a=this.ExecuteInternal())&&this.BuildResult(f)}finally{this.DisposeAllPolyPts(),this.m_ExecuteLocked=!1}return a}};d.Clipper.prototype.FixHoleLinkage=function(a){if(null!==a.FirstLeft&&(a.IsHole===a.FirstLeft.IsHole||null===a.FirstLeft.Pts)){for(var b=a.FirstLeft;null!==b&&(b.IsHole===a.IsHole||null===b.Pts);)b=b.FirstLeft;a.FirstLeft=b}};d.Clipper.prototype.ExecuteInternal=function(){try{this.Reset();this.m_Maxima=this.m_SortedEdges=null;var a={},
+b={};if(!this.PopScanbeam(a))return!1;for(this.InsertLocalMinimaIntoAEL(a.v);this.PopScanbeam(b)||this.LocalMinimaPending();){this.ProcessHorizontals();this.m_GhostJoins.length=0;if(!this.ProcessIntersections(b.v))return!1;this.ProcessEdgesAtTopOfScanbeam(b.v);a.v=b.v;this.InsertLocalMinimaIntoAEL(a.v)}var c;var e=0;for(c=this.m_PolyOuts.length;ea.Top.y?b.Top.xd.Clipper.TopX(b,a.Top.y):b.Curr.xa.WindCnt2}case d.ClipType.ctUnion:switch(c){case d.PolyFillType.pftEvenOdd:case d.PolyFillType.pftNonZero:return 0===a.WindCnt2;case d.PolyFillType.pftPositive:return 0>=
+a.WindCnt2;default:return 0<=a.WindCnt2}case d.ClipType.ctDifference:if(a.PolyTyp===d.PolyType.ptSubject)switch(c){case d.PolyFillType.pftEvenOdd:case d.PolyFillType.pftNonZero:return 0===a.WindCnt2;case d.PolyFillType.pftPositive:return 0>=a.WindCnt2;default:return 0<=a.WindCnt2}else switch(c){case d.PolyFillType.pftEvenOdd:case d.PolyFillType.pftNonZero:return 0!==a.WindCnt2;case d.PolyFillType.pftPositive:return 0a.WindCnt2}case d.ClipType.ctXor:if(0===a.WindDelta)switch(c){case d.PolyFillType.pftEvenOdd:case d.PolyFillType.pftNonZero:return 0===
+a.WindCnt2;case d.PolyFillType.pftPositive:return 0>=a.WindCnt2;default:return 0<=a.WindCnt2}}return!0};d.Clipper.prototype.SetWindingCount=function(a){for(var b=a.PrevInAEL;null!==b&&(b.PolyTyp!==a.PolyTyp||0===b.WindDelta);)b=b.PrevInAEL;if(null===b)b=a.PolyTyp===d.PolyType.ptSubject?this.m_SubjFillType:this.m_ClipFillType,a.WindCnt=0===a.WindDelta?b===d.PolyFillType.pftNegative?-1:1:a.WindDelta,a.WindCnt2=0,b=this.m_ActiveEdges;else{if(0===a.WindDelta&&this.m_ClipType!==d.ClipType.ctUnion)a.WindCnt=
+1;else if(this.IsEvenOddFillType(a))if(0===a.WindDelta){for(var c=!0,e=b.PrevInAEL;null!==e;)e.PolyTyp===b.PolyTyp&&0!==e.WindDelta&&(c=!c),e=e.PrevInAEL;a.WindCnt=c?0:1}else a.WindCnt=a.WindDelta;else a.WindCnt=0>b.WindCnt*b.WindDelta?1b.WindDelta*a.WindDelta?b.WindCnt:b.WindCnt+a.WindDelta:0===a.WindDelta?1:a.WindDelta:0===a.WindDelta?0>b.WindCnt?b.WindCnt-1:b.WindCnt+1:0>b.WindDelta*a.WindDelta?b.WindCnt:b.WindCnt+a.WindDelta;a.WindCnt2=b.WindCnt2;b=b.NextInAEL}if(this.IsEvenOddAltFillType(a))for(;b!==
+a;)0!==b.WindDelta&&(a.WindCnt2=0===a.WindCnt2?1:0),b=b.NextInAEL;else for(;b!==a;)a.WindCnt2+=b.WindDelta,b=b.NextInAEL};d.Clipper.prototype.AddEdgeToSEL=function(a){null===this.m_SortedEdges?(this.m_SortedEdges=a,a.PrevInSEL=null,a.NextInSEL=null):(a.NextInSEL=this.m_SortedEdges,a.PrevInSEL=null,this.m_SortedEdges=this.m_SortedEdges.PrevInSEL=a)};d.Clipper.prototype.PopEdgeFromSEL=function(a){a.v=this.m_SortedEdges;if(null===a.v)return!1;var b=a.v;this.m_SortedEdges=a.v.NextInSEL;null!==this.m_SortedEdges&&
+(this.m_SortedEdges.PrevInSEL=null);b.NextInSEL=null;b.PrevInSEL=null;return!0};d.Clipper.prototype.CopyAELToSEL=function(){var a=this.m_ActiveEdges;for(this.m_SortedEdges=a;null!==a;)a.PrevInSEL=a.PrevInAEL,a=a.NextInSEL=a.NextInAEL};d.Clipper.prototype.SwapPositionsInSEL=function(a,b){if(null!==a.NextInSEL||null!==a.PrevInSEL)if(null!==b.NextInSEL||null!==b.PrevInSEL){if(a.NextInSEL===b){var c=b.NextInSEL;null!==c&&(c.PrevInSEL=a);var e=a.PrevInSEL;null!==e&&(e.NextInSEL=b);b.PrevInSEL=e;b.NextInSEL=
+a;a.PrevInSEL=b;a.NextInSEL=c}else b.NextInSEL===a?(c=a.NextInSEL,null!==c&&(c.PrevInSEL=b),e=b.PrevInSEL,null!==e&&(e.NextInSEL=a),a.PrevInSEL=e,a.NextInSEL=b,b.PrevInSEL=a,b.NextInSEL=c):(c=a.NextInSEL,e=a.PrevInSEL,a.NextInSEL=b.NextInSEL,null!==a.NextInSEL&&(a.NextInSEL.PrevInSEL=a),a.PrevInSEL=b.PrevInSEL,null!==a.PrevInSEL&&(a.PrevInSEL.NextInSEL=a),b.NextInSEL=c,null!==b.NextInSEL&&(b.NextInSEL.PrevInSEL=b),b.PrevInSEL=e,null!==b.PrevInSEL&&(b.PrevInSEL.NextInSEL=b));null===a.PrevInSEL?this.m_SortedEdges=
+a:null===b.PrevInSEL&&(this.m_SortedEdges=b)}};d.Clipper.prototype.AddLocalMaxPoly=function(a,b,c){this.AddOutPt(a,c);0===b.WindDelta&&this.AddOutPt(b,c);a.OutIdx===b.OutIdx?(a.OutIdx=-1,b.OutIdx=-1):a.OutIdxb.Dx){var e=this.AddOutPt(a,c);b.OutIdx=a.OutIdx;a.Side=d.EdgeSide.esLeft;b.Side=d.EdgeSide.esRight;var f=a;a=f.PrevInAEL===b?b.PrevInAEL:f.PrevInAEL}else e=
+this.AddOutPt(b,c),a.OutIdx=b.OutIdx,a.Side=d.EdgeSide.esRight,b.Side=d.EdgeSide.esLeft,f=b,a=f.PrevInAEL===a?a.PrevInAEL:f.PrevInAEL;if(null!==a&&0<=a.OutIdx&&a.Top.ya.OutIdx){var c=
+this.CreateOutRec();c.IsOpen=0===a.WindDelta;var e=new d.OutPt;c.Pts=e;e.Idx=c.Idx;e.Pt.x=b.x;e.Pt.y=b.y;d.use_xyz&&(e.Pt.Z=b.Z);e.Next=e;e.Prev=e;c.IsOpen||this.SetHoleState(a,c);a.OutIdx=c.Idx}else{c=this.m_PolyOuts[a.OutIdx];var f=c.Pts,g=a.Side===d.EdgeSide.esLeft;if(g&&d.IntPoint.op_Equality(b,f.Pt))return f;if(!g&&d.IntPoint.op_Equality(b,f.Prev.Pt))return f.Prev;e=new d.OutPt;e.Idx=c.Idx;e.Pt.x=b.x;e.Pt.y=b.y;d.use_xyz&&(e.Pt.Z=b.Z);e.Next=f;e.Prev=f.Prev;e.Prev.Next=e;f.Prev=e;g&&(c.Pts=e)}return e};
+d.Clipper.prototype.GetLastOutPt=function(a){var b=this.m_PolyOuts[a.OutIdx];return a.Side===d.EdgeSide.esLeft?b.Pts:b.Pts.Prev};d.Clipper.prototype.SwapPoints=function(a,b){var c=new d.IntPoint1(a.Value);a.Value.x=b.Value.x;a.Value.y=b.Value.y;d.use_xyz&&(a.Value.Z=b.Value.Z);b.Value.x=c.x;b.Value.y=c.y;d.use_xyz&&(b.Value.Z=c.Z)};d.Clipper.prototype.HorzSegmentsOverlap=function(a,b,c,e){if(a>b){var d=a;a=b;b=d}c>e&&(d=c,c=e,e=d);return a=g&&e>=c||f>=g&&f>=c};d.Clipper.prototype.GetBottomPt=function(a){for(var b=null,c=a.Next;c!==a;)c.Pt.y>a.Pt.y?(a=c,b=null):c.Pt.y===a.Pt.y&&c.Pt.x<=a.Pt.x&&
+(c.Pt.xe.Pt.y?a:c.Pt.ye.Pt.x?b:c.Next===c?b:e.Next===e?a:this.FirstIsBottomPt(c,
+e)?a:b};d.Clipper.prototype.OutRec1RightOfOutRec2=function(a,b){do if(a=a.FirstLeft,a===b)return!0;while(null!==a);return!1};d.Clipper.prototype.GetOutRec=function(a){for(a=this.m_PolyOuts[a];a!==this.m_PolyOuts[a.Idx];)a=this.m_PolyOuts[a.Idx];return a};d.Clipper.prototype.AppendPolygon=function(a,b){var c=this.m_PolyOuts[a.OutIdx],e=this.m_PolyOuts[b.OutIdx];var f=this.OutRec1RightOfOutRec2(c,e)?e:this.OutRec1RightOfOutRec2(e,c)?c:this.GetLowermostRec(c,e);var g=c.Pts,h=g.Prev,m=e.Pts,k=m.Prev;
+a.Side===d.EdgeSide.esLeft?b.Side===d.EdgeSide.esLeft?(this.ReversePolyPtLinks(m),m.Next=g,g.Prev=m,h.Next=k,k.Prev=h,c.Pts=k):(k.Next=g,g.Prev=k,m.Prev=h,h.Next=m,c.Pts=m):b.Side===d.EdgeSide.esRight?(this.ReversePolyPtLinks(m),h.Next=k,k.Prev=h,m.Next=g,g.Prev=m):(h.Next=m,m.Prev=h,g.Prev=k,k.Next=g);c.BottomPt=null;f===e&&(e.FirstLeft!==c&&(c.FirstLeft=e.FirstLeft),c.IsHole=e.IsHole);e.Pts=null;e.BottomPt=null;e.FirstLeft=c;f=a.OutIdx;g=b.OutIdx;a.OutIdx=-1;b.OutIdx=-1;for(h=this.m_ActiveEdges;null!==
+h;){if(h.OutIdx===g){h.OutIdx=f;h.Side=a.Side;break}h=h.NextInAEL}e.Idx=c.Idx};d.Clipper.prototype.ReversePolyPtLinks=function(a){if(null!==a){var b=a;do{var c=b.Next;b.Next=b.Prev;b=b.Prev=c}while(b!==a)}};d.Clipper.SwapSides=function(a,b){var c=a.Side;a.Side=b.Side;b.Side=c};d.Clipper.SwapPolyIndexes=function(a,b){var c=a.OutIdx;a.OutIdx=b.OutIdx;b.OutIdx=c};d.Clipper.prototype.IntersectEdges=function(a,b,c){var e=0<=a.OutIdx,f=0<=b.OutIdx;d.use_xyz&&this.SetZ(c,a,b);if(!d.use_lines||0!==a.WindDelta&&
+0!==b.WindDelta){if(a.PolyTyp===b.PolyTyp)if(this.IsEvenOddFillType(a)){var g=a.WindCnt;a.WindCnt=b.WindCnt;b.WindCnt=g}else a.WindCnt=0===a.WindCnt+b.WindDelta?-a.WindCnt:a.WindCnt+b.WindDelta,b.WindCnt=0===b.WindCnt-a.WindDelta?-b.WindCnt:b.WindCnt-a.WindDelta;else this.IsEvenOddFillType(b)?a.WindCnt2=0===a.WindCnt2?1:0:a.WindCnt2+=b.WindDelta,this.IsEvenOddFillType(a)?b.WindCnt2=0===b.WindCnt2?1:0:b.WindCnt2-=a.WindDelta;if(a.PolyTyp===d.PolyType.ptSubject){var h=this.m_SubjFillType;var k=this.m_ClipFillType}else h=
+this.m_ClipFillType,k=this.m_SubjFillType;if(b.PolyTyp===d.PolyType.ptSubject){var l=this.m_SubjFillType;g=this.m_ClipFillType}else l=this.m_ClipFillType,g=this.m_SubjFillType;switch(h){case d.PolyFillType.pftPositive:h=a.WindCnt;break;case d.PolyFillType.pftNegative:h=-a.WindCnt;break;default:h=Math.abs(a.WindCnt)}switch(l){case d.PolyFillType.pftPositive:l=b.WindCnt;break;case d.PolyFillType.pftNegative:l=-b.WindCnt;break;default:l=Math.abs(b.WindCnt)}if(e&&f)0!==h&&1!==h||0!==l&&1!==l||a.PolyTyp!==
+b.PolyTyp&&this.m_ClipType!==d.ClipType.ctXor?this.AddLocalMaxPoly(a,b,c):(this.AddOutPt(a,c),this.AddOutPt(b,c),d.Clipper.SwapSides(a,b),d.Clipper.SwapPolyIndexes(a,b));else if(e){if(0===l||1===l)this.AddOutPt(a,c),d.Clipper.SwapSides(a,b),d.Clipper.SwapPolyIndexes(a,b)}else if(f){if(0===h||1===h)this.AddOutPt(b,c),d.Clipper.SwapSides(a,b),d.Clipper.SwapPolyIndexes(a,b)}else if(!(0!==h&&1!==h||0!==l&&1!==l)){switch(k){case d.PolyFillType.pftPositive:e=a.WindCnt2;break;case d.PolyFillType.pftNegative:e=
+-a.WindCnt2;break;default:e=Math.abs(a.WindCnt2)}switch(g){case d.PolyFillType.pftPositive:f=b.WindCnt2;break;case d.PolyFillType.pftNegative:f=-b.WindCnt2;break;default:f=Math.abs(b.WindCnt2)}if(a.PolyTyp!==b.PolyTyp)this.AddLocalMinPoly(a,b,c);else if(1===h&&1===l)switch(this.m_ClipType){case d.ClipType.ctIntersection:0=e&&0>=f&&this.AddLocalMinPoly(a,b,c);break;case d.ClipType.ctDifference:(a.PolyTyp===d.PolyType.ptClip&&0=e&&0>=f)&&this.AddLocalMinPoly(a,b,c);break;case d.ClipType.ctXor:this.AddLocalMinPoly(a,b,c)}else d.Clipper.SwapSides(a,b)}}else if(0!==a.WindDelta||0!==b.WindDelta)a.PolyTyp===b.PolyTyp&&a.WindDelta!==b.WindDelta&&this.m_ClipType===d.ClipType.ctUnion?0===a.WindDelta?f&&(this.AddOutPt(a,c),e&&(a.OutIdx=-1)):e&&(this.AddOutPt(b,c),f&&(b.OutIdx=-1)):a.PolyTyp!==b.PolyTyp&&(0!==a.WindDelta||1!==Math.abs(b.WindCnt)||this.m_ClipType===d.ClipType.ctUnion&&0!==
+b.WindCnt2?0!==b.WindDelta||1!==Math.abs(a.WindCnt)||this.m_ClipType===d.ClipType.ctUnion&&0!==a.WindCnt2||(this.AddOutPt(b,c),f&&(b.OutIdx=-1)):(this.AddOutPt(a,c),e&&(a.OutIdx=-1)))};d.Clipper.prototype.DeleteFromSEL=function(a){var b=a.PrevInSEL,c=a.NextInSEL;if(null!==b||null!==c||a===this.m_SortedEdges)null!==b?b.NextInSEL=c:this.m_SortedEdges=c,null!==c&&(c.PrevInSEL=b),a.NextInSEL=null,a.PrevInSEL=null};d.Clipper.prototype.ProcessHorizontals=function(){for(var a={};this.PopEdgeFromSEL(a);)this.ProcessHorizontal(a.v)};
+d.Clipper.prototype.GetHorzDirection=function(a,b){a.Bot.x=h.Top.x&&(l=null)}else{for(;null!==l.Next&&l.Next.xp.Curr.x;)0<=a.OutIdx&&!g&&this.AddOutPt(a,new d.IntPoint2(l.x,
+a.Bot.y)),l=l.Prev;if(e===d.Direction.dLeftToRight&&p.Curr.x>c||e===d.Direction.dRightToLeft&&p.Curr.xe.Top.y&&
+d.ClipperBase.SlopesEqual3(a,e,this.m_UseFullRange)?(t=this.AddOutPt(e,a.Bot),this.AddJoin(n,t,a.Top)):null!==t&&t.Curr.x===a.Bot.x&&t.Curr.y===a.Bot.y&&0!==t.WindDelta&&0<=t.OutIdx&&t.Curr.y>t.Top.y&&d.ClipperBase.SlopesEqual3(a,t,this.m_UseFullRange)&&(t=this.AddOutPt(t,a.Bot),this.AddJoin(n,t,a.Top)))):this.UpdateEdgeIntoAEL(a):(0<=a.OutIdx&&this.AddOutPt(a,a.Top),this.DeleteFromAEL(a))};d.Clipper.prototype.GetNextInAEL=function(a,b){return b===d.Direction.dLeftToRight?a.NextInAEL:a.PrevInAEL};
+d.Clipper.prototype.IsMinima=function(a){return null!==a&&a.Prev.NextInLML!==a&&a.Next.NextInLML!==a};d.Clipper.prototype.IsMaxima=function(a,b){return null!==a&&a.Top.y===b&&null===a.NextInLML};d.Clipper.prototype.IsIntermediate=function(a,b){return a.Top.y===b&&null!==a.NextInLML};d.Clipper.prototype.GetMaximaPair=function(a){return d.IntPoint.op_Equality(a.Next.Top,a.Top)&&null===a.Next.NextInLML?a.Next:d.IntPoint.op_Equality(a.Prev.Top,a.Top)&&null===a.Prev.NextInLML?a.Prev:null};d.Clipper.prototype.GetMaximaPairEx=
+function(a){a=this.GetMaximaPair(a);return null===a||a.OutIdx===d.ClipperBase.Skip||a.NextInAEL===a.PrevInAEL&&!d.ClipperBase.IsHorizontal(a)?null:a};d.Clipper.prototype.ProcessIntersections=function(a){if(null===this.m_ActiveEdges)return!0;try{this.BuildIntersectList(a);if(0===this.m_IntersectList.length)return!0;if(1===this.m_IntersectList.length||this.FixupIntersectionOrder())this.ProcessIntersectList();else return!1}catch(b){this.m_SortedEdges=null,this.m_IntersectList.length=0,d.Error("ProcessIntersections error")}this.m_SortedEdges=
+null;return!0};d.Clipper.prototype.BuildIntersectList=function(a){if(null!==this.m_ActiveEdges){var b=this.m_ActiveEdges;for(this.m_SortedEdges=b;null!==b;)b.PrevInSEL=b.PrevInAEL,b.NextInSEL=b.NextInAEL,b.Curr.x=d.Clipper.TopX(b,a),b=b.NextInAEL;for(var c=!0;c&&null!==this.m_SortedEdges;){c=!1;for(b=this.m_SortedEdges;null!==b.NextInSEL;){var e=b.NextInSEL,f=new d.IntPoint0;b.Curr.x>e.Curr.x?(this.IntersectPoint(b,e,f),f.ya?Math.ceil(a-.5):Math.round(a)};J=function(a){return 0>a?Math.ceil(a-.5):Math.floor(a+.5)};K=function(a){return 0>a?-Math.round(Math.abs(a)):Math.round(a)};L=function(a){if(0>a)return a-=.5,-2147483648>a?Math.ceil(a):a|0;a+=.5;return 2147483647b.Top.y)return c.y=a.Top.y,c.x=d.Clipper.TopX(b,a.Top.y),c.xa.Curr.y&&(c.y=a.Curr.y,c.x=Math.abs(a.Dx)>Math.abs(b.Dx)?d.Clipper.TopX(b,c.y):d.Clipper.TopX(a,c.y))}};d.Clipper.prototype.ProcessEdgesAtTopOfScanbeam=function(a){for(var b,c,e=this.m_ActiveEdges;null!==e;){if(c=
+this.IsMaxima(e,a))c=this.GetMaximaPairEx(e),c=null===c||!d.ClipperBase.IsHorizontal(c);if(c)this.StrictlySimple&&this.InsertMaxima(e.Top.x),b=e.PrevInAEL,this.DoMaxima(e),e=null===b?this.m_ActiveEdges:b.NextInAEL;else{this.IsIntermediate(e,a)&&d.ClipperBase.IsHorizontal(e.NextInLML)?(e=this.UpdateEdgeIntoAEL(e),0<=e.OutIdx&&this.AddOutPt(e,e.Bot),this.AddEdgeToSEL(e)):(e.Curr.x=d.Clipper.TopX(e,a),e.Curr.y=a);d.use_xyz&&(e.Curr.Z=e.Top.y===a?e.Top.Z:e.Bot.y===a?e.Bot.Z:0);if(this.StrictlySimple&&
+(b=e.PrevInAEL,0<=e.OutIdx&&0!==e.WindDelta&&null!==b&&0<=b.OutIdx&&b.Curr.x===e.Curr.x&&0!==b.WindDelta)){var f=new d.IntPoint1(e.Curr);d.use_xyz&&this.SetZ(f,b,e);c=this.AddOutPt(b,f);b=this.AddOutPt(e,f);this.AddJoin(c,b,f)}e=e.NextInAEL}}this.ProcessHorizontals();this.m_Maxima=null;for(e=this.m_ActiveEdges;null!==e;)this.IsIntermediate(e,a)&&(c=null,0<=e.OutIdx&&(c=this.AddOutPt(e,e.Top)),e=this.UpdateEdgeIntoAEL(e),b=e.PrevInAEL,f=e.NextInAEL,null!==b&&b.Curr.x===e.Bot.x&&b.Curr.y===e.Bot.y&&
+null!==c&&0<=b.OutIdx&&b.Curr.y===b.Top.y&&d.ClipperBase.SlopesEqual5(e.Curr,e.Top,b.Curr,b.Top,this.m_UseFullRange)&&0!==e.WindDelta&&0!==b.WindDelta?(b=this.AddOutPt(ePrev2,e.Bot),this.AddJoin(c,b,e.Top)):null!==f&&f.Curr.x===e.Bot.x&&f.Curr.y===e.Bot.y&&null!==c&&0<=f.OutIdx&&f.Curr.y===f.Top.y&&d.ClipperBase.SlopesEqual5(e.Curr,e.Top,f.Curr,f.Top,this.m_UseFullRange)&&0!==e.WindDelta&&0!==f.WindDelta&&(b=this.AddOutPt(f,e.Bot),this.AddJoin(c,b,e.Top))),e=e.NextInAEL};d.Clipper.prototype.DoMaxima=
+function(a){var b=this.GetMaximaPairEx(a);if(null===b)0<=a.OutIdx&&this.AddOutPt(a,a.Top),this.DeleteFromAEL(a);else{for(var c=a.NextInAEL;null!==c&&c!==b;)this.IntersectEdges(a,c,a.Top),this.SwapPositionsInAEL(a,c),c=a.NextInAEL;-1===a.OutIdx&&-1===b.OutIdx?(this.DeleteFromAEL(a),this.DeleteFromAEL(b)):0<=a.OutIdx&&0<=b.OutIdx?(0<=a.OutIdx&&this.AddLocalMaxPoly(a,b,a.Top),this.DeleteFromAEL(a),this.DeleteFromAEL(b)):d.use_lines&&0===a.WindDelta?(0<=a.OutIdx&&(this.AddOutPt(a,a.Top),a.OutIdx=d.ClipperBase.Unassigned),
+this.DeleteFromAEL(a),0<=b.OutIdx&&(this.AddOutPt(b,a.Top),b.OutIdx=d.ClipperBase.Unassigned),this.DeleteFromAEL(b)):d.Error("DoMaxima error")}};d.Clipper.ReversePaths=function(a){for(var b=0,c=a.length;bf)){for(var g=Array(f),h=0;hf||!e.IsOpen&&3>f)){this.FixHoleLinkage(e);var g=new d.PolyNode;a.m_AllPolys.push(g);e.PolyNode=g;g.m_polygon.length=f;e=e.Pts.Prev;for(var h=0;hb.Pt.x?d.Direction.dRightToLeft:d.Direction.dLeftToRight;e=c.Pt.x>e.Pt.x?d.Direction.dRightToLeft:d.Direction.dLeftToRight;if(h===e)return!1;if(h===d.Direction.dLeftToRight){for(;a.Next.Pt.x<=f.x&&a.Next.Pt.x>=a.Pt.x&&a.Next.Pt.y===f.y;)a=a.Next;g&&a.Pt.x!==f.x&&(a=a.Next);b=this.DupOutPt(a,!g);d.IntPoint.op_Inequality(b.Pt,f)&&(a=b,a.Pt.x=f.x,a.Pt.y=f.y,d.use_xyz&&(a.Pt.Z=f.Z),b=this.DupOutPt(a,!g))}else{for(;a.Next.Pt.x>=f.x&&a.Next.Pt.x<=a.Pt.x&&a.Next.Pt.y===
+f.y;)a=a.Next;g||a.Pt.x===f.x||(a=a.Next);b=this.DupOutPt(a,g);d.IntPoint.op_Inequality(b.Pt,f)&&(a=b,a.Pt.x=f.x,a.Pt.y=f.y,d.use_xyz&&(a.Pt.Z=f.Z),b=this.DupOutPt(a,g))}if(e===d.Direction.dLeftToRight){for(;c.Next.Pt.x<=f.x&&c.Next.Pt.x>=c.Pt.x&&c.Next.Pt.y===f.y;)c=c.Next;g&&c.Pt.x!==f.x&&(c=c.Next);e=this.DupOutPt(c,!g);d.IntPoint.op_Inequality(e.Pt,f)&&(c=e,c.Pt.x=f.x,c.Pt.y=f.y,d.use_xyz&&(c.Pt.Z=f.Z),e=this.DupOutPt(c,!g))}else{for(;c.Next.Pt.x>=f.x&&c.Next.Pt.x<=c.Pt.x&&c.Next.Pt.y===f.y;)c=
+c.Next;g||c.Pt.x===f.x||(c=c.Next);e=this.DupOutPt(c,g);d.IntPoint.op_Inequality(e.Pt,f)&&(c=e,c.Pt.x=f.x,c.Pt.y=f.y,d.use_xyz&&(c.Pt.Z=f.Z),e=this.DupOutPt(c,g))}h===d.Direction.dLeftToRight===g?(a.Prev=c,c.Next=a,b.Next=e,e.Prev=b):(a.Next=c,c.Prev=a,b.Prev=e,e.Next=b);return!0};d.Clipper.prototype.JoinPoints=function(a,b,c){var e=a.OutPt1,f;new d.OutPt;var g=a.OutPt2,h;new d.OutPt;if((h=a.OutPt1.Pt.y===a.OffPt.y)&&d.IntPoint.op_Equality(a.OffPt,a.OutPt1.Pt)&&d.IntPoint.op_Equality(a.OffPt,a.OutPt2.Pt)){if(b!==
+c)return!1;for(f=a.OutPt1.Next;f!==e&&d.IntPoint.op_Equality(f.Pt,a.OffPt);)f=f.Next;f=f.Pt.y>a.OffPt.y;for(h=a.OutPt2.Next;h!==g&&d.IntPoint.op_Equality(h.Pt,a.OffPt);)h=h.Next;if(f===h.Pt.y>a.OffPt.y)return!1;f?(f=this.DupOutPt(e,!1),h=this.DupOutPt(g,!0),e.Prev=g,g.Next=e,f.Next=h,h.Prev=f):(f=this.DupOutPt(e,!0),h=this.DupOutPt(g,!1),e.Next=g,g.Prev=e,f.Prev=h,h.Next=f);a.OutPt1=e;a.OutPt2=f;return!0}if(h){for(f=e;e.Prev.Pt.y===e.Pt.y&&e.Prev!==f&&e.Prev!==g;)e=e.Prev;for(;f.Next.Pt.y===f.Pt.y&&
+f.Next!==e&&f.Next!==g;)f=f.Next;if(f.Next===e||f.Next===g)return!1;for(h=g;g.Prev.Pt.y===g.Pt.y&&g.Prev!==h&&g.Prev!==f;)g=g.Prev;for(;h.Next.Pt.y===h.Pt.y&&h.Next!==g&&h.Next!==e;)h=h.Next;if(h.Next===g||h.Next===e)return!1;c={Left:null,Right:null};if(!this.GetOverlap(e.Pt.x,f.Pt.x,g.Pt.x,h.Pt.x,c))return!1;b=c.Left;var k=c.Right;c=new d.IntPoint0;e.Pt.x>=b&&e.Pt.x<=k?(c.x=e.Pt.x,c.y=e.Pt.y,d.use_xyz&&(c.Z=e.Pt.Z),b=e.Pt.x>f.Pt.x):g.Pt.x>=b&&g.Pt.x<=k?(c.x=g.Pt.x,c.y=g.Pt.y,d.use_xyz&&(c.Z=g.Pt.Z),
+b=g.Pt.x>h.Pt.x):f.Pt.x>=b&&f.Pt.x<=k?(c.x=f.Pt.x,c.y=f.Pt.y,d.use_xyz&&(c.Z=f.Pt.Z),b=f.Pt.x>e.Pt.x):(c.x=h.Pt.x,c.y=h.Pt.y,d.use_xyz&&(c.Z=h.Pt.Z),b=h.Pt.x>g.Pt.x);a.OutPt1=e;a.OutPt2=g;return this.JoinHorz(e,f,g,h,c,b)}for(f=e.Next;d.IntPoint.op_Equality(f.Pt,e.Pt)&&f!==e;)f=f.Next;if(k=f.Pt.y>e.Pt.y||!d.ClipperBase.SlopesEqual4(e.Pt,f.Pt,a.OffPt,this.m_UseFullRange)){for(f=e.Prev;d.IntPoint.op_Equality(f.Pt,e.Pt)&&f!==e;)f=f.Prev;if(f.Pt.y>e.Pt.y||!d.ClipperBase.SlopesEqual4(e.Pt,f.Pt,a.OffPt,
+this.m_UseFullRange))return!1}for(h=g.Next;d.IntPoint.op_Equality(h.Pt,g.Pt)&&h!==g;)h=h.Next;var l=h.Pt.y>g.Pt.y||!d.ClipperBase.SlopesEqual4(g.Pt,h.Pt,a.OffPt,this.m_UseFullRange);if(l){for(h=g.Prev;d.IntPoint.op_Equality(h.Pt,g.Pt)&&h!==g;)h=h.Prev;if(h.Pt.y>g.Pt.y||!d.ClipperBase.SlopesEqual4(g.Pt,h.Pt,a.OffPt,this.m_UseFullRange))return!1}if(f===e||h===g||f===h||b===c&&k===l)return!1;k?(f=this.DupOutPt(e,!1),h=this.DupOutPt(g,!0),e.Prev=g,g.Next=e,f.Next=h,h.Prev=f):(f=this.DupOutPt(e,!0),h=
+this.DupOutPt(g,!1),e.Next=g,g.Prev=e,f.Prev=h,h.Next=f);a.OutPt1=e;a.OutPt2=f;return!0};d.Clipper.GetBounds=function(a){for(var b=0,c=a.length;be.right&&(e.right=a[b][f].x),a[b][f].ye.bottom&&(e.bottom=a[b][f].y);return e};d.Clipper.prototype.GetBounds2=
+function(a){var b=a,c=new d.IntRect;c.left=a.Pt.x;c.right=a.Pt.x;c.top=a.Pt.y;c.bottom=a.Pt.y;for(a=a.Next;a!==b;)a.Pt.xc.right&&(c.right=a.Pt.x),a.Pt.yc.bottom&&(c.bottom=a.Pt.y),a=a.Next;return c};d.Clipper.PointInPolygon=function(a,b){var c=0,e=b.length;if(3>e)return 0;for(var d=b[0],g=1;g<=e;++g){var h=g===e?b[0]:b[g];if(h.y===a.y&&(h.x===a.x||d.y===a.y&&h.x>a.x===d.x=a.x)if(h.x>a.x)c=1-c;
+else{var k=(d.x-a.x)*(h.y-a.y)-(h.x-a.x)*(d.y-a.y);if(0===k)return-1;0d.y&&(c=1-c)}else if(h.x>a.x){k=(d.x-a.x)*(h.y-a.y)-(h.x-a.x)*(d.y-a.y);if(0===k)return-1;0d.y&&(c=1-c)}d=h}return c};d.Clipper.prototype.PointInPolygon=function(a,b){var c=0,d=b,f=a.x,g=a.y;var h=b.Pt.x;var k=b.Pt.y;do{b=b.Next;var l=b.Pt.x,n=b.Pt.y;if(n===g&&(l===f||k===g&&l>f===h=f)if(l>f)c=1-c;else{h=(h-f)*(n-g)-(l-f)*(k-g);if(0===h)return-1;0k&&(c=1-c)}else if(l>f){h=
+(h-f)*(n-g)-(l-f)*(k-g);if(0===h)return-1;0k&&(c=1-c)}h=l;k=n}while(d!==b);return c};d.Clipper.prototype.Poly2ContainsPoly1=function(a,b){var c=a;do{var d=this.PointInPolygon(c.Pt,b);if(0<=d)return 0b)return 0;for(var c=0,d=0,f=b-1;dMath.abs(a.y-b.y)?a.x>b.x===a.xa.x===b.xb.y===a.ya.y===b.yc&&(c=0);var g=Array(c);for(f=0;fe)){var f=new d.PolyNode;f.m_jointype=b;f.m_endtype=c;if(c===d.EndType.etClosedLine||c===d.EndType.etClosedPolygon)for(;0f.m_polygon[b].y||a[h].y===f.m_polygon[b].y&&a[h].xg)&&(this.m_polyNodes.AddChild(f),c===d.EndType.etClosedPolygon))if(0>this.m_lowest.x)this.m_lowest=new d.IntPoint2(this.m_polyNodes.ChildCount()-1,b);else if(a=this.m_polyNodes.Childs()[this.m_lowest.x].m_polygon[this.m_lowest.y],f.m_polygon[b].y>
+a.y||f.m_polygon[b].y===a.y&&f.m_polygon[b].x=this.ArcTolerance?d.ClipperOffset.def_arc_tolerance:this.ArcTolerance>Math.abs(a)*d.ClipperOffset.def_arc_tolerance?Math.abs(a)*d.ClipperOffset.def_arc_tolerance:
+this.ArcTolerance)/Math.abs(a));this.m_sin=Math.sin(d.ClipperOffset.two_pi/f);this.m_cos=Math.cos(d.ClipperOffset.two_pi/f);this.m_StepsPerRad=f/d.ClipperOffset.two_pi;0>a&&(this.m_sin=-this.m_sin);for(c=0;c=a&&(3>g||e.m_endtype!==d.EndType.etClosedPolygon))){this.m_destPoly=[];if(1===g)if(e.m_jointype===d.JoinType.jtRound)for(g=1,e=0,b=1;b<=f;b++){this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[0].x+
+g*a),d.ClipperOffset.Round(this.m_srcPoly[0].y+e*a)));var h=g;g=g*this.m_cos-this.m_sin*e;e=h*this.m_sin+e*this.m_cos}else for(e=g=-1,b=0;4>b;++b)this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[0].x+g*a),d.ClipperOffset.Round(this.m_srcPoly[0].y+e*a))),0>g?g=1:0>e?e=1:g=-1;else{for(b=this.m_normals.length=0;bMath.abs(this.m_sinA*this.m_delta)){if(0this.m_sinA&&(this.m_sinA=-1);if(0>
+this.m_sinA*this.m_delta)this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[a].x+this.m_normals[b].x*this.m_delta),d.ClipperOffset.Round(this.m_srcPoly[a].y+this.m_normals[b].y*this.m_delta))),this.m_destPoly.push(new d.IntPoint1(this.m_srcPoly[a])),this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[a].x+this.m_normals[a].x*this.m_delta),d.ClipperOffset.Round(this.m_srcPoly[a].y+this.m_normals[a].y*this.m_delta)));else switch(c){case d.JoinType.jtMiter:c=
+1+(this.m_normals[a].x*this.m_normals[b].x+this.m_normals[a].y*this.m_normals[b].y);c>=this.m_miterLim?this.DoMiter(a,b,c):this.DoSquare(a,b);break;case d.JoinType.jtSquare:this.DoSquare(a,b);break;case d.JoinType.jtRound:this.DoRound(a,b)}return a};d.ClipperOffset.prototype.DoSquare=function(a,b){var c=Math.tan(Math.atan2(this.m_sinA,this.m_normals[b].x*this.m_normals[a].x+this.m_normals[b].y*this.m_normals[a].y)/4);this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[a].x+this.m_delta*
+(this.m_normals[b].x-this.m_normals[b].y*c)),d.ClipperOffset.Round(this.m_srcPoly[a].y+this.m_delta*(this.m_normals[b].y+this.m_normals[b].x*c))));this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[a].x+this.m_delta*(this.m_normals[a].x+this.m_normals[a].y*c)),d.ClipperOffset.Round(this.m_srcPoly[a].y+this.m_delta*(this.m_normals[a].y-this.m_normals[a].x*c))))};d.ClipperOffset.prototype.DoMiter=function(a,b,c){c=this.m_delta/c;this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[a].x+
+(this.m_normals[b].x+this.m_normals[a].x)*c),d.ClipperOffset.Round(this.m_srcPoly[a].y+(this.m_normals[b].y+this.m_normals[a].y)*c)))};d.ClipperOffset.prototype.DoRound=function(a,b){for(var c=Math.max(d.Cast_Int32(d.ClipperOffset.Round(this.m_StepsPerRad*Math.abs(Math.atan2(this.m_sinA,this.m_normals[b].x*this.m_normals[a].x+this.m_normals[b].y*this.m_normals[a].y)))),1),e=this.m_normals[b].x,f=this.m_normals[b].y,g,h=0;hb)return a;c||(a=[a]);for(var e=a.length,f,g,h,k,l,n,r,p=[],t=0;tf)h=g,p.push(h);else{h=g;k=b*b;l=g[0];for(r=n=1;rb)return d.JS.Clone(a);var c=a[0]instanceof Array;c||(a=
+[a]);var e,f,g,h=a.length,k=b*b,l=[];for(e=0;eg;g++){var p=[];r=n.length;if(n[r-1].x!==n[0].x||n[r-1].y!==n[0].y){var t=1;n.push({X:n[0].x,Y:n[0].y});r=n.length}else t=0;var u=[];for(f=0;fg)return 0;b&&(a[g]=a[0],g++);for(;--g;){var h=a[g];var k=h.x;h=h.y;var l=a[g-1];var n=l.x;l=l.y;f+=d((k-n)*(k-n)+(h-l)*(h-l))}b&&a.pop();return f/
+c};d.JS.PerimeterOfPaths=function(a,b,c){c||(c=1);for(var e=0,f=0;f>>0,s=0;sDe(e)?(r=e+1,a=o-De(e)):(r=e,a=o),{year:r,dayOfYear:a}}function Ie(e,t,n){var s,i,r=Ve(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ae(i=e.year()-1,t,n):a>Ae(e.year(),t,n)?(s=a-Ae(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ae(e,t,n){var s=Ve(e,t,n),i=Ve(e+1,t,n);return(De(e)-s+i)/7}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),H("week","w"),H("isoWeek","W"),L("week",5),L("isoWeek",5),ue("w",B),ue("ww",B,z),ue("W",B),ue("WW",B,z),fe(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=k(e)});I("d",0,"do","day"),I("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),I("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),I("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),H("day","d"),H("weekday","e"),H("isoWeekday","E"),L("day",11),L("weekday",11),L("isoWeekday",11),ue("d",B),ue("e",B),ue("E",B),ue("dd",function(e,t){return t.weekdaysMinRegex(e)}),ue("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ue("dddd",function(e,t){return t.weekdaysRegex(e)}),fe(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:g(n).invalidWeekday=e}),fe(["d","e","E"],function(e,t,n,s){t[s]=k(e)});var je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var Ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var ze="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var $e=ae;var qe=ae;var Je=ae;function Be(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=y([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);for(a.sort(e),o.sort(e),u.sort(e),l.sort(e),t=0;t<7;t++)o[t]=de(o[t]),u[t]=de(u[t]),l[t]=de(l[t]);this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Qe(){return this.hours()%12||12}function Xe(e,t){I(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function Ke(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Qe),I("k",["kk",2],0,function(){return this.hours()||24}),I("hmm",0,0,function(){return""+Qe.apply(this)+U(this.minutes(),2)}),I("hmmss",0,0,function(){return""+Qe.apply(this)+U(this.minutes(),2)+U(this.seconds(),2)}),I("Hmm",0,0,function(){return""+this.hours()+U(this.minutes(),2)}),I("Hmmss",0,0,function(){return""+this.hours()+U(this.minutes(),2)+U(this.seconds(),2)}),Xe("a",!0),Xe("A",!1),H("hour","h"),L("hour",13),ue("a",Ke),ue("A",Ke),ue("H",B),ue("h",B),ue("k",B),ue("HH",B,z),ue("hh",B,z),ue("kk",B,z),ue("hmm",Q),ue("hmmss",X),ue("Hmm",Q),ue("Hmmss",X),ce(["H","HH"],ge),ce(["k","kk"],function(e,t,n){var s=k(e);t[ge]=24===s?0:s}),ce(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ce(["h","hh"],function(e,t,n){t[ge]=k(e),g(n).bigHour=!0}),ce("hmm",function(e,t,n){var s=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s)),g(n).bigHour=!0}),ce("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s,2)),t[ve]=k(e.substr(i)),g(n).bigHour=!0}),ce("Hmm",function(e,t,n){var s=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s))}),ce("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=k(e.substr(0,s)),t[pe]=k(e.substr(s,2)),t[ve]=k(e.substr(i))});var et,tt=Te("Hours",!0),nt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:He,monthsShort:Re,week:{dow:0,doy:6},weekdays:je,weekdaysMin:ze,weekdaysShort:Ze,meridiemParse:/[ap]\.?m?\.?/i},st={},it={};function rt(e){return e?e.toLowerCase().replace("_","-"):e}function at(e){var t=null;if(!st[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=et._abbr,require("./locale/"+e),ot(t)}catch(e){}return st[e]}function ot(e,t){var n;return e&&((n=l(t)?lt(e):ut(e,t))?et=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),et._abbr}function ut(e,t){if(null!==t){var n,s=nt;if(t.abbr=e,null!=st[e])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=st[e]._config;else if(null!=t.parentLocale)if(null!=st[t.parentLocale])s=st[t.parentLocale]._config;else{if(null==(n=at(t.parentLocale)))return it[t.parentLocale]||(it[t.parentLocale]=[]),it[t.parentLocale].push({name:e,config:t}),null;s=n._config}return st[e]=new P(b(s,t)),it[e]&&it[e].forEach(function(e){ut(e.name,e.config)}),ot(e),st[e]}return delete st[e],null}function lt(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return et;if(!o(e)){if(t=at(e))return t;e=[e]}return function(e){for(var t,n,s,i,r=0;r=t&&a(i,n,!0)>=t-1)break;t--}r++}return et}(e)}function dt(e){var t,n=e._a;return n&&-2===g(e).overflow&&(t=n[_e]<0||11Pe(n[me],n[_e])?ye:n[ge]<0||24Ae(n,r,a)?g(e)._overflowWeeks=!0:null!=u?g(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[me]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=ht(e._a[me],s[me]),(e._dayOfYear>De(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),n=Ge(r,0,e._dayOfYear),e._a[_e]=n.getUTCMonth(),e._a[ye]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=s[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ge]&&0===e._a[pe]&&0===e._a[ve]&&0===e._a[we]&&(e._nextDay=!0,e._a[ge]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o=new Date(e,t,n,s,i,r,a);return e<100&&0<=e&&isFinite(o.getFullYear())&&o.setFullYear(e),o}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ge]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}var ft=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,mt=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/Z|[+-]\d\d(?::?\d\d)?/,yt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],gt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((\-?\d+)/i;function vt(e){var t,n,s,i,r,a,o=e._i,u=ft.exec(o)||mt.exec(o);if(u){for(g(e).iso=!0,t=0,n=yt.length;tn.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},ln.isLocal=function(){return!!this.isValid()&&!this._isUTC},ln.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},ln.isUtc=Vt,ln.isUTC=Vt,ln.zoneAbbr=function(){return this._isUTC?"UTC":""},ln.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},ln.dates=n("dates accessor is deprecated. Use date instead.",nn),ln.months=n("months accessor is deprecated. Use month instead",Fe),ln.years=n("years accessor is deprecated. Use year instead",Oe),ln.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),ln.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e={};if(w(e,this),(e=Yt(e))._a){var t=e._isUTC?y(e._a):Tt(e._a);this._isDSTShifted=this.isValid()&&0=t.length&&(t=void 0),{value:t&&t[r++],done:!t}}}}function f(t,n){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var e,i,o=r.call(t),u=[];try{for(;(void 0===n||n-- >0)&&!(e=o.next()).done;)u.push(e.value)}catch(t){i={error:t}}finally{try{e&&!e.done&&(r=o.return)&&r.call(o)}finally{if(i)throw i.error}}return u}function h(){for(var t=[],n=0;n ".length,u=void 0;n&&e++<5&&!("html"===(u=j(n))||e>1&&i+r.length*o+u.length>=80);)r.push(u),i+=u.length,n=n.parentNode;return r.reverse().join(" > ")}catch(t){return""}}function j(t){var n,r,e,i,o,u=t,s=[];if(!u||!u.tagName)return"";if(s.push(u.tagName.toLowerCase()),u.id&&s.push("#"+u.id),(n=u.className)&&p(n))for(r=n.split(/\s+/),o=0;o=200&&n<300?t.Success:429===n?t.RateLimit:n>=400&&n<500?t.Invalid:n>=500?t.Failed:t.Unknown}}(t.Status||(t.Status={})),function(t){t.Explicit="explicitly_set",t.Sampler="client_sampler",t.Rate="client_rate",t.Inheritance="inheritance"}(s||(s={}));var _=Object.setPrototypeOf||({__proto__:[]}instanceof Array?function(t,n){return t.__proto__=n,t}:function(t,n){for(var r in n)t.hasOwnProperty(r)||(t[r]=n[r]);return t});var k=function(t){function n(n){var r=this.constructor,e=t.call(this,n)||this;return e.message=n,e.name=r.prototype.constructor.name,_(e,r.prototype),e}return r(n,t),n}(Error),S=/^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+)/,O=function(){function t(t){"string"==typeof t?this.t(t):this.i(t),this.o()}return t.prototype.toString=function(t){void 0===t&&(t=!1);var n=this,r=n.host,e=n.path,i=n.pass,o=n.port,u=n.projectId;return n.protocol+"://"+n.user+(t&&i?":"+i:"")+"@"+r+(o?":"+o:"")+"/"+(e?e+"/":e)+u},t.prototype.t=function(t){var n=S.exec(t);if(!n)throw new k("Invalid Dsn");var r=f(n.slice(1),6),e=r[0],i=r[1],o=r[2],u=void 0===o?"":o,s=r[3],c=r[4],a=void 0===c?"":c,h="",v=r[5],l=v.split("/");if(l.length>1&&(h=l.slice(0,-1).join("/"),v=l.pop()),v){var d=v.match(/^\d+/);d&&(v=d[0])}this.i({host:s,pass:u,path:h,projectId:v,port:a,protocol:e,user:i})},t.prototype.i=function(t){this.protocol=t.protocol,this.user=t.user,this.pass=t.pass||"",this.host=t.host,this.port=t.port||"",this.path=t.path||"",this.projectId=t.projectId},t.prototype.o=function(){var t=this;if(["protocol","user","host","projectId"].forEach(function(n){if(!t[n])throw new k("Invalid Dsn: "+n+" missing")}),!this.projectId.match(/^\d+$/))throw new k("Invalid Dsn: Invalid projectId "+this.projectId);if("http"!==this.protocol&&"https"!==this.protocol)throw new k("Invalid Dsn: Invalid protocol "+this.protocol);if(this.port&&isNaN(parseInt(this.port,10)))throw new k("Invalid Dsn: Invalid port "+this.port)},t}(),T=function(){function t(){this.u="function"==typeof WeakSet,this.s=this.u?new WeakSet:[]}return t.prototype.memoize=function(t){if(this.u)return!!this.s.has(t)||(this.s.add(t),!1);for(var n=0;n";function R(t){try{return t&&"function"==typeof t&&t.name||D}catch(t){return D}}function I(t,n){return void 0===n&&(n=0),"string"!=typeof t||0===n?t:t.length<=n?t:t.substr(0,n)+"..."}function N(t,n){if(!Array.isArray(t))return"";for(var r=[],e=0;e"}try{o.currentTarget=w(i.currentTarget)?x(i.currentTarget):Object.prototype.toString.call(i.currentTarget)}catch(t){o.currentTarget=""}for(var e in"undefined"!=typeof CustomEvent&&E(t,CustomEvent)&&(o.detail=i.detail),i)Object.prototype.hasOwnProperty.call(i,e)&&(o[e]=i);return o}return t}function q(t){return function(t){return~-encodeURI(t).split(/%..|./).length}(JSON.stringify(t))}function L(t,n,r){void 0===n&&(n=3),void 0===r&&(r=102400);var e=P(t,n);return q(e)>r?L(t,n-1,r):e}function U(t,n){return"domain"===n&&t&&"object"==typeof t&&t.h?"[Domain]":"domainEmitter"===n?"[DomainEmitter]":"undefined"!=typeof global&&t===global?"[Global]":"undefined"!=typeof window&&t===window?"[Window]":"undefined"!=typeof document&&t===document?"[Document]":m(r=t)&&"nativeEvent"in r&&"preventDefault"in r&&"stopPropagation"in r?"[SyntheticEvent]":"number"==typeof t&&t!=t?"[NaN]":void 0===t?"[undefined]":"function"==typeof t?"[Function: "+R(t)+"]":"symbol"==typeof t?"["+String(t)+"]":"bigint"==typeof t?"[BigInt: "+String(t)+"]":t;var r}function H(t,n,r,e){if(void 0===r&&(r=1/0),void 0===e&&(e=new T),0===r)return function(t){var n=Object.prototype.toString.call(t);if("string"==typeof t)return t;if("[object Object]"===n)return"[Object]";if("[object Array]"===n)return"[Array]";var r=U(t);return y(r)?r:n}(n);if(null!=n&&"function"==typeof n.toJSON)return n.toJSON();var i=U(n,t);if(y(i))return i;var o=C(n),u=Array.isArray(n)?[]:{};if(e.memoize(n))return"[Circular ~]";for(var s in o)Object.prototype.hasOwnProperty.call(o,s)&&(u[s]=H(s,o[s],r-1,e));return e.unmemoize(n),u}function P(t,n){try{return JSON.parse(JSON.stringify(t,function(t,r){return H(t,r,n)}))}catch(t){return"**non-serializable**"}}function F(t,n){void 0===n&&(n=40);var r=Object.keys(C(t));if(r.sort(),!r.length)return"[object has no keys]";if(r[0].length>=n)return I(r[0],n);for(var e=r.length;e>0;e--){var i=r.slice(0,e).join(", ");if(!(i.length>n))return e===r.length?i:I(i,n)}return""}function X(t){var n,r;if(m(t)){var e=t,i={};try{for(var o=a(Object.keys(e)),u=o.next();!u.done;u=o.next()){var s=u.value;void 0!==e[s]&&(i[s]=X(e[s]))}}catch(t){n={error:t}}finally{try{u&&!u.done&&(r=o.return)&&r.call(o)}finally{if(n)throw n.error}}return i}return Array.isArray(t)?t.map(X):t}function B(){return"[object process]"===Object.prototype.toString.call("undefined"!=typeof process?process:0)}var J={};function W(){return B()?global:"undefined"!=typeof window?window:"undefined"!=typeof self?self:J}function $(){var t=W(),n=t.crypto||t.msCrypto;if(void 0!==n&&n.getRandomValues){var r=new Uint16Array(8);n.getRandomValues(r),r[3]=4095&r[3]|16384,r[4]=16383&r[4]|32768;var e=function(t){for(var n=t.toString(16);n.length<4;)n="0"+n;return n};return e(r[0])+e(r[1])+e(r[2])+e(r[3])+e(r[4])+e(r[5])+e(r[6])+e(r[7])}return"xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g,function(t){var n=16*Math.random()|0;return("x"===t?n:3&n|8).toString(16)})}function G(t){if(!t)return{};var n=t.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);if(!n)return{};var r=n[6]||"",e=n[8]||"";return{host:n[4],path:n[5],protocol:n[2],relative:n[5]+r+e}}function z(t){if(t.message)return t.message;if(t.exception&&t.exception.values&&t.exception.values[0]){var n=t.exception.values[0];return n.type&&n.value?n.type+": "+n.value:n.type||n.value||t.event_id||""}return t.event_id||""}function V(t){var n=W();if(!("console"in n))return t();var r=n.console,e={};["debug","info","warn","error","log","assert"].forEach(function(t){t in n.console&&r[t].__sentry_original__&&(e[t]=r[t],r[t]=r[t].__sentry_original__)});var i=t();return Object.keys(e).forEach(function(t){r[t]=e[t]}),i}function K(t,n,r){t.exception=t.exception||{},t.exception.values=t.exception.values||[],t.exception.values[0]=t.exception.values[0]||{},t.exception.values[0].value=t.exception.values[0].value||n||"",t.exception.values[0].type=t.exception.values[0].type||r||"Error"}function Q(t,n){void 0===n&&(n={});try{t.exception.values[0].mechanism=t.exception.values[0].mechanism||{},Object.keys(n).forEach(function(r){t.exception.values[0].mechanism[r]=n[r]})}catch(t){}}var Y=6e4;var Z=W(),tt="Sentry Logger ",nt=function(){function t(){this.v=!1}return t.prototype.disable=function(){this.v=!1},t.prototype.enable=function(){this.v=!0},t.prototype.log=function(){for(var t=[],n=0;n2?n[2]:void 0;if(e){var i=ut,o=String(e);ut=o,vt("history",{from:i,to:o})}return t.apply(this,n)}}st.onpopstate=function(){for(var t=[],n=0;n0&&r(!1)},t);_t.all(n.T).then(function(){clearTimeout(e),r(!0)}).then(null,function(){r(!0)})})},t}(),St={nowSeconds:function(){return Date.now()/1e3}};var Ot=B()?function(){try{return(t=module,n="perf_hooks",t.require(n)).performance}catch(t){return}var t,n}():function(){var t=W().performance;if(t&&t.now)return{now:function(){return t.now()},timeOrigin:Date.now()-t.now()}}(),Tt=void 0===Ot?St:{nowSeconds:function(){return(Ot.timeOrigin+Ot.now())/1e3}},Dt=St.nowSeconds.bind(St),Rt=(Tt.nowSeconds.bind(Tt),function(){var t=W().performance;if(t)t.timeOrigin?t.timeOrigin:t.timing&&t.timing.navigationStart||Date.now()}(),function(){function t(){this.D=!1,this.R=[],this.I=[],this.N=[],this.M={},this.A={},this.C={},this.q={}}return t.clone=function(n){var r=new t;return n&&(r.N=h(n.N),r.A=c({},n.A),r.C=c({},n.C),r.q=c({},n.q),r.M=n.M,r.L=n.L,r.U=n.U,r.H=n.H,r.P=n.P,r.F=n.F,r.I=h(n.I)),r},t.prototype.addScopeListener=function(t){this.R.push(t)},t.prototype.addEventProcessor=function(t){return this.I.push(t),this},t.prototype.setUser=function(t){return this.M=t||{},this.H&&this.H.update({user:t}),this.X(),this},t.prototype.getUser=function(){return this.M},t.prototype.setTags=function(t){return this.A=c(c({},this.A),t),this.X(),this},t.prototype.setTag=function(t,n){var r;return this.A=c(c({},this.A),((r={})[t]=n,r)),this.X(),this},t.prototype.setExtras=function(t){return this.C=c(c({},this.C),t),this.X(),this},t.prototype.setExtra=function(t,n){var r;return this.C=c(c({},this.C),((r={})[t]=n,r)),this.X(),this},t.prototype.setFingerprint=function(t){return this.F=t,this.X(),this},t.prototype.setLevel=function(t){return this.L=t,this.X(),this},t.prototype.setTransactionName=function(t){return this.P=t,this.X(),this},t.prototype.setTransaction=function(t){return this.setTransactionName(t)},t.prototype.setContext=function(t,n){var r;return null===n?delete this.q[t]:this.q=c(c({},this.q),((r={})[t]=n,r)),this.X(),this},t.prototype.setSpan=function(t){return this.U=t,this.X(),this},t.prototype.getSpan=function(){return this.U},t.prototype.getTransaction=function(){var t,n,r,e,i=this.getSpan();return(null===(t=i)||void 0===t?void 0:t.transaction)?null===(n=i)||void 0===n?void 0:n.transaction:(null===(e=null===(r=i)||void 0===r?void 0:r.spanRecorder)||void 0===e?void 0:e.spans[0])?i.spanRecorder.spans[0]:void 0},t.prototype.setSession=function(t){return t?this.H=t:delete this.H,this.X(),this},t.prototype.getSession=function(){return this.H},t.prototype.update=function(n){if(!n)return this;if("function"==typeof n){var r=n(this);return r instanceof t?r:this}return n instanceof t?(this.A=c(c({},this.A),n.A),this.C=c(c({},this.C),n.C),this.q=c(c({},this.q),n.q),n.M&&Object.keys(n.M).length&&(this.M=n.M),n.L&&(this.L=n.L),n.F&&(this.F=n.F)):m(n)&&(n=n,this.A=c(c({},this.A),n.tags),this.C=c(c({},this.C),n.extra),this.q=c(c({},this.q),n.contexts),n.user&&(this.M=n.user),n.level&&(this.L=n.level),n.fingerprint&&(this.F=n.fingerprint)),this},t.prototype.clear=function(){return this.N=[],this.A={},this.C={},this.M={},this.q={},this.L=void 0,this.P=void 0,this.F=void 0,this.U=void 0,this.H=void 0,this.X(),this},t.prototype.addBreadcrumb=function(t,n){var r=c({timestamp:Dt()},t);return this.N=void 0!==n&&n>=0?h(this.N,[r]).slice(-n):h(this.N,[r]),this.X(),this},t.prototype.clearBreadcrumbs=function(){return this.N=[],this.X(),this},t.prototype.applyToEvent=function(t,n){var r;if(this.C&&Object.keys(this.C).length&&(t.extra=c(c({},this.C),t.extra)),this.A&&Object.keys(this.A).length&&(t.tags=c(c({},this.A),t.tags)),this.M&&Object.keys(this.M).length&&(t.user=c(c({},this.M),t.user)),this.q&&Object.keys(this.q).length&&(t.contexts=c(c({},this.q),t.contexts)),this.L&&(t.level=this.L),this.P&&(t.transaction=this.P),this.U){t.contexts=c({trace:this.U.getTraceContext()},t.contexts);var e=null===(r=this.U.transaction)||void 0===r?void 0:r.name;e&&(t.tags=c({transaction:e},t.tags))}return this.B(t),t.breadcrumbs=h(t.breadcrumbs||[],this.N),t.breadcrumbs=t.breadcrumbs.length>0?t.breadcrumbs:void 0,this.J(h(It(),this.I),t,n)},t.prototype.J=function(t,n,r,e){var i=this;return void 0===e&&(e=0),new _t(function(o,u){var s=t[e];if(null===n||"function"!=typeof s)o(n);else{var a=s(c({},n),r);g(a)?a.then(function(n){return i.J(t,n,r,e+1).then(o)}).then(null,u):i.J(t,a,r,e+1).then(o).then(null,u)}})},t.prototype.X=function(){var t=this;this.D||(this.D=!0,this.R.forEach(function(n){n(t)}),this.D=!1)},t.prototype.B=function(t){t.fingerprint=t.fingerprint?Array.isArray(t.fingerprint)?t.fingerprint:[t.fingerprint]:[],this.F&&(t.fingerprint=t.fingerprint.concat(this.F)),t.fingerprint&&!t.fingerprint.length&&delete t.fingerprint},t}());function It(){var t=W();return t.__SENTRY__=t.__SENTRY__||{},t.__SENTRY__.globalEventProcessors=t.__SENTRY__.globalEventProcessors||[],t.__SENTRY__.globalEventProcessors}function Nt(t){It().push(t)}var Mt=function(){function t(t){this.errors=0,this.sid=$(),this.timestamp=Date.now(),this.started=Date.now(),this.duration=0,this.status=i.Ok,t&&this.update(t)}return t.prototype.update=function(t){void 0===t&&(t={}),t.user&&(t.user.ip_address&&(this.ipAddress=t.user.ip_address),t.did||(this.did=t.user.id||t.user.email||t.user.username)),this.timestamp=t.timestamp||Date.now(),t.sid&&(this.sid=32===t.sid.length?t.sid:$()),t.did&&(this.did=""+t.did),"number"==typeof t.started&&(this.started=t.started),"number"==typeof t.duration?this.duration=t.duration:this.duration=this.timestamp-this.started,t.release&&(this.release=t.release),t.environment&&(this.environment=t.environment),t.ipAddress&&(this.ipAddress=t.ipAddress),t.userAgent&&(this.userAgent=t.userAgent),"number"==typeof t.errors&&(this.errors=t.errors),t.status&&(this.status=t.status)},t.prototype.close=function(t){t?this.update({status:t}):this.status===i.Ok?this.update({status:i.Exited}):this.update()},t.prototype.toJSON=function(){return X({sid:""+this.sid,init:!0,started:new Date(this.started).toISOString(),timestamp:new Date(this.timestamp).toISOString(),status:this.status,errors:this.errors,did:"number"==typeof this.did||"string"==typeof this.did?""+this.did:void 0,duration:this.duration,attrs:X({release:this.release,environment:this.environment,ip_address:this.ipAddress,user_agent:this.userAgent})})},t}(),At=3,Ct=function(){function t(t,n,r){void 0===n&&(n=new Rt),void 0===r&&(r=At),this.W=r,this.$=[{}],this.getStackTop().scope=n,this.bindClient(t)}return t.prototype.isOlderThan=function(t){return this.W=t&&(clearInterval(i),r(!1)))},1)})},t.prototype.ct=function(){return this.it},t.prototype.vt=function(){return!1!==this.getOptions().enabled&&void 0!==this.ut},t.prototype.dt=function(t,n,r){var e=this,i=this.getOptions().normalizeDepth,o=void 0===i?3:i,u=c(c({},t),{event_id:t.event_id||(r&&r.event_id?r.event_id:$()),timestamp:t.timestamp||Dt()});this.pt(u),this.yt(u);var s=n;r&&r.captureContext&&(s=Rt.clone(s).update(r.captureContext));var a=_t.resolve(u);return s&&(a=s.applyToEvent(u,r)),a.then(function(t){return"number"==typeof o&&o>0?e.bt(t,o):t})},t.prototype.bt=function(t,n){if(!t)return null;var r=c(c(c(c(c({},t),t.breadcrumbs&&{breadcrumbs:t.breadcrumbs.map(function(t){return c(c({},t),t.data&&{data:P(t.data,n)})})}),t.user&&{user:P(t.user,n)}),t.contexts&&{contexts:P(t.contexts,n)}),t.extra&&{extra:P(t.extra,n)});return t.contexts&&t.contexts.trace&&(r.contexts.trace=t.contexts.trace),r},t.prototype.pt=function(t){var n=this.getOptions(),r=n.environment,e=n.release,i=n.dist,o=n.maxValueLength,u=void 0===o?250:o;"environment"in t||(t.environment="environment"in n?r:"production"),void 0===t.release&&void 0!==e&&(t.release=e),void 0===t.dist&&void 0!==i&&(t.dist=i),t.message&&(t.message=I(t.message,u));var s=t.exception&&t.exception.values&&t.exception.values[0];s&&s.value&&(s.value=I(s.value,u));var c=t.request;c&&c.url&&(c.url=I(c.url,u))},t.prototype.yt=function(t){var n=t.sdk,r=Object.keys(this.rt);n&&r.length>0&&(n.integrations=r)},t.prototype.wt=function(t){this.ct().sendEvent(t)},t.prototype.at=function(t,n,r){return this.gt(t,n,r).then(function(t){return t.event_id},function(t){rt.error(t)})},t.prototype.gt=function(t,n,r){var e=this,i=this.getOptions(),o=i.beforeSend,u=i.sampleRate;if(!this.vt())return _t.reject(new k("SDK not enabled, will not send event."));var s="transaction"===t.type;return!s&&"number"==typeof u&&Math.random()>u?_t.reject(new k("Discarding event because it's not included in the random sample (sampling rate = "+u+")")):this.dt(t,r,n).then(function(t){if(null===t)throw new k("An event processor returned null, will not send event.");if(n&&n.data&&!0===n.data.__sentry__||s||!o)return t;var r=o(t,n);if(void 0===r)throw new k("`beforeSend` method has to return `null` or a valid event.");return g(r)?r.then(function(t){return t},function(t){throw new k("beforeSend rejected with "+t)}):r}).then(function(t){if(null===t)throw new k("`beforeSend` returned `null`, will not send event.");var n=r&&r.getSession&&r.getSession();return!s&&n&&e.lt(n,t),e.wt(t),t}).then(null,function(t){if(t instanceof k)throw t;throw e.captureException(t,{data:{__sentry__:!0},originalException:t}),new k("Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: "+t)})},t.prototype.st=function(t){var n=this;this.et+=1,t.then(function(t){return n.et-=1,t},function(t){return n.et-=1,t})},t}(),zt=function(){function n(){}return n.prototype.sendEvent=function(n){return _t.resolve({reason:"NoopTransport: Event has been skipped because no Dsn is configured.",status:t.Status.Skipped})},n.prototype.close=function(t){return _t.resolve(!0)},n}(),Vt=function(){function t(t){this.ot=t,this.ot.dsn||rt.warn("No DSN provided, backend will not do anything."),this.Et=this.xt()}return t.prototype.eventFromException=function(t,n){throw new k("Backend has to implement `eventFromException` method")},t.prototype.eventFromMessage=function(t,n,r){throw new k("Backend has to implement `eventFromMessage` method")},t.prototype.sendEvent=function(t){this.Et.sendEvent(t).then(null,function(t){rt.error("Error while sending event: "+t)})},t.prototype.sendSession=function(t){this.Et.sendSession?this.Et.sendSession(t).then(null,function(t){rt.error("Error while sending session: "+t)}):rt.warn("Dropping session because custom transport doesn't implement sendSession")},t.prototype.getTransport=function(){return this.Et},t.prototype.xt=function(){return new zt},t}();function Kt(t){if(t.metadata&&t.metadata.sdk){var n=t.metadata.sdk;return{name:n.name,version:n.version}}}function Qt(t,n){return n?(t.sdk=t.sdk||{name:n.name,version:n.version},t.sdk.name=t.sdk.name||n.name,t.sdk.version=t.sdk.version||n.version,t.sdk.integrations=h(t.sdk.integrations||[],n.integrations||[]),t.sdk.packages=h(t.sdk.packages||[],n.packages||[]),t):t}function Yt(t,n){var r=Kt(n);return{body:JSON.stringify(c({sent_at:(new Date).toISOString()},r&&{sdk:r}))+"\n"+JSON.stringify({type:"session"})+"\n"+JSON.stringify(t),type:"session",url:n.getEnvelopeEndpointWithUrlEncodedAuth()}}function Zt(t,n){var r=t.tags||{},e=r.__sentry_samplingMethod,i=r.__sentry_sampleRate,o=function(t,n){var r={};for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&n.indexOf(e)<0&&(r[e]=t[e]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(e=Object.getOwnPropertySymbols(t);i|[-a-z]+:|.*bundle|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,an=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:file|https?|blob|chrome|webpack|resource|moz-extension|capacitor).*?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. \/=]+)(?::(\d+))?(?::(\d+))?\s*$/i,fn=/^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,hn=/(\S+) line (\d+)(?: > eval line \d+)* > eval/i,vn=/\((\S*)(?::(\d+))(?::(\d+))\)/,ln=/Minified React error #\d+;/i;function dn(t){var n=null,r=0;t&&("number"==typeof t.framesToPop?r=t.framesToPop:ln.test(t.message)&&(r=1));try{if(n=function(t){if(!t||!t.stacktrace)return null;for(var n,r=t.stacktrace,e=/ line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i,i=/ line (\d+), column (\d+)\s*(?:in (?:]+)>|([^)]+))\((.*)\))? in (.*):\s*$/i,o=r.split("\n"),u=[],s=0;s eval")>-1&&(n=hn.exec(r[3]))?(r[1]=r[1]||"eval",r[3]=n[1],r[4]=n[2],r[5]=""):0!==u||r[5]||void 0===t.columnNumber||(i[0].column=t.columnNumber+1),e={url:r[3],func:r[1]||sn,args:r[2]?r[2].split(","):[],line:r[4]?+r[4]:null,column:r[5]?+r[5]:null}}!e.func&&e.line&&(e.func=sn),i.push(e)}if(!i.length)return null;return{message:yn(t),name:t.name,stack:i}}(t))return pn(n,r)}catch(t){}return{message:yn(t),name:t&&t.name,stack:[],failed:!0}}function pn(t,n){try{return c(c({},t),{stack:t.stack.slice(n)})}catch(n){return t}}function yn(t){var n=t&&t.message;return n?n.error&&"string"==typeof n.error.message?n.error.message:n:"No error message"}var mn=50;function bn(t){var n=gn(t.stack),r={type:t.name,value:t.message};return n&&n.length&&(r.stacktrace={frames:n}),void 0===r.type&&""===r.value&&(r.value="Unrecoverable error caught"),r}function wn(t){return{exception:{values:[bn(t)]}}}function gn(t){if(!t||!t.length)return[];var n=t,r=n[0].func||"",e=n[n.length-1].func||"";return-1===r.indexOf("captureMessage")&&-1===r.indexOf("captureException")||(n=n.slice(1)),-1!==e.indexOf("sentryWrapped")&&(n=n.slice(0,-1)),n.slice(0,mn).map(function(t){return{colno:null===t.column?void 0:t.column,filename:t.url||n[0].url,function:t.func||"?",in_app:!0,lineno:null===t.line?void 0:t.line}}).reverse()}function En(n,r,e){var i=jn(r,e&&e.syntheticException||void 0,{attachStacktrace:n.attachStacktrace});return Q(i,{handled:!0,type:"generic"}),i.level=t.Severity.Error,e&&e.event_id&&(i.event_id=e.event_id),_t.resolve(i)}function xn(n,r,e,i){void 0===e&&(e=t.Severity.Info);var o=_n(r,i&&i.syntheticException||void 0,{attachStacktrace:n.attachStacktrace});return o.level=e,i&&i.event_id&&(o.event_id=i.event_id),_t.resolve(o)}function jn(t,n,r){var e,i;if(void 0===r&&(r={}),l(t)&&t.error)return e=wn(dn(t=t.error));if(d(t)||(i=t,"[object DOMException]"===Object.prototype.toString.call(i))){var o=t,u=o.name||(d(o)?"DOMError":"DOMException"),s=o.message?u+": "+o.message:u;return K(e=_n(s,n,r),s),"code"in o&&(e.tags=c(c({},e.tags),{"DOMException.code":""+o.code})),e}return v(t)?e=wn(dn(t)):m(t)||b(t)?(Q(e=function(t,n,r){var e={exception:{values:[{type:b(t)?t.constructor.name:r?"UnhandledRejection":"Error",value:"Non-Error "+(r?"promise rejection":"exception")+" captured with keys: "+F(t)}]},extra:{__serialized__:L(t)}};if(n){var i=gn(dn(n).stack);e.stacktrace={frames:i}}return e}(t,n,r.rejection),{synthetic:!0}),e):(K(e=_n(t,n,r),""+t,void 0),Q(e,{synthetic:!0}),e)}function _n(t,n,r){void 0===r&&(r={});var e={message:t};if(r.attachStacktrace&&n){var i=gn(dn(n).stack);e.stacktrace={frames:i}}return e}var kn=function(){function n(t){this.options=t,this.T=new kt(30),this.It={},this.Nt=new Jt(t.dsn,t.Mt),this.url=this.Nt.getStoreEndpointWithUrlEncodedAuth()}return n.prototype.sendEvent=function(t){throw new k("Transport Class has to implement `sendEvent` method")},n.prototype.close=function(t){return this.T.drain(t)},n.prototype.At=function(n){var r=n.requestType,e=n.response,i=n.headers,o=n.resolve,u=n.reject,s=t.Status.fromHttpCode(e.status);this.Ct(i)&&rt.warn("Too many requests, backing off until: "+this.qt(r)),s!==t.Status.Success?u(e):o({status:s})},n.prototype.qt=function(t){return this.It[t]||this.It.all},n.prototype.Lt=function(t){return this.qt(t)>new Date(Date.now())},n.prototype.Ct=function(t){var n,r,e,i,o=Date.now(),u=t["x-sentry-rate-limits"],s=t["retry-after"];if(u){try{for(var c=a(u.trim().split(",")),f=c.next();!f.done;f=c.next()){var h=f.value.split(":",2),v=parseInt(h[0],10),l=1e3*(isNaN(v)?60:v);try{for(var d=(e=void 0,a(h[1].split(";"))),p=d.next();!p.done;p=d.next()){var y=p.value;this.It[y||"all"]=new Date(o+l)}}catch(t){e={error:t}}finally{try{p&&!p.done&&(i=d.return)&&i.call(d)}finally{if(e)throw e.error}}}}catch(t){n={error:t}}finally{try{f&&!f.done&&(r=c.return)&&r.call(c)}finally{if(n)throw n.error}}return!0}return!!s&&(this.It.all=new Date(o+function(t,n){if(!n)return Y;var r=parseInt(""+n,10);if(!isNaN(r))return 1e3*r;var e=Date.parse(""+n);return isNaN(e)?Y:e-t}(o,s)),!0)},n}(),Sn=W(),On=function(t){function n(){return null!==t&&t.apply(this,arguments)||this}return r(n,t),n.prototype.sendEvent=function(t){return this.Ut(Zt(t,this.Nt),t)},n.prototype.sendSession=function(t){return this.Ut(Yt(t,this.Nt),t)},n.prototype.Ut=function(t,n){var r=this;if(this.Lt(t.type))return Promise.reject({event:n,type:t.type,reason:"Transport locked till "+this.qt(t.type)+" due to too many requests.",status:429});var e={body:t.body,method:"POST",referrerPolicy:ot()?"origin":""};return void 0!==this.options.fetchParameters&&Object.assign(e,this.options.fetchParameters),void 0!==this.options.headers&&(e.headers=this.options.headers),this.T.add(new _t(function(n,i){Sn.fetch(t.url,e).then(function(e){var o={"x-sentry-rate-limits":e.headers.get("X-Sentry-Rate-Limits"),"retry-after":e.headers.get("Retry-After")};r.At({requestType:t.type,response:e,headers:o,resolve:n,reject:i})}).catch(i)}))},n}(kn),Tn=function(t){function n(){return null!==t&&t.apply(this,arguments)||this}return r(n,t),n.prototype.sendEvent=function(t){return this.Ut(Zt(t,this.Nt),t)},n.prototype.sendSession=function(t){return this.Ut(Yt(t,this.Nt),t)},n.prototype.Ut=function(t,n){var r=this;return this.Lt(t.type)?Promise.reject({event:n,type:t.type,reason:"Transport locked till "+this.qt(t.type)+" due to too many requests.",status:429}):this.T.add(new _t(function(n,e){var i=new XMLHttpRequest;for(var o in i.onreadystatechange=function(){if(4===i.readyState){var o={"x-sentry-rate-limits":i.getResponseHeader("X-Sentry-Rate-Limits"),"retry-after":i.getResponseHeader("Retry-After")};r.At({requestType:t.type,response:i,headers:o,resolve:n,reject:e})}},i.open("POST",t.url),r.options.headers)r.options.headers.hasOwnProperty(o)&&i.setRequestHeader(o,r.options.headers[o]);i.send(t.body)}))},n}(kn),Dn=Object.freeze({__proto__:null,BaseTransport:kn,FetchTransport:On,XHRTransport:Tn}),Rn=function(n){function e(){return null!==n&&n.apply(this,arguments)||this}return r(e,n),e.prototype.eventFromException=function(t,n){return En(this.ot,t,n)},e.prototype.eventFromMessage=function(n,r,e){return void 0===r&&(r=t.Severity.Info),xn(this.ot,n,r,e)},e.prototype.xt=function(){if(!this.ot.dsn)return n.prototype.xt.call(this);var t=c(c({},this.ot.transportOptions),{dsn:this.ot.dsn,Mt:this.ot.Mt});return this.ot.transport?new this.ot.transport(t):et()?new On(t):new Tn(t)},e}(Vt),In=0;function Nn(){return In>0}function Mn(t,n,r){if(void 0===n&&(n={}),"function"!=typeof t)return t;try{if(t.__sentry__)return t;if(t.__sentry_wrapped__)return t.__sentry_wrapped__}catch(n){return t}var sentryWrapped=function(){var e=Array.prototype.slice.call(arguments);try{r&&"function"==typeof r&&r.apply(this,arguments);var i=e.map(function(t){return Mn(t,n)});return t.handleEvent?t.handleEvent.apply(this,i):t.apply(this,i)}catch(t){throw In+=1,setTimeout(function(){In-=1}),Bt(function(r){r.addEventProcessor(function(t){var r=c({},t);return n.mechanism&&(K(r,void 0,void 0),Q(r,n.mechanism)),r.extra=c(c({},r.extra),{arguments:e}),r}),captureException(t)}),t}};try{for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&(sentryWrapped[e]=t[e])}catch(t){}t.prototype=t.prototype||{},sentryWrapped.prototype=t.prototype,Object.defineProperty(t,"__sentry_wrapped__",{enumerable:!1,value:sentryWrapped}),Object.defineProperties(sentryWrapped,{__sentry__:{enumerable:!1,value:!0},__sentry_original__:{enumerable:!1,value:t}});try{Object.getOwnPropertyDescriptor(sentryWrapped,"name").configurable&&Object.defineProperty(sentryWrapped,"name",{get:function(){return t.name}})}catch(t){}return sentryWrapped}function An(t){if(void 0===t&&(t={}),t.eventId)if(t.dsn){var n=document.createElement("script");n.async=!0,n.src=new Jt(t.dsn).getReportDialogEndpoint(t),t.onLoad&&(n.onload=t.onLoad),(document.head||document.body).appendChild(n)}else rt.error("Missing dsn option in showReportDialog call");else rt.error("Missing eventId option in showReportDialog call")}var Cn=function(){function n(t){this.name=n.id,this.Ht=!1,this.Pt=!1,this.ot=c({onerror:!0,onunhandledrejection:!0},t)}return n.prototype.setupOnce=function(){Error.stackTraceLimit=50,this.ot.onerror&&(rt.log("Global Handler attached: onerror"),this.Ft()),this.ot.onunhandledrejection&&(rt.log("Global Handler attached: onunhandledrejection"),this.Xt())},n.prototype.Ft=function(){var t=this;this.Ht||(ht({callback:function(r){var e=r.error,i=Ut(),o=i.getIntegration(n),u=e&&!0===e.__sentry_own_request__;if(o&&!Nn()&&!u){var s=i.getClient(),c=y(e)?t.Bt(r.msg,r.url,r.line,r.column):t.Jt(jn(e,void 0,{attachStacktrace:s&&s.getOptions().attachStacktrace,rejection:!1}),r.url,r.line,r.column);Q(c,{handled:!1,type:"onerror"}),i.captureEvent(c,{originalException:e})}},type:"error"}),this.Ht=!0)},n.prototype.Xt=function(){var r=this;this.Pt||(ht({callback:function(e){var i=e;try{"reason"in e?i=e.reason:"detail"in e&&"reason"in e.detail&&(i=e.detail.reason)}catch(t){}var o=Ut(),u=o.getIntegration(n),s=i&&!0===i.__sentry_own_request__;if(!u||Nn()||s)return!0;var c=o.getClient(),a=y(i)?r.Wt(i):jn(i,void 0,{attachStacktrace:c&&c.getOptions().attachStacktrace,rejection:!0});a.level=t.Severity.Error,Q(a,{handled:!1,type:"onunhandledrejection"}),o.captureEvent(a,{originalException:i})},type:"unhandledrejection"}),this.Pt=!0)},n.prototype.Bt=function(t,n,r,e){var i,o=l(t)?t.message:t;if(p(o)){var u=o.match(/^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i);u&&(i=u[1],o=u[2])}var s={exception:{values:[{type:i||"Error",value:o}]}};return this.Jt(s,n,r,e)},n.prototype.Wt=function(t){return{exception:{values:[{type:"UnhandledRejection",value:"Non-Error promise rejection captured with value: "+String(t)}]}}},n.prototype.Jt=function(t,n,r,e){t.exception=t.exception||{},t.exception.values=t.exception.values||[],t.exception.values[0]=t.exception.values[0]||{},t.exception.values[0].stacktrace=t.exception.values[0].stacktrace||{},t.exception.values[0].stacktrace.frames=t.exception.values[0].stacktrace.frames||[];var i=isNaN(parseInt(e,10))?void 0:e,o=isNaN(parseInt(r,10))?void 0:r,u=p(n)&&n.length>0?n:function(){try{return document.location.href}catch(t){return""}}();return 0===t.exception.values[0].stacktrace.frames.length&&t.exception.values[0].stacktrace.frames.push({colno:i,filename:u,function:"?",in_app:!0,lineno:o}),t},n.id="GlobalHandlers",n}(),qn=["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"],Ln=function(){function t(n){this.name=t.id,this.ot=c({XMLHttpRequest:!0,eventTarget:!0,requestAnimationFrame:!0,setInterval:!0,setTimeout:!0},n)}return t.prototype.setupOnce=function(){var t=W();(this.ot.setTimeout&&A(t,"setTimeout",this.$t.bind(this)),this.ot.setInterval&&A(t,"setInterval",this.$t.bind(this)),this.ot.requestAnimationFrame&&A(t,"requestAnimationFrame",this.Gt.bind(this)),this.ot.XMLHttpRequest&&"XMLHttpRequest"in t&&A(XMLHttpRequest.prototype,"send",this.zt.bind(this)),this.ot.eventTarget)&&(Array.isArray(this.ot.eventTarget)?this.ot.eventTarget:qn).forEach(this.Vt.bind(this))},t.prototype.$t=function(t){return function(){for(var n=[],r=0;r"}0!==n.length&&Ut().addBreadcrumb({category:"ui."+t.name,message:n},{event:t.event,name:t.name})},n.prototype.Yt=function(t){if(t.endTimestamp){if(t.xhr.__sentry_own_request__)return;var n=t.xhr.__sentry_xhr__||{},r=n.method,e=n.url,i=n.status_code,o=n.body;Ut().addBreadcrumb({category:"xhr",data:{method:r,url:e,status_code:i},type:"http"},{xhr:t.xhr,input:o})}else;},n.prototype.Zt=function(n){n.endTimestamp&&(n.fetchData.url.match(/sentry_key/)&&"POST"===n.fetchData.method||(n.error?Ut().addBreadcrumb({category:"fetch",data:n.fetchData,level:t.Severity.Error,type:"http"},{data:n.error,input:n.args}):Ut().addBreadcrumb({category:"fetch",data:c(c({},n.fetchData),{status_code:n.response.status}),type:"http"},{input:n.args,response:n.response})))},n.prototype.tn=function(t){var n=W(),r=t.from,e=t.to,i=G(n.location.href),o=G(r),u=G(e);o.path||(o=i),i.protocol===u.protocol&&i.host===u.host&&(e=u.relative),i.protocol===o.protocol&&i.host===o.host&&(r=o.relative),Ut().addBreadcrumb({category:"navigation",data:{from:r,to:e}})},n.id="Breadcrumbs",n}(),Hn="cause",Pn=5,Fn=function(){function t(n){void 0===n&&(n={}),this.name=t.id,this.nn=n.key||Hn,this.O=n.limit||Pn}return t.prototype.setupOnce=function(){Nt(function(n,r){var e=Ut().getIntegration(t);return e?e.rn(n,r):n})},t.prototype.rn=function(t,n){if(!(t.exception&&t.exception.values&&n&&E(n.originalException,Error)))return t;var r=this.en(n.originalException,this.nn);return t.exception.values=h(r,t.exception.values),t},t.prototype.en=function(t,n,r){if(void 0===r&&(r=[]),!E(t[n],Error)||r.length+1>=this.O)return r;var e=bn(dn(t[n]));return this.en(t[n],n,h([e],r))},t.id="LinkedErrors",t}(),Xn=W(),Bn=function(){function t(){this.name=t.id}return t.prototype.setupOnce=function(){Nt(function(n){var r,e,i;if(Ut().getIntegration(t)){if(!Xn.navigator&&!Xn.location&&!Xn.document)return n;var o=(null===(r=n.request)||void 0===r?void 0:r.url)||(null===(e=Xn.location)||void 0===e?void 0:e.href),u=(Xn.document||{}).referrer,s=(Xn.navigator||{}).userAgent,a=c(c(c({},null===(i=n.request)||void 0===i?void 0:i.headers),u&&{Referer:u}),s&&{"User-Agent":s}),f=c(c({},o&&{url:o}),{headers:a});return c(c({},n),{request:f})}return n})},t.id="UserAgent",t}(),Jn=Object.freeze({__proto__:null,GlobalHandlers:Cn,TryCatch:Ln,Breadcrumbs:Un,LinkedErrors:Fn,UserAgent:Bn}),Wn=function(t){function n(n){return void 0===n&&(n={}),t.call(this,Rn,n)||this}return r(n,t),n.prototype.showReportDialog=function(t){void 0===t&&(t={}),W().document&&(this.vt()?An(c(c({},t),{dsn:t.dsn||this.getDsn()})):rt.error("Trying to call showReportDialog with Sentry Client disabled"))},n.prototype.dt=function(n,r,e){return n.platform=n.platform||"javascript",t.prototype.dt.call(this,n,r,e)},n.prototype.wt=function(n){var r=this.getIntegration(Un);r&&r.addSentryBreadcrumb(n),t.prototype.wt.call(this,n)},n}(Gt),$n=[new on,new rn,new Ln,new Un,new Cn,new Fn,new Bn];var Gn={},zn=W();zn.Sentry&&zn.Sentry.Integrations&&(Gn=zn.Sentry.Integrations);var Vn=c(c(c({},Gn),un),Jn);return t.BrowserClient=Wn,t.Hub=Ct,t.Integrations=Vn,t.SDK_NAME="sentry.javascript.browser",t.SDK_VERSION=nn,t.Scope=Rt,t.Transports=Dn,t.addBreadcrumb=function(t){Xt("addBreadcrumb",t)},t.addGlobalEventProcessor=Nt,t.captureEvent=function(t){return Xt("captureEvent",t)},t.captureException=captureException,t.captureMessage=function(t,n){var r;try{throw new Error(t)}catch(t){r=t}return Xt("captureMessage",t,"string"==typeof n?n:void 0,c({originalException:t,syntheticException:r},"string"!=typeof n?{captureContext:n}:void 0))},t.close=function(t){var n=Ut().getClient();return n?n.close(t):_t.reject(!1)},t.configureScope=function(t){Xt("configureScope",t)},t.defaultIntegrations=$n,t.eventFromException=En,t.eventFromMessage=xn,t.flush=function(t){var n=Ut().getClient();return n?n.flush(t):_t.reject(!1)},t.forceLoad=function(){},t.getCurrentHub=Ut,t.getHubFromCarrier=Pt,t.init=function(t){if(void 0===t&&(t={}),void 0===t.defaultIntegrations&&(t.defaultIntegrations=$n),void 0===t.release){var n=W();n.SENTRY_RELEASE&&n.SENTRY_RELEASE.id&&(t.release=n.SENTRY_RELEASE.id)}void 0===t.autoSessionTracking&&(t.autoSessionTracking=!0),t.Mt=t.Mt||{},t.Mt.sdk={name:"sentry.javascript.browser",packages:[{name:"npm:@sentry/browser",version:nn}],version:nn},function(t,n){!0===n.debug&&rt.enable();var r=Ut(),e=new t(n);r.bindClient(e)}(Wn,t),t.autoSessionTracking&&function(){var t=W(),n=t.document;if(void 0!==n){var r=Ut(),e="complete"===n.readyState,i=!1,o=function(){i&&e&&r.endSession()},u=function(){e=!0,o(),t.removeEventListener("load",u)};r.startSession(),e||t.addEventListener("load",u);try{var s=new PerformanceObserver(function(t,n){t.getEntries().forEach(function(t){"first-contentful-paint"===t.name&&t.startTime=0&&(b=j-(v=Math.sqrt(t)/(2*Math.abs(h))),l=j+v,1>=Math.abs(b)&&k++,1>=Math.abs(l)&&k++,b< -1&&(b=l)),1===k?p<0?f=d+b:g=d+b:2===k&&(f=d+(o<0?l:b),g=d+(o<0?b:l)),!f||!g);d+=2)p=n;var q={};return f&&(q.rise=z(e,f)),g&&(q.set=z(e,g)),f||g||(q[o>0?"alwaysUp":"alwaysDown"]=!0),q},"object"==typeof exports&&"undefined"!=typeof module?module.exports=a:"function"==typeof define&&define.amd?define(a):window.SunCalc=a}()
diff --git a/js/lib/threejs/threejs.js b/js/lib/threejs/threejs.js
new file mode 100644
index 0000000..5477f44
--- /dev/null
+++ b/js/lib/threejs/threejs.js
@@ -0,0 +1,136 @@
+/* eslint-disable */
+
+/**
+ * @license
+ * Copyright 2010-2022 Three.js Authors
+ * SPDX-License-Identifier: MIT
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="142dev",i=100,n=300,r=301,s=302,a=303,o=304,l=306,c=1e3,h=1001,u=1002,d=1003,p=1004,m=1005,f=1006,g=1007,v=1008,x=1009,y=1012,_=1014,M=1015,b=1016,w=1020,S=1023,T=1026,A=1027,E=33776,C=33777,L=33778,R=33779,P=35840,I=35841,D=35842,N=35843,O=37492,z=37496,F=37808,B=37809,U=37810,k=37811,G=37812,V=37813,H=37814,W=37815,j=37816,q=37817,X=37818,Y=37819,Z=37820,J=37821,K=36492,Q=2300,$=2301,tt=2302,et=2400,it=2401,nt=2402,rt=2500,st=2501,at=3e3,ot=3001,lt="srgb",ct="srgb-linear",ht=7680,ut=35044,dt="300 es",pt=1035;class mt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const i=this._listeners;void 0===i[t]&&(i[t]=[]),-1===i[t].indexOf(e)&&i[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const i=this._listeners;return void 0!==i[t]&&-1!==i[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const i=this._listeners[t];if(void 0!==i){const t=i.indexOf(e);-1!==t&&i.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const i=e.slice(0);for(let e=0,n=i.length;e>8&255]+ft[t>>16&255]+ft[t>>24&255]+"-"+ft[255&e]+ft[e>>8&255]+"-"+ft[e>>16&15|64]+ft[e>>24&255]+"-"+ft[63&i|128]+ft[i>>8&255]+"-"+ft[i>>16&255]+ft[i>>24&255]+ft[255&n]+ft[n>>8&255]+ft[n>>16&255]+ft[n>>24&255]).toLowerCase()}function _t(t,e,i){return Math.max(e,Math.min(i,t))}function Mt(t,e){return(t%e+e)%e}function bt(t,e,i){return(1-i)*t+i*e}function wt(t){return 0==(t&t-1)&&0!==t}function St(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function Tt(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}var At=Object.freeze({__proto__:null,DEG2RAD:vt,RAD2DEG:xt,generateUUID:yt,clamp:_t,euclideanModulo:Mt,mapLinear:function(t,e,i,n,r){return n+(t-e)*(r-n)/(i-e)},inverseLerp:function(t,e,i){return t!==e?(i-t)/(e-t):0},lerp:bt,damp:function(t,e,i,n){return bt(t,e,1-Math.exp(-i*n))},pingpong:function(t,e=1){return e-Math.abs(Mt(t,2*e)-e)},smoothstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*(3-2*t)},smootherstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(gt=t);let e=gt+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*vt},radToDeg:function(t){return t*xt},isPowerOfTwo:wt,ceilPowerOfTwo:St,floorPowerOfTwo:Tt,setQuaternionFromProperEuler:function(t,e,i,n,r){const s=Math.cos,a=Math.sin,o=s(i/2),l=a(i/2),c=s((e+n)/2),h=a((e+n)/2),u=s((e-n)/2),d=a((e-n)/2),p=s((n-e)/2),m=a((n-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}},normalize:function(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}},denormalize:function(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}});class Et{constructor(t=0,e=0){Et.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this)}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this)}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,i=this.y,n=t.elements;return this.x=n[0]*e+n[3]*i+n[6],this.y=n[1]*e+n[4]*i+n[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const i=this.length();return this.divideScalar(i||1).multiplyScalar(Math.max(t,Math.min(e,i)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,i=this.y-t.y;return e*e+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,i){return this.x=t.x+(e.x-t.x)*i,this.y=t.y+(e.y-t.y)*i,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e,i){return void 0!==i&&console.warn("THREE.Vector2: offset has been removed from .fromBufferAttribute()."),this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const i=Math.cos(e),n=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*i-s*n+t.x,this.y=r*n+s*i+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class Ct{constructor(){Ct.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.")}set(t,e,i,n,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=n,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=i,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,i=t.elements;return e[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],this}extractBasis(t,e,i){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),i.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const i=t.elements,n=e.elements,r=this.elements,s=i[0],a=i[3],o=i[6],l=i[1],c=i[4],h=i[7],u=i[2],d=i[5],p=i[8],m=n[0],f=n[3],g=n[6],v=n[1],x=n[4],y=n[7],_=n[2],M=n[5],b=n[8];return r[0]=s*m+a*v+o*_,r[3]=s*f+a*x+o*M,r[6]=s*g+a*y+o*b,r[1]=l*m+c*v+h*_,r[4]=l*f+c*x+h*M,r[7]=l*g+c*y+h*b,r[2]=u*m+d*v+p*_,r[5]=u*f+d*x+p*M,r[8]=u*g+d*y+p*b,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],i=t[1],n=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-i*r*c+i*a*o+n*r*l-n*s*o}invert(){const t=this.elements,e=t[0],i=t[1],n=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+i*u+n*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(n*l-c*i)*m,t[2]=(a*i-n*s)*m,t[3]=u*m,t[4]=(c*e-n*o)*m,t[5]=(n*r-a*e)*m,t[6]=d*m,t[7]=(i*o-l*e)*m,t[8]=(s*e-i*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,i,n,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(i*o,i*l,-i*(o*s+l*a)+s+t,-n*l,n*o,-n*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){const i=this.elements;return i[0]*=t,i[3]*=t,i[6]*=t,i[1]*=e,i[4]*=e,i[7]*=e,this}rotate(t){const e=Math.cos(t),i=Math.sin(t),n=this.elements,r=n[0],s=n[3],a=n[6],o=n[1],l=n[4],c=n[7];return n[0]=e*r+i*o,n[3]=e*s+i*l,n[6]=e*a+i*c,n[1]=-i*r+e*o,n[4]=-i*s+e*l,n[7]=-i*a+e*c,this}translate(t,e){const i=this.elements;return i[0]+=t*i[2],i[3]+=t*i[5],i[6]+=t*i[8],i[1]+=e*i[2],i[4]+=e*i[5],i[7]+=e*i[8],this}equals(t){const e=this.elements,i=t.elements;for(let t=0;t<9;t++)if(e[t]!==i[t])return!1;return!0}fromArray(t,e=0){for(let i=0;i<9;i++)this.elements[i]=t[i+e];return this}toArray(t=[],e=0){const i=this.elements;return t[e]=i[0],t[e+1]=i[1],t[e+2]=i[2],t[e+3]=i[3],t[e+4]=i[4],t[e+5]=i[5],t[e+6]=i[6],t[e+7]=i[7],t[e+8]=i[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}function Lt(t){for(let e=t.length-1;e>=0;--e)if(t[e]>65535)return!0;return!1}const Rt={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function Pt(t,e){return new Rt[t](e)}function It(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function Dt(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function Nt(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}const Ot={[lt]:{[ct]:Dt},[ct]:{[lt]:Nt}},zt={legacyMode:!0,get workingColorSpace(){return ct},set workingColorSpace(t){console.warn("THREE.ColorManagement: .workingColorSpace is readonly.")},convert:function(t,e,i){if(this.legacyMode||e===i||!e||!i)return t;if(Ot[e]&&void 0!==Ot[e][i]){const n=Ot[e][i];return t.r=n(t.r),t.g=n(t.g),t.b=n(t.b),t}throw new Error("Unsupported color space conversion.")},fromWorkingColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},toWorkingColorSpace:function(t,e){return this.convert(t,e,this.workingColorSpace)}},Ft={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},Bt={r:0,g:0,b:0},Ut={h:0,s:0,l:0},kt={h:0,s:0,l:0};function Gt(t,e,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+6*(e-t)*(2/3-i):t}function Vt(t,e){return e.r=t.r,e.g=t.g,e.b=t.b,e}class Ht{constructor(t,e,i){return this.isColor=!0,this.r=1,this.g=1,this.b=1,void 0===e&&void 0===i?this.set(t):this.setRGB(t,e,i)}set(t){return t&&t.isColor?this.copy(t):"number"==typeof t?this.setHex(t):"string"==typeof t&&this.setStyle(t),this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e="srgb"){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,zt.toWorkingColorSpace(this,e),this}setRGB(t,e,i,n="srgb-linear"){return this.r=t,this.g=e,this.b=i,zt.toWorkingColorSpace(this,n),this}setHSL(t,e,i,n="srgb-linear"){if(t=Mt(t,1),e=_t(e,0,1),i=_t(i,0,1),0===e)this.r=this.g=this.b=i;else{const n=i<=.5?i*(1+e):i+e-i*e,r=2*i-n;this.r=Gt(r,n,t+1/3),this.g=Gt(r,n,t),this.b=Gt(r,n,t-1/3)}return zt.toWorkingColorSpace(this,n),this}setStyle(t,e="srgb"){function i(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let n;if(n=/^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec(t)){let t;const r=n[1],s=n[2];switch(r){case"rgb":case"rgba":if(t=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(s))return this.r=Math.min(255,parseInt(t[1],10))/255,this.g=Math.min(255,parseInt(t[2],10))/255,this.b=Math.min(255,parseInt(t[3],10))/255,zt.toWorkingColorSpace(this,e),i(t[4]),this;if(t=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(s))return this.r=Math.min(100,parseInt(t[1],10))/100,this.g=Math.min(100,parseInt(t[2],10))/100,this.b=Math.min(100,parseInt(t[3],10))/100,zt.toWorkingColorSpace(this,e),i(t[4]),this;break;case"hsl":case"hsla":if(t=/^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(s)){const n=parseFloat(t[1])/360,r=parseInt(t[2],10)/100,s=parseInt(t[3],10)/100;return i(t[4]),this.setHSL(n,r,s,e)}}}else if(n=/^\#([A-Fa-f\d]+)$/.exec(t)){const t=n[1],i=t.length;if(3===i)return this.r=parseInt(t.charAt(0)+t.charAt(0),16)/255,this.g=parseInt(t.charAt(1)+t.charAt(1),16)/255,this.b=parseInt(t.charAt(2)+t.charAt(2),16)/255,zt.toWorkingColorSpace(this,e),this;if(6===i)return this.r=parseInt(t.charAt(0)+t.charAt(1),16)/255,this.g=parseInt(t.charAt(2)+t.charAt(3),16)/255,this.b=parseInt(t.charAt(4)+t.charAt(5),16)/255,zt.toWorkingColorSpace(this,e),this}return t&&t.length>0?this.setColorName(t,e):this}setColorName(t,e="srgb"){const i=Ft[t.toLowerCase()];return void 0!==i?this.setHex(i,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=Dt(t.r),this.g=Dt(t.g),this.b=Dt(t.b),this}copyLinearToSRGB(t){return this.r=Nt(t.r),this.g=Nt(t.g),this.b=Nt(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t="srgb"){return zt.fromWorkingColorSpace(Vt(this,Bt),t),_t(255*Bt.r,0,255)<<16^_t(255*Bt.g,0,255)<<8^_t(255*Bt.b,0,255)<<0}getHexString(t="srgb"){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e="srgb-linear"){zt.fromWorkingColorSpace(Vt(this,Bt),e);const i=Bt.r,n=Bt.g,r=Bt.b,s=Math.max(i,n,r),a=Math.min(i,n,r);let o,l;const c=(a+s)/2;if(a===s)o=0,l=0;else{const t=s-a;switch(l=c<=.5?t/(s+a):t/(2-s-a),s){case i:o=(n-r)/t+(n2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=It("canvas");e.width=t.width,e.height=t.height;const i=e.getContext("2d");i.drawImage(t,0,0,t.width,t.height);const n=i.getImageData(0,0,t.width,t.height),r=n.data;for(let t=0;t1)switch(this.wrapS){case c:t.x=t.x-Math.floor(t.x);break;case h:t.x=t.x<0?0:1;break;case u:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case c:t.y=t.y-Math.floor(t.y);break;case h:t.y=t.y<0?0:1;break;case u:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}}Zt.DEFAULT_IMAGE=null,Zt.DEFAULT_MAPPING=n;class Jt{constructor(t=0,e=0,i=0,n=1){Jt.prototype.isVector4=!0,this.x=t,this.y=e,this.z=i,this.w=n}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,i,n){return this.x=t,this.y=e,this.z=i,this.w=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this)}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this)}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,i=this.y,n=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*i+s[8]*n+s[12]*r,this.y=s[1]*e+s[5]*i+s[9]*n+s[13]*r,this.z=s[2]*e+s[6]*i+s[10]*n+s[14]*r,this.w=s[3]*e+s[7]*i+s[11]*n+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,i,n,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o=0?1:-1,n=1-e*e;if(n>Number.EPSILON){const r=Math.sqrt(n),s=Math.atan2(r,e*i);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*i;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,i,n,r,s){const a=i[n],o=i[n+1],l=i[n+2],c=i[n+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,i,n){return this._x=t,this._y=e,this._z=i,this._w=n,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e){if(!t||!t.isEuler)throw new Error("THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.");const i=t._x,n=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(i/2),c=a(n/2),h=a(r/2),u=o(i/2),d=o(n/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!1!==e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const i=e/2,n=Math.sin(i);return this._x=t.x*n,this._y=t.y*n,this._z=t.z*n,this._w=Math.cos(i),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,i=e[0],n=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=i+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-n)*t}else if(i>a&&i>h){const t=2*Math.sqrt(1+i-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(n+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-i-h);this._w=(r-l)/t,this._x=(n+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-i-a);this._w=(s-n)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let i=t.dot(e)+1;return iMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=i):(this._x=0,this._y=-t.z,this._z=t.y,this._w=i)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=i),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(_t(this.dot(t),-1,1)))}rotateTowards(t,e){const i=this.angleTo(t);if(0===i)return this;const n=Math.min(1,e/i);return this.slerp(t,n),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t,e){return void 0!==e?(console.warn("THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(t,e)):this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const i=t._x,n=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=i*c+s*a+n*l-r*o,this._y=n*c+s*o+r*a-i*l,this._z=r*c+s*l+i*o-n*a,this._w=s*c-i*a-n*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const i=this._x,n=this._y,r=this._z,s=this._w;let a=s*t._w+i*t._x+n*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=i,this._y=n,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*i+e*this._x,this._y=t*n+e*this._y,this._z=t*r+e*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=i*h+this._x*u,this._y=n*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,i){return this.copy(t).slerp(e,i)}random(){const t=Math.random(),e=Math.sqrt(1-t),i=Math.sqrt(t),n=2*Math.PI*Math.random(),r=2*Math.PI*Math.random();return this.set(e*Math.cos(n),i*Math.sin(r),i*Math.cos(r),e*Math.sin(n))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class ee{constructor(t=0,e=0,i=0){ee.prototype.isVector3=!0,this.x=t,this.y=e,this.z=i}set(t,e,i){return void 0===i&&(i=this.z),this.x=t,this.y=e,this.z=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this.z+=t.z,this)}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this.z-=t.z,this)}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t,e){return void 0!==e?(console.warn("THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(t,e)):(this.x*=t.x,this.y*=t.y,this.z*=t.z,this)}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return t&&t.isEuler||console.error("THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order."),this.applyQuaternion(ne.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(ne.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,i=this.y,n=this.z,r=t.elements;return this.x=r[0]*e+r[3]*i+r[6]*n,this.y=r[1]*e+r[4]*i+r[7]*n,this.z=r[2]*e+r[5]*i+r[8]*n,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,i=this.y,n=this.z,r=t.elements,s=1/(r[3]*e+r[7]*i+r[11]*n+r[15]);return this.x=(r[0]*e+r[4]*i+r[8]*n+r[12])*s,this.y=(r[1]*e+r[5]*i+r[9]*n+r[13])*s,this.z=(r[2]*e+r[6]*i+r[10]*n+r[14])*s,this}applyQuaternion(t){const e=this.x,i=this.y,n=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=o*e+s*n-a*i,c=o*i+a*e-r*n,h=o*n+r*i-s*e,u=-r*e-s*i-a*n;return this.x=l*o+u*-r+c*-a-h*-s,this.y=c*o+u*-s+h*-r-l*-a,this.z=h*o+u*-a+l*-s-c*-r,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,i=this.y,n=this.z,r=t.elements;return this.x=r[0]*e+r[4]*i+r[8]*n,this.y=r[1]*e+r[5]*i+r[9]*n,this.z=r[2]*e+r[6]*i+r[10]*n,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const i=this.length();return this.divideScalar(i||1).multiplyScalar(Math.max(t,Math.min(e,i)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,i){return this.x=t.x+(e.x-t.x)*i,this.y=t.y+(e.y-t.y)*i,this.z=t.z+(e.z-t.z)*i,this}cross(t,e){return void 0!==e?(console.warn("THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(t,e)):this.crossVectors(this,t)}crossVectors(t,e){const i=t.x,n=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=n*o-r*a,this.y=r*s-i*o,this.z=i*a-n*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const i=t.dot(this)/e;return this.copy(t).multiplyScalar(i)}projectOnPlane(t){return ie.copy(this).projectOnVector(t),this.sub(ie)}reflect(t){return this.sub(ie.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const i=this.dot(t)/e;return Math.acos(_t(i,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,i=this.y-t.y,n=this.z-t.z;return e*e+i*i+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,i){const n=Math.sin(e)*t;return this.x=n*Math.sin(i),this.y=Math.cos(e)*t,this.z=n*Math.cos(i),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,i){return this.x=t*Math.sin(e),this.y=i,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),i=this.setFromMatrixColumn(t,1).length(),n=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=i,this.z=n,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e,i){return void 0!==i&&console.warn("THREE.Vector3: offset has been removed from .fromBufferAttribute()."),this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=2*(Math.random()-.5),e=Math.random()*Math.PI*2,i=Math.sqrt(1-t**2);return this.x=i*Math.cos(e),this.y=i*Math.sin(e),this.z=t,this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const ie=new ee,ne=new te;class re{constructor(t=new ee(1/0,1/0,1/0),e=new ee(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){let e=1/0,i=1/0,n=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.length;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,i,n),this.max.set(r,s,a),this}setFromBufferAttribute(t){let e=1/0,i=1/0,n=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.count;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,i,n),this.max.set(r,s,a),this}setFromPoints(t){this.makeEmpty();for(let e=0,i=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,ae),ae.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,i;return t.normal.x>0?(e=t.normal.x*this.min.x,i=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,i=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,i+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,i+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,i+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,i+=t.normal.z*this.min.z),e<=-t.constant&&i>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(me),fe.subVectors(this.max,me),le.subVectors(t.a,me),ce.subVectors(t.b,me),he.subVectors(t.c,me),ue.subVectors(ce,le),de.subVectors(he,ce),pe.subVectors(le,he);let e=[0,-ue.z,ue.y,0,-de.z,de.y,0,-pe.z,pe.y,ue.z,0,-ue.x,de.z,0,-de.x,pe.z,0,-pe.x,-ue.y,ue.x,0,-de.y,de.x,0,-pe.y,pe.x,0];return!!xe(e,le,ce,he,fe)&&(e=[1,0,0,0,1,0,0,0,1],!!xe(e,le,ce,he,fe)&&(ge.crossVectors(ue,de),e=[ge.x,ge.y,ge.z],xe(e,le,ce,he,fe)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return ae.copy(t).clamp(this.min,this.max).sub(t).length()}getBoundingSphere(t){return this.getCenter(t.center),t.radius=.5*this.getSize(ae).length(),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(se[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),se[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),se[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),se[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),se[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),se[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),se[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),se[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(se)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const se=[new ee,new ee,new ee,new ee,new ee,new ee,new ee,new ee],ae=new ee,oe=new re,le=new ee,ce=new ee,he=new ee,ue=new ee,de=new ee,pe=new ee,me=new ee,fe=new ee,ge=new ee,ve=new ee;function xe(t,e,i,n,r){for(let s=0,a=t.length-3;s<=a;s+=3){ve.fromArray(t,s);const a=r.x*Math.abs(ve.x)+r.y*Math.abs(ve.y)+r.z*Math.abs(ve.z),o=e.dot(ve),l=i.dot(ve),c=n.dot(ve);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const ye=new re,_e=new ee,Me=new ee,be=new ee;class we{constructor(t=new ee,e=-1){this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const i=this.center;void 0!==e?i.copy(e):ye.setFromPoints(t).getCenter(i);let n=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){be.subVectors(t,this.center);const e=be.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),i=.5*(t-this.radius);this.center.add(be.multiplyScalar(i/t)),this.radius+=i}return this}union(t){return!0===this.center.equals(t.center)?Me.set(0,0,1).multiplyScalar(t.radius):Me.subVectors(t.center,this.center).normalize().multiplyScalar(t.radius),this.expandByPoint(_e.copy(t.center).add(Me)),this.expandByPoint(_e.copy(t.center).sub(Me)),this}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const Se=new ee,Te=new ee,Ae=new ee,Ee=new ee,Ce=new ee,Le=new ee,Re=new ee;class Pe{constructor(t=new ee,e=new ee(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.direction).multiplyScalar(t).add(this.origin)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Se)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const i=e.dot(this.direction);return i<0?e.copy(this.origin):e.copy(this.direction).multiplyScalar(i).add(this.origin)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Se.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Se.copy(this.direction).multiplyScalar(e).add(this.origin),Se.distanceToSquared(t))}distanceSqToSegment(t,e,i,n){Te.copy(t).add(e).multiplyScalar(.5),Ae.copy(e).sub(t).normalize(),Ee.copy(this.origin).sub(Te);const r=.5*t.distanceTo(e),s=-this.direction.dot(Ae),a=Ee.dot(this.direction),o=-Ee.dot(Ae),l=Ee.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return i&&i.copy(this.direction).multiplyScalar(h).add(this.origin),n&&n.copy(Ae).multiplyScalar(u).add(Te),d}intersectSphere(t,e){Se.subVectors(t.center,this.origin);const i=Se.dot(this.direction),n=Se.dot(Se)-i*i,r=t.radius*t.radius;if(n>r)return null;const s=Math.sqrt(r-n),a=i-s,o=i+s;return a<0&&o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const i=-(this.origin.dot(t.normal)+t.constant)/e;return i>=0?i:null}intersectPlane(t,e){const i=this.distanceToPlane(t);return null===i?null:this.at(i,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let i,n,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(i=(t.min.x-u.x)*l,n=(t.max.x-u.x)*l):(i=(t.max.x-u.x)*l,n=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),i>s||r>n?null:((r>i||i!=i)&&(i=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),i>o||a>n?null:((a>i||i!=i)&&(i=a),(o=0?i:n,e)))}intersectsBox(t){return null!==this.intersectBox(t,Se)}intersectTriangle(t,e,i,n,r){Ce.subVectors(e,t),Le.subVectors(i,t),Re.crossVectors(Ce,Le);let s,a=this.direction.dot(Re);if(a>0){if(n)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}Ee.subVectors(this.origin,t);const o=s*this.direction.dot(Le.crossVectors(Ee,Le));if(o<0)return null;const l=s*this.direction.dot(Ce.cross(Ee));if(l<0)return null;if(o+l>a)return null;const c=-s*Ee.dot(Re);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class Ie{constructor(){Ie.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.")}set(t,e,i,n,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=i,g[12]=n,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new Ie).fromArray(this.elements)}copy(t){const e=this.elements,i=t.elements;return e[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],e[9]=i[9],e[10]=i[10],e[11]=i[11],e[12]=i[12],e[13]=i[13],e[14]=i[14],e[15]=i[15],this}copyPosition(t){const e=this.elements,i=t.elements;return e[12]=i[12],e[13]=i[13],e[14]=i[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,i){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),i.setFromMatrixColumn(this,2),this}makeBasis(t,e,i){return this.set(t.x,e.x,i.x,0,t.y,e.y,i.y,0,t.z,e.z,i.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,i=t.elements,n=1/De.setFromMatrixColumn(t,0).length(),r=1/De.setFromMatrixColumn(t,1).length(),s=1/De.setFromMatrixColumn(t,2).length();return e[0]=i[0]*n,e[1]=i[1]*n,e[2]=i[2]*n,e[3]=0,e[4]=i[4]*r,e[5]=i[5]*r,e[6]=i[6]*r,e[7]=0,e[8]=i[8]*s,e[9]=i[9]*s,e[10]=i[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){t&&t.isEuler||console.error("THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.");const e=this.elements,i=t.x,n=t.y,r=t.z,s=Math.cos(i),a=Math.sin(i),o=Math.cos(n),l=Math.sin(n),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,i=s*h,n=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=i+n*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=n+i*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,i=o*h,n=l*c,r=l*h;e[0]=t+r*a,e[4]=n*a-i,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=i*a-n,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,i=o*h,n=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=n+i*a,e[1]=i+n*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,i=s*h,n=a*c,r=a*h;e[0]=o*c,e[4]=n*l-i,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=i*l-n,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,i=s*l,n=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=n*h+i,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=i*h+n,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,i=s*l,n=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=i*h-n,e[2]=n*h-i,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(Oe,t,ze)}lookAt(t,e,i){const n=this.elements;return Ue.subVectors(t,e),0===Ue.lengthSq()&&(Ue.z=1),Ue.normalize(),Fe.crossVectors(i,Ue),0===Fe.lengthSq()&&(1===Math.abs(i.z)?Ue.x+=1e-4:Ue.z+=1e-4,Ue.normalize(),Fe.crossVectors(i,Ue)),Fe.normalize(),Be.crossVectors(Ue,Fe),n[0]=Fe.x,n[4]=Be.x,n[8]=Ue.x,n[1]=Fe.y,n[5]=Be.y,n[9]=Ue.y,n[2]=Fe.z,n[6]=Be.z,n[10]=Ue.z,this}multiply(t,e){return void 0!==e?(console.warn("THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead."),this.multiplyMatrices(t,e)):this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const i=t.elements,n=e.elements,r=this.elements,s=i[0],a=i[4],o=i[8],l=i[12],c=i[1],h=i[5],u=i[9],d=i[13],p=i[2],m=i[6],f=i[10],g=i[14],v=i[3],x=i[7],y=i[11],_=i[15],M=n[0],b=n[4],w=n[8],S=n[12],T=n[1],A=n[5],E=n[9],C=n[13],L=n[2],R=n[6],P=n[10],I=n[14],D=n[3],N=n[7],O=n[11],z=n[15];return r[0]=s*M+a*T+o*L+l*D,r[4]=s*b+a*A+o*R+l*N,r[8]=s*w+a*E+o*P+l*O,r[12]=s*S+a*C+o*I+l*z,r[1]=c*M+h*T+u*L+d*D,r[5]=c*b+h*A+u*R+d*N,r[9]=c*w+h*E+u*P+d*O,r[13]=c*S+h*C+u*I+d*z,r[2]=p*M+m*T+f*L+g*D,r[6]=p*b+m*A+f*R+g*N,r[10]=p*w+m*E+f*P+g*O,r[14]=p*S+m*C+f*I+g*z,r[3]=v*M+x*T+y*L+_*D,r[7]=v*b+x*A+y*R+_*N,r[11]=v*w+x*E+y*P+_*O,r[15]=v*S+x*C+y*I+_*z,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],i=t[4],n=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-n*l*h-r*a*u+i*l*u+n*a*d-i*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-n*s*d+n*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+i*s*d+r*a*c-i*l*c)+t[15]*(-n*a*c-e*o*h+e*a*u+n*s*h-i*s*u+i*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,i){const n=this.elements;return t.isVector3?(n[12]=t.x,n[13]=t.y,n[14]=t.z):(n[12]=t,n[13]=e,n[14]=i),this}invert(){const t=this.elements,e=t[0],i=t[1],n=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,x=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,y=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,_=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,M=e*v+i*x+n*y+r*_;if(0===M)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const b=1/M;return t[0]=v*b,t[1]=(m*u*r-h*f*r-m*n*d+i*f*d+h*n*g-i*u*g)*b,t[2]=(a*f*r-m*o*r+m*n*l-i*f*l-a*n*g+i*o*g)*b,t[3]=(h*o*r-a*u*r-h*n*l+i*u*l+a*n*d-i*o*d)*b,t[4]=x*b,t[5]=(c*f*r-p*u*r+p*n*d-e*f*d-c*n*g+e*u*g)*b,t[6]=(p*o*r-s*f*r-p*n*l+e*f*l+s*n*g-e*o*g)*b,t[7]=(s*u*r-c*o*r+c*n*l-e*u*l-s*n*d+e*o*d)*b,t[8]=y*b,t[9]=(p*h*r-c*m*r-p*i*d+e*m*d+c*i*g-e*h*g)*b,t[10]=(s*m*r-p*a*r+p*i*l-e*m*l-s*i*g+e*a*g)*b,t[11]=(c*a*r-s*h*r-c*i*l+e*h*l+s*i*d-e*a*d)*b,t[12]=_*b,t[13]=(c*m*n-p*h*n+p*i*u-e*m*u-c*i*f+e*h*f)*b,t[14]=(p*a*n-s*m*n-p*i*o+e*m*o+s*i*f-e*a*f)*b,t[15]=(s*h*n-c*a*n+c*i*o-e*h*o-s*i*u+e*a*u)*b,this}scale(t){const e=this.elements,i=t.x,n=t.y,r=t.z;return e[0]*=i,e[4]*=n,e[8]*=r,e[1]*=i,e[5]*=n,e[9]*=r,e[2]*=i,e[6]*=n,e[10]*=r,e[3]*=i,e[7]*=n,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],i=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],n=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,i,n))}makeTranslation(t,e,i){return this.set(1,0,0,t,0,1,0,e,0,0,1,i,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),i=Math.sin(t);return this.set(1,0,0,0,0,e,-i,0,0,i,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,0,i,0,0,1,0,0,-i,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,-i,0,0,i,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const i=Math.cos(e),n=Math.sin(e),r=1-i,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+i,l*a-n*o,l*o+n*a,0,l*a+n*o,c*a+i,c*o-n*s,0,l*o-n*a,c*o+n*s,r*o*o+i,0,0,0,0,1),this}makeScale(t,e,i){return this.set(t,0,0,0,0,e,0,0,0,0,i,0,0,0,0,1),this}makeShear(t,e,i,n,r,s){return this.set(1,i,r,0,t,1,s,0,e,n,1,0,0,0,0,1),this}compose(t,e,i){const n=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,x=o*c,y=o*h,_=i.x,M=i.y,b=i.z;return n[0]=(1-(m+g))*_,n[1]=(d+y)*_,n[2]=(p-x)*_,n[3]=0,n[4]=(d-y)*M,n[5]=(1-(u+g))*M,n[6]=(f+v)*M,n[7]=0,n[8]=(p+x)*b,n[9]=(f-v)*b,n[10]=(1-(u+m))*b,n[11]=0,n[12]=t.x,n[13]=t.y,n[14]=t.z,n[15]=1,this}decompose(t,e,i){const n=this.elements;let r=De.set(n[0],n[1],n[2]).length();const s=De.set(n[4],n[5],n[6]).length(),a=De.set(n[8],n[9],n[10]).length();this.determinant()<0&&(r=-r),t.x=n[12],t.y=n[13],t.z=n[14],Ne.copy(this);const o=1/r,l=1/s,c=1/a;return Ne.elements[0]*=o,Ne.elements[1]*=o,Ne.elements[2]*=o,Ne.elements[4]*=l,Ne.elements[5]*=l,Ne.elements[6]*=l,Ne.elements[8]*=c,Ne.elements[9]*=c,Ne.elements[10]*=c,e.setFromRotationMatrix(Ne),i.x=r,i.y=s,i.z=a,this}makePerspective(t,e,i,n,r,s){void 0===s&&console.warn("THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.");const a=this.elements,o=2*r/(e-t),l=2*r/(i-n),c=(e+t)/(e-t),h=(i+n)/(i-n),u=-(s+r)/(s-r),d=-2*s*r/(s-r);return a[0]=o,a[4]=0,a[8]=c,a[12]=0,a[1]=0,a[5]=l,a[9]=h,a[13]=0,a[2]=0,a[6]=0,a[10]=u,a[14]=d,a[3]=0,a[7]=0,a[11]=-1,a[15]=0,this}makeOrthographic(t,e,i,n,r,s){const a=this.elements,o=1/(e-t),l=1/(i-n),c=1/(s-r),h=(e+t)*o,u=(i+n)*l,d=(s+r)*c;return a[0]=2*o,a[4]=0,a[8]=0,a[12]=-h,a[1]=0,a[5]=2*l,a[9]=0,a[13]=-u,a[2]=0,a[6]=0,a[10]=-2*c,a[14]=-d,a[3]=0,a[7]=0,a[11]=0,a[15]=1,this}equals(t){const e=this.elements,i=t.elements;for(let t=0;t<16;t++)if(e[t]!==i[t])return!1;return!0}fromArray(t,e=0){for(let i=0;i<16;i++)this.elements[i]=t[i+e];return this}toArray(t=[],e=0){const i=this.elements;return t[e]=i[0],t[e+1]=i[1],t[e+2]=i[2],t[e+3]=i[3],t[e+4]=i[4],t[e+5]=i[5],t[e+6]=i[6],t[e+7]=i[7],t[e+8]=i[8],t[e+9]=i[9],t[e+10]=i[10],t[e+11]=i[11],t[e+12]=i[12],t[e+13]=i[13],t[e+14]=i[14],t[e+15]=i[15],t}}const De=new ee,Ne=new Ie,Oe=new ee(0,0,0),ze=new ee(1,1,1),Fe=new ee,Be=new ee,Ue=new ee,ke=new Ie,Ge=new te;class Ve{constructor(t=0,e=0,i=0,n=Ve.DefaultOrder){this.isEuler=!0,this._x=t,this._y=e,this._z=i,this._order=n}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,i,n=this._order){return this._x=t,this._y=e,this._z=i,this._order=n,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,i=!0){const n=t.elements,r=n[0],s=n[4],a=n[8],o=n[1],l=n[5],c=n[9],h=n[2],u=n[6],d=n[10];switch(e){case"XYZ":this._y=Math.asin(_t(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-s,r)):(this._x=Math.atan2(u,l),this._z=0);break;case"YXZ":this._x=Math.asin(-_t(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,l)):(this._y=Math.atan2(-h,r),this._z=0);break;case"ZXY":this._x=Math.asin(_t(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-h,d),this._z=Math.atan2(-s,l)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-_t(h,-1,1)),Math.abs(h)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-s,l));break;case"YZX":this._z=Math.asin(_t(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-h,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-_t(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(u,l),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===i&&this._onChangeCallback(),this}setFromQuaternion(t,e,i){return ke.makeRotationFromQuaternion(t),this.setFromRotationMatrix(ke,e,i)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return Ge.setFromEuler(this),this.setFromQuaternion(Ge,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}toVector3(){console.error("THREE.Euler: .toVector3() has been removed. Use Vector3.setFromEuler() instead")}}Ve.DefaultOrder="XYZ",Ve.RotationOrders=["XYZ","YZX","ZXY","XZY","YXZ","ZYX"];class He{constructor(){this.mask=1}set(t){this.mask=(1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0){n.children=[];for(let e=0;e0){n.animations=[];for(let e=0;e0&&(i.geometries=e),n.length>0&&(i.materials=n),r.length>0&&(i.textures=r),a.length>0&&(i.images=a),o.length>0&&(i.shapes=o),l.length>0&&(i.skeletons=l),c.length>0&&(i.animations=c),h.length>0&&(i.nodes=h)}return i.object=n,i;function s(t){const e=[];for(const i in t){const n=t[i];delete n.metadata,e.push(n)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?n.multiplyScalar(1/Math.sqrt(r)):n.set(0,0,0)}static getBarycoord(t,e,i,n,r){ri.subVectors(n,e),si.subVectors(i,e),ai.subVectors(t,e);const s=ri.dot(ri),a=ri.dot(si),o=ri.dot(ai),l=si.dot(si),c=si.dot(ai),h=s*l-a*a;if(0===h)return r.set(-2,-1,-1);const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,i,n){return this.getBarycoord(t,e,i,n,oi),oi.x>=0&&oi.y>=0&&oi.x+oi.y<=1}static getUV(t,e,i,n,r,s,a,o){return this.getBarycoord(t,e,i,n,oi),o.set(0,0),o.addScaledVector(r,oi.x),o.addScaledVector(s,oi.y),o.addScaledVector(a,oi.z),o}static isFrontFacing(t,e,i,n){return ri.subVectors(i,e),si.subVectors(t,e),ri.cross(si).dot(n)<0}set(t,e,i){return this.a.copy(t),this.b.copy(e),this.c.copy(i),this}setFromPointsAndIndices(t,e,i,n){return this.a.copy(t[e]),this.b.copy(t[i]),this.c.copy(t[n]),this}setFromAttributeAndIndices(t,e,i,n){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,i),this.c.fromBufferAttribute(t,n),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return ri.subVectors(this.c,this.b),si.subVectors(this.a,this.b),.5*ri.cross(si).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return mi.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return mi.getBarycoord(t,this.a,this.b,this.c,e)}getUV(t,e,i,n,r){return mi.getUV(t,this.a,this.b,this.c,e,i,n,r)}containsPoint(t){return mi.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return mi.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const i=this.a,n=this.b,r=this.c;let s,a;li.subVectors(n,i),ci.subVectors(r,i),ui.subVectors(t,i);const o=li.dot(ui),l=ci.dot(ui);if(o<=0&&l<=0)return e.copy(i);di.subVectors(t,n);const c=li.dot(di),h=ci.dot(di);if(c>=0&&h<=c)return e.copy(n);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(i).addScaledVector(li,s);pi.subVectors(t,r);const d=li.dot(pi),p=ci.dot(pi);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(i).addScaledVector(ci,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return hi.subVectors(r,n),a=(h-c)/(h-c+(d-p)),e.copy(n).addScaledVector(hi,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(i).addScaledVector(li,s).addScaledVector(ci,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}let fi=0;class gi extends mt{constructor(){super(),this.isMaterial=!0,Object.defineProperty(this,"id",{value:fi++}),this.uuid=yt(),this.name="",this.type="Material",this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=i,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=ht,this.stencilZFail=ht,this.stencilZPass=ht,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaToCoverage=!1,this.premultipliedAlpha=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0,this._alphaTest=0}get alphaTest(){return this._alphaTest}set alphaTest(t){this._alphaTest>0!=t>0&&this.version++,this._alphaTest=t}onBuild(){}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const i=t[e];if(void 0===i){console.warn("THREE.Material: '"+e+"' parameter is undefined.");continue}if("shading"===e){console.warn("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead."),this.flatShading=1===i;continue}const n=this[e];void 0!==n?n&&n.isColor?n.set(i):n&&n.isVector3&&i&&i.isVector3?n.copy(i):this[e]=i:console.warn("THREE."+this.type+": '"+e+"' is not a property of this material.")}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const i={metadata:{version:4.5,type:"Material",generator:"Material.toJSON"}};function n(t){const e=[];for(const i in t){const n=t[i];delete n.metadata,e.push(n)}return e}if(i.uuid=this.uuid,i.type=this.type,""!==this.name&&(i.name=this.name),this.color&&this.color.isColor&&(i.color=this.color.getHex()),void 0!==this.roughness&&(i.roughness=this.roughness),void 0!==this.metalness&&(i.metalness=this.metalness),void 0!==this.sheen&&(i.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(i.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(i.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(i.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(i.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(i.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(i.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(i.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(i.shininess=this.shininess),void 0!==this.clearcoat&&(i.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(i.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(i.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(i.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(i.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,i.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.iridescence&&(i.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(i.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(i.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(i.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(i.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(i.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(i.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(i.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(i.lightMap=this.lightMap.toJSON(t).uuid,i.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(i.aoMap=this.aoMap.toJSON(t).uuid,i.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(i.bumpMap=this.bumpMap.toJSON(t).uuid,i.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(i.normalMap=this.normalMap.toJSON(t).uuid,i.normalMapType=this.normalMapType,i.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(i.displacementMap=this.displacementMap.toJSON(t).uuid,i.displacementScale=this.displacementScale,i.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(i.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(i.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(i.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(i.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(i.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(i.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(i.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(i.combine=this.combine)),void 0!==this.envMapIntensity&&(i.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(i.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(i.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(i.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(i.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(i.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(i.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(i.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&(i.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(i.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(i.size=this.size),null!==this.shadowSide&&(i.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(i.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(i.blending=this.blending),0!==this.side&&(i.side=this.side),this.vertexColors&&(i.vertexColors=!0),this.opacity<1&&(i.opacity=this.opacity),!0===this.transparent&&(i.transparent=this.transparent),i.depthFunc=this.depthFunc,i.depthTest=this.depthTest,i.depthWrite=this.depthWrite,i.colorWrite=this.colorWrite,i.stencilWrite=this.stencilWrite,i.stencilWriteMask=this.stencilWriteMask,i.stencilFunc=this.stencilFunc,i.stencilRef=this.stencilRef,i.stencilFuncMask=this.stencilFuncMask,i.stencilFail=this.stencilFail,i.stencilZFail=this.stencilZFail,i.stencilZPass=this.stencilZPass,void 0!==this.rotation&&0!==this.rotation&&(i.rotation=this.rotation),!0===this.polygonOffset&&(i.polygonOffset=!0),0!==this.polygonOffsetFactor&&(i.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(i.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(i.linewidth=this.linewidth),void 0!==this.dashSize&&(i.dashSize=this.dashSize),void 0!==this.gapSize&&(i.gapSize=this.gapSize),void 0!==this.scale&&(i.scale=this.scale),!0===this.dithering&&(i.dithering=!0),this.alphaTest>0&&(i.alphaTest=this.alphaTest),!0===this.alphaToCoverage&&(i.alphaToCoverage=this.alphaToCoverage),!0===this.premultipliedAlpha&&(i.premultipliedAlpha=this.premultipliedAlpha),!0===this.wireframe&&(i.wireframe=this.wireframe),this.wireframeLinewidth>1&&(i.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(i.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(i.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(i.flatShading=this.flatShading),!1===this.visible&&(i.visible=!1),!1===this.toneMapped&&(i.toneMapped=!1),!1===this.fog&&(i.fog=!1),"{}"!==JSON.stringify(this.userData)&&(i.userData=this.userData),e){const e=n(t.textures),r=n(t.images);e.length>0&&(i.textures=e),r.length>0&&(i.images=r)}return i}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let i=null;if(null!==e){const t=e.length;i=new Array(t);for(let n=0;n!==t;++n)i[n]=e[n].clone()}return this.clippingPlanes=i,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}class vi extends gi{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new Ht(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const xi=new ee,yi=new Et;class _i{constructor(t,e,i){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=!0===i,this.usage=ut,this.updateRange={offset:0,count:-1},this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this}copyAt(t,e,i){t*=this.itemSize,i*=e.itemSize;for(let n=0,r=this.itemSize;n0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const i in e)void 0!==e[i]&&(t[i]=e[i]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const i=this.attributes;for(const e in i){const n=i[e];t.data.attributes[e]=n.toJSON(t.data)}const n={};let r=!1;for(const e in this.morphAttributes){const i=this.morphAttributes[e],s=[];for(let e=0,n=i.length;e0&&(n[e]=s,r=!0)}r&&(t.data.morphAttributes=n,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const i=t.index;null!==i&&this.setIndex(i.clone(e));const n=t.attributes;for(const t in n){const i=n[t];this.setAttribute(t,i.clone(e))}const r=t.morphAttributes;for(const t in r){const i=[],n=r[t];for(let t=0,r=n.length;t0){const i=t[e[0]];if(void 0!==i){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=i.length;ti.far?null:{distance:c,point:Yi.clone(),object:t}}(t,e,i,n,Oi,zi,Fi,Xi);if(p){o&&(Wi.fromBufferAttribute(o,c),ji.fromBufferAttribute(o,h),qi.fromBufferAttribute(o,u),p.uv=mi.getUV(Xi,Oi,zi,Fi,Wi,ji,qi,new Et)),l&&(Wi.fromBufferAttribute(l,c),ji.fromBufferAttribute(l,h),qi.fromBufferAttribute(l,u),p.uv2=mi.getUV(Xi,Oi,zi,Fi,Wi,ji,qi,new Et));const t={a:c,b:h,c:u,normal:new ee,materialIndex:0};mi.getNormal(Oi,zi,Fi,t.normal),p.face=t}return p}class Ki extends Pi{constructor(t=1,e=1,i=1,n=1,r=1,s=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:i,widthSegments:n,heightSegments:r,depthSegments:s};const a=this;n=Math.floor(n),r=Math.floor(r),s=Math.floor(s);const o=[],l=[],c=[],h=[];let u=0,d=0;function p(t,e,i,n,r,s,p,m,f,g,v){const x=s/f,y=p/g,_=s/2,M=p/2,b=m/2,w=f+1,S=g+1;let T=0,A=0;const E=new ee;for(let s=0;s0?1:-1,c.push(E.x,E.y,E.z),h.push(o/f),h.push(1-s/g),T+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader;const i={};for(const t in this.extensions)!0===this.extensions[t]&&(i[t]=!0);return Object.keys(i).length>0&&(e.extensions=i),e}}class nn extends ni{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new Ie,this.projectionMatrix=new Ie,this.projectionMatrixInverse=new Ie}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(-e[8],-e[9],-e[10]).normalize()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}class rn extends nn{constructor(t=50,e=1,i=.1,n=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=i,this.far=n,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*xt*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*vt*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*xt*Math.atan(Math.tan(.5*vt*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}setViewOffset(t,e,i,n,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=i,this.view.offsetY=n,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*vt*this.fov)/this.zoom,i=2*e,n=this.aspect*i,r=-.5*n;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*n/t,e-=s.offsetY*i/a,n*=s.width/t,i*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+n,e,e-i,t,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const sn=90;class an extends ni{constructor(t,e,i){if(super(),this.type="CubeCamera",!0!==i.isWebGLCubeRenderTarget)return void console.error("THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.");this.renderTarget=i;const n=new rn(sn,1,t,e);n.layers=this.layers,n.up.set(0,-1,0),n.lookAt(new ee(1,0,0)),this.add(n);const r=new rn(sn,1,t,e);r.layers=this.layers,r.up.set(0,-1,0),r.lookAt(new ee(-1,0,0)),this.add(r);const s=new rn(sn,1,t,e);s.layers=this.layers,s.up.set(0,0,1),s.lookAt(new ee(0,1,0)),this.add(s);const a=new rn(sn,1,t,e);a.layers=this.layers,a.up.set(0,0,-1),a.lookAt(new ee(0,-1,0)),this.add(a);const o=new rn(sn,1,t,e);o.layers=this.layers,o.up.set(0,-1,0),o.lookAt(new ee(0,0,1)),this.add(o);const l=new rn(sn,1,t,e);l.layers=this.layers,l.up.set(0,-1,0),l.lookAt(new ee(0,0,-1)),this.add(l)}update(t,e){null===this.parent&&this.updateMatrixWorld();const i=this.renderTarget,[n,r,s,a,o,l]=this.children,c=t.getRenderTarget(),h=t.toneMapping,u=t.xr.enabled;t.toneMapping=0,t.xr.enabled=!1;const d=i.texture.generateMipmaps;i.texture.generateMipmaps=!1,t.setRenderTarget(i,0),t.render(e,n),t.setRenderTarget(i,1),t.render(e,r),t.setRenderTarget(i,2),t.render(e,s),t.setRenderTarget(i,3),t.render(e,a),t.setRenderTarget(i,4),t.render(e,o),i.texture.generateMipmaps=d,t.setRenderTarget(i,5),t.render(e,l),t.setRenderTarget(c),t.toneMapping=h,t.xr.enabled=u,i.texture.needsPMREMUpdate=!0}}class on extends Zt{constructor(t,e,i,n,s,a,o,l,c,h){super(t=void 0!==t?t:[],e=void 0!==e?e:r,i,n,s,a,o,l,c,h),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class ln extends Kt{constructor(t,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const i={width:t,height:t,depth:1},n=[i,i,i,i,i,i];this.texture=new on(n,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.encoding),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:f}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.encoding=e.encoding,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const i={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},n=new Ki(5,5,5),r=new en({name:"CubemapFromEquirect",uniforms:Qi(i.uniforms),vertexShader:i.vertexShader,fragmentShader:i.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const s=new Zi(n,r),a=e.minFilter;e.minFilter===v&&(e.minFilter=f);return new an(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,i,n){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,i,n);t.setRenderTarget(r)}}const cn=new ee,hn=new ee,un=new Ct;class dn{constructor(t=new ee(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,i,n){return this.normal.set(t,e,i),this.constant=n,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,i){const n=cn.subVectors(i,e).cross(hn.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(n,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(this.normal).multiplyScalar(-this.distanceToPoint(t)).add(t)}intersectLine(t,e){const i=t.delta(cn),n=this.normal.dot(i);if(0===n)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/n;return r<0||r>1?null:e.copy(i).multiplyScalar(r).add(t.start)}intersectsLine(t){const e=this.distanceToPoint(t.start),i=this.distanceToPoint(t.end);return e<0&&i>0||i<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const i=e||un.getNormalMatrix(t),n=this.coplanarPoint(cn).applyMatrix4(t),r=this.normal.applyMatrix3(i).normalize();return this.constant=-n.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const pn=new we,mn=new ee;class fn{constructor(t=new dn,e=new dn,i=new dn,n=new dn,r=new dn,s=new dn){this.planes=[t,e,i,n,r,s]}set(t,e,i,n,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(i),a[3].copy(n),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let i=0;i<6;i++)e[i].copy(t.planes[i]);return this}setFromProjectionMatrix(t){const e=this.planes,i=t.elements,n=i[0],r=i[1],s=i[2],a=i[3],o=i[4],l=i[5],c=i[6],h=i[7],u=i[8],d=i[9],p=i[10],m=i[11],f=i[12],g=i[13],v=i[14],x=i[15];return e[0].setComponents(a-n,h-o,m-u,x-f).normalize(),e[1].setComponents(a+n,h+o,m+u,x+f).normalize(),e[2].setComponents(a+r,h+l,m+d,x+g).normalize(),e[3].setComponents(a-r,h-l,m-d,x-g).normalize(),e[4].setComponents(a-s,h-c,m-p,x-v).normalize(),e[5].setComponents(a+s,h+c,m+p,x+v).normalize(),this}intersectsObject(t){const e=t.geometry;return null===e.boundingSphere&&e.computeBoundingSphere(),pn.copy(e.boundingSphere).applyMatrix4(t.matrixWorld),this.intersectsSphere(pn)}intersectsSprite(t){return pn.center.set(0,0,0),pn.radius=.7071067811865476,pn.applyMatrix4(t.matrixWorld),this.intersectsSphere(pn)}intersectsSphere(t){const e=this.planes,i=t.center,n=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(i)0?t.max.x:t.min.x,mn.y=n.normal.y>0?t.max.y:t.min.y,mn.z=n.normal.z>0?t.max.z:t.min.z,n.distanceToPoint(mn)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let i=0;i<6;i++)if(e[i].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function gn(){let t=null,e=!1,i=null,n=null;function r(e,s){i(e,s),n=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==i&&(n=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(n),e=!1},setAnimationLoop:function(t){i=t},setContext:function(e){t=e}}}function vn(t,e){const i=e.isWebGL2,n=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),n.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const i=n.get(e);i&&(t.deleteBuffer(i.buffer),n.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=n.get(e);return void((!t||t.version 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif",iridescence_fragment:"#ifdef USE_IRIDESCENCE\nconst mat3 XYZ_TO_REC709 = mat3(\n\t\t3.2404542, -0.9692660,\t0.0556434,\n\t -1.5371385,\t1.8760108, -0.2040259,\n\t -0.4985314,\t0.0415560,\t1.0572252\n);\nvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t vec3 sqrtF0 = sqrt( fresnel0 );\n\t return ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n}\nvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t return pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n}\nfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t return pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n}\nvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t float phase = 2.0 * PI * OPD * 1.0e-9;\n\t vec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t vec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t vec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t vec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( -pow2( phase ) * var );\n\t xyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[0] ) * exp( -4.5282e+09 * pow2( phase ) );\n\t xyz /= 1.0685e-7;\n\t vec3 srgb = XYZ_TO_REC709 * xyz;\n\t return srgb;\n}\nvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t vec3 I;\n\t float iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t float sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t float cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t if ( cosTheta2Sq < 0.0 ) {\n\t\t\t return vec3( 1.0 );\n\t }\n\t float cosTheta2 = sqrt( cosTheta2Sq );\n\t float R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t float R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t float R21 = R12;\n\t float T121 = 1.0 - R12;\n\t float phi12 = 0.0;\n\t if ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t float phi21 = PI - phi12;\n\t vec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t vec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t vec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t vec3 phi23 = vec3( 0.0 );\n\t if ( baseIOR[0] < iridescenceIOR ) phi23[0] = PI;\n\t if ( baseIOR[1] < iridescenceIOR ) phi23[1] = PI;\n\t if ( baseIOR[2] < iridescenceIOR ) phi23[2] = PI;\n\t float OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t vec3 phi = vec3( phi21 ) + phi23;\n\t vec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t vec3 r123 = sqrt( R123 );\n\t vec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t vec3 C0 = R12 + Rs;\n\t I = C0;\n\t vec3 Cm = Rs - T121;\n\t for ( int m = 1; m <= 2; ++m ) {\n\t\t\t Cm *= r123;\n\t\t\t vec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\t I += Cm * Sm;\n\t }\n\t return max( I, vec3( 0.0 ) );\n}\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\tvec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define r0 1.0\n\t#define v0 0.339\n\t#define m0 - 2.0\n\t#define r1 0.8\n\t#define v1 0.276\n\t#define m1 - 1.0\n\t#define r4 0.4\n\t#define v4 0.046\n\t#define m4 2.0\n\t#define r5 0.305\n\t#define v5 0.016\n\t#define m5 3.0\n\t#define r6 0.21\n\t#define v6 0.0038\n\t#define m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= r1 ) {\n\t\t\tmip = ( r0 - roughness ) * ( m1 - m0 ) / ( r0 - r1 ) + m0;\n\t\t} else if ( roughness >= r4 ) {\n\t\t\tmip = ( r1 - roughness ) * ( m4 - m1 ) / ( r1 - r4 ) + m1;\n\t\t} else if ( roughness >= r5 ) {\n\t\t\tmip = ( r4 - roughness ) * ( m5 - m4 ) / ( r4 - r5 ) + m4;\n\t\t} else if ( roughness >= r6 ) {\n\t\t\tmip = ( r5 - roughness ) * ( m6 - m5 ) / ( r5 - r6 ) + m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 envColor = textureCubeUV( envMap, reflectVec, 0.0 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#if defined( USE_ENVMAP )\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_vertex:"vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\nvIndirectFront += getAmbientLightIrradiance( ambientLightColor );\nvIndirectFront += getLightProbeIrradiance( lightProbe, geometry.normal );\n#ifdef DOUBLE_SIDED\n\tvIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n\tvIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry.normal );\n#endif\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointLightInfo( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotLightInfo( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalLightInfo( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry.normal );\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#else\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material )\t(0)",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\t#ifdef SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULARINTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vUv ).a;\n\t\t#endif\n\t\t#ifdef USE_SPECULARCOLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vUv ).rgb;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( ior - 1.0 ) / ( ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEENCOLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEENROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vUv ).a;\n\t#endif\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3(\t\t0, 1,\t\t0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\treflectedLight.directSpecular += irradiance * BRDF_GGX_Iridescence( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness );\n\t#else\n\t\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\nfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\nif ( material.iridescenceThickness == 0.0 ) {\n\tmaterial.iridescence = 0.0;\n} else {\n\tmaterial.iridescence = saturate( material.iridescence );\n}\nif ( material.iridescence > 0.0 ) {\n\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * faceDirection;\n\t\t\tbitangent = bitangent * faceDirection;\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;",normal_fragment_maps:"#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( - vViewPosition, normal, mapN, faceDirection );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );\n\t\treturn normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN, faceDirection );\n\t#endif\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",output_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= transmissionAlpha + 0.1;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), \n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n\t\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\tvec4 shadowWorldPosition;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3(\t1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108,\t1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605,\t1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tfloat transmissionAlpha = 1.0;\n\tfloat transmissionFactor = transmission;\n\tfloat thicknessFactor = thickness;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\ttransmissionFactor *= texture2D( transmissionMap, vUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tthicknessFactor *= texture2D( thicknessMap, vUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmission = getIBLVolumeRefraction(\n\t\tn, v, roughnessFactor, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, ior, thicknessFactor,\n\t\tattenuationColor, attenuationDistance );\n\ttotalDiffuse = mix( totalDiffuse, transmission.rgb, transmissionFactor );\n\ttransmissionAlpha = mix( transmissionAlpha, transmission.a, transmissionFactor );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat framebufferLod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\t#ifdef texture2DLodEXT\n\t\t\treturn texture2DLodEXT( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#else\n\t\t\treturn texture2D( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#endif\n\t}\n\tvec3 applyVolumeAttenuation( const in vec3 radiance, const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( attenuationDistance == 0.0 ) {\n\t\t\treturn radiance;\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance * radiance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a );\n\t}\n#endif",uv_pars_fragment:"#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif",uv_pars_vertex:"#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif",uv_vertex:"#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif",uv2_pars_fragment:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif",uv2_pars_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif",uv2_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION )\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tgl_FragColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tgl_FragColor = vec4( mix( pow( gl_FragColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), gl_FragColor.rgb * 0.0773993808, vec3( lessThanEqual( gl_FragColor.rgb, vec3( 0.04045 ) ) ) ), gl_FragColor.w );\n\t#endif\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"#include \nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 vReflect = vWorldDirection;\n\t#include \n\tgl_FragColor = envColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include