1
0
mirror of https://github.com/beestat/app.git synced 2025-05-23 18:04:14 -04:00

Floor plan editor early access

This commit is contained in:
Jon Ziebell 2022-08-04 21:32:10 -04:00
parent e5147c316a
commit c1403ae03b
55 changed files with 5732 additions and 283 deletions

20
api/floor_plan.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/**
* Floor plan.
*
* @author Jon Ziebell
*/
class floor_plan extends cora\crud {
public static $exposed = [
'private' => [
'read_id',
'update',
'create',
'delete'
],
'public' => []
];
}

View File

@ -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) {

View File

@ -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; }

Binary file not shown.

Binary file not shown.

View File

@ -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",

View File

@ -80,6 +80,8 @@ window.addEventListener('resize', rocket.throttle(100, function() {
beestat.dispatcher.dispatchEvent('breakpoint');
}
});
beestat.dispatcher.dispatchEvent('resize');
}));
// First run

View File

@ -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.');

View File

@ -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);
}
});

View File

@ -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.

View File

@ -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;
};

View File

@ -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.
*

View File

@ -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);
};

View File

@ -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();
}));

View File

@ -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.'));
};

View File

@ -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/???');
}));
};

View File

@ -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();
}));

View File

@ -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();
}));

View File

@ -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();
}));

View File

@ -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 =

View File

@ -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';
};

788
js/component/floor_plan.js Normal file
View File

@ -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;
};

View File

@ -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_;
};

View File

@ -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;
};

View File

@ -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};
};*/

View File

@ -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;
};

View File

@ -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')

View File

@ -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;
};

View File

@ -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;
};

127
js/component/input/radio.js Normal file
View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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'));

View File

@ -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';
};

View File

@ -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);
}
};

View File

@ -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
];
};

View File

@ -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'));

View File

@ -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');

View File

@ -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'));

View File

@ -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'));

View File

@ -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');
}
}

View File

@ -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;
};

692
js/component/scene.js Normal file
View File

@ -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);
});
});
};

View File

@ -13,6 +13,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/lib/rocket/rocket.js"></script>' . PHP_EOL;
echo '<script src="/js/lib/moment/moment.js"></script>' . PHP_EOL;
echo '<script src="/js/lib/highcharts/highcharts.js"></script>' . PHP_EOL;
echo '<script src="/js/lib/threejs/threejs.js"></script>' . PHP_EOL;
echo '<script src="/js/lib/suncalc/suncalc.js"></script>' . PHP_EOL;
echo '<script src="/js/lib/clipper/clipper.js"></script>' . PHP_EOL;
// Beestat
echo '<script src="/js/beestat.js"></script>' . PHP_EOL;
@ -47,6 +50,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/layer/analyze.js"></script>' . PHP_EOL;
echo '<script src="/js/layer/settings.js"></script>' . PHP_EOL;
echo '<script src="/js/layer/air_quality.js"></script>' . PHP_EOL;
echo '<script src="/js/layer/visualize.js"></script>' . PHP_EOL;
// Component
echo '<script src="/js/component.js"></script>' . PHP_EOL;
@ -70,7 +74,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/card/settings.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/air_quality_detail.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/air_quality_summary.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/visualize.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/air_quality_not_supported.js"></script>' . PHP_EOL;
echo '<script src="/js/component/card/floor_plan_editor.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/runtime_thermostat_summary.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/temperature_profiles.js"></script>' . PHP_EOL;
@ -88,6 +94,9 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/logo.js"></script>' . PHP_EOL;
echo '<script src="/js/component/menu.js"></script>' . PHP_EOL;
echo '<script src="/js/component/menu_item.js"></script>' . PHP_EOL;
echo '<script src="/js/component/scene.js"></script>' . PHP_EOL;
echo '<script src="/js/component/floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/radio_group.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/runtime_thermostat_summary_custom.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/announcements.js"></script>' . PHP_EOL;
@ -104,12 +113,21 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/modal/patreon_status.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/newsletter.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/air_quality_detail_custom.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/create_floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/delete_floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/change_floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input/text.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input/checkbox.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input/radio.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input/select.js"></script>' . PHP_EOL;
echo '<script src="/js/component/button.js"></script>' . PHP_EOL;
echo '<script src="/js/component/button_group.js"></script>' . PHP_EOL;
echo '<script src="/js/component/title.js"></script>' . PHP_EOL;
echo '<script src="/js/component/floor_plan_entity.js"></script>' . PHP_EOL;
echo '<script src="/js/component/floor_plan_entity/room.js"></script>' . PHP_EOL;
echo '<script src="/js/component/floor_plan_entity/point.js"></script>' . PHP_EOL;
echo '<script src="/js/component/floor_plan_entity/wall.js"></script>' . PHP_EOL;
echo '<script src="/js/component/metric.js"></script>' . PHP_EOL;
echo '<script src="/js/component/metric/setpoint.js"></script>' . PHP_EOL;
echo '<script src="/js/component/metric/setpoint/heat.js"></script>' . PHP_EOL;

View File

@ -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);
};

View File

@ -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);

59
js/layer/visualize.js Normal file
View File

@ -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);
};

257
js/lib/clipper/clipper.js Normal file
View File

@ -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<<a.DB-15)-1;this.mt2=2*a.t}function U(a,b){return a&b}function H(a,b){return a|b}function N(a,b){return a^b}function O(a,b){return a&~b}function D(){}function P(a){return a}function C(a){this.r2=q();this.q3=q();k.ONE.dlShiftTo(2*a.t,this.r2);this.mu=this.r2.divide(a);this.m=a}var d={version:"6.4.2.2",use_lines:!0,use_xyz:!1},G=!1;"undefined"!==
typeof module&&module.exports?(module.exports=d,G=!0):"undefined"!==typeof document?window.ClipperLib=d:self.ClipperLib=d;if(G){var u="chrome";var v="Netscape"}else u=navigator.userAgent.toString().toLowerCase(),v=navigator.appName;var I=-1!=u.indexOf("chrome")&&-1==u.indexOf("chromium")?1:0;G=-1!=u.indexOf("chromium")?1:0;var Q=-1!=u.indexOf("safari")&&-1==u.indexOf("chrome")&&-1==u.indexOf("chromium")?1:0;var J=-1!=u.indexOf("firefox")?1:0;u.indexOf("firefox/17");u.indexOf("firefox/15");u.indexOf("firefox/3");
var K=-1!=u.indexOf("opera")?1:0;u.indexOf("msie 10");u.indexOf("msie 9");var L=-1!=u.indexOf("msie 8")?1:0;var V=-1!=u.indexOf("msie 7")?1:0;u=-1!=u.indexOf("msie ")?1:0;d.biginteger_used=null;"Microsoft Internet Explorer"==v?(k.prototype.am=S,v=30):"Netscape"!=v?(k.prototype.am=R,v=26):(k.prototype.am=T,v=28);k.prototype.DB=v;k.prototype.DM=(1<<v)-1;k.prototype.DV=1<<v;k.prototype.FV=Math.pow(2,52);k.prototype.F1=52-v;k.prototype.F2=2*v-52;var E=[],x;v=48;for(x=0;9>=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<b.compareTo(k.ZERO)&&this.m.subTo(b,
b);return b};B.prototype.revert=function(a){var b=q();a.copyTo(b);this.reduce(b);return b};B.prototype.reduce=function(a){for(;a.t<=this.mt2;)a[a.t++]=0;for(var b=0;b<this.m.t;++b){var c=a[b]&32767,e=c*this.mpl+((c*this.mph+(a[b]>>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;0<a?this[0]=a:-1>a?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)-1)<<g,this[this.t++]=h>>this.DB-g):this[this.t-1]|=h<<g,g+=c,g>=this.DB&&(g-=this.DB))}8==c&&0!=(a[0]&128)&&(this.s=-1,0<g&&(this[this.t-1]|=(1<<this.DB-g)-1<<g));this.clamp();d&&k.ZERO.subTo(this,this)};k.prototype.clamp=function(){for(var a=this.s&this.DM;0<this.t&&this[this.t-1]==a;)--this.t};k.prototype.dlShiftTo=function(a,b){var c;for(c=this.t-1;0<=c;--c)b[c+a]=this[c];for(c=a-1;0<=c;--c)b[c]=
0;b.t=this.t+a;b.s=this.s};k.prototype.drShiftTo=function(a,b){for(var c=a;c<this.t;++c)b[c-a]=this[c];b.t=Math.max(this.t-a,0);b.s=this.s};k.prototype.lShiftTo=function(a,b){var c=a%this.DB,e=this.DB-c,d=(1<<e)-1,g=Math.floor(a/this.DB),h=this.s<<c&this.DM,m;for(m=this.t-1;0<=m;--m)b[m+g+1]=this[m]>>e|h,h=(this[m]&d)<<c;for(m=g-1;0<=m;--m)b[m]=0;b[g]=h;b.t=this.t+g+1;b.s=this.s;b.clamp()};k.prototype.rShiftTo=function(a,b){b.s=this.s;var c=Math.floor(a/this.DB);if(c>=this.t)b.t=0;else{var e=a%this.DB,
d=this.DB-e,g=(1<<e)-1;b[0]=this[c]>>e;for(var h=c+1;h<this.t;++h)b[h-c-1]|=(this[h]&g)<<d,b[h-c]=this[h]>>e;0<e&&(b[this.t-c-1]|=(this.s&g)<<d);b.t=this.t-c;b.clamp()}};k.prototype.subTo=function(a,b){for(var c=0,e=0,d=Math.min(a.t,this.t);c<d;)e+=this[c]-a[c],b[c++]=e&this.DM,e>>=this.DB;if(a.t<this.t){for(e-=a.s;c<this.t;)e+=this[c],b[c++]=e&this.DM,e>>=this.DB;e+=this.s}else{for(e+=this.s;c<a.t;)e-=a[c],b[c++]=e&this.DM,e>>=this.DB;e-=a.s}b.s=0>e?-1:0;-1>e?b[c++]=this.DV+e:0<e&&(b[c++]=e);b.t=
c;b.clamp()};k.prototype.multiplyTo=function(a,b){var c=this.abs(),e=a.abs(),d=c.t;for(b.t=d+e.t;0<=--d;)b[d]=0;for(d=0;d<e.t;++d)b[d+c.t]=c.am(0,e[d],b,d,0,c.t);b.s=0;b.clamp();this.s!=a.s&&k.ZERO.subTo(b,b)};k.prototype.squareTo=function(a){for(var b=this.abs(),c=a.t=2*b.t;0<=--c;)a[c]=0;for(c=0;c<b.t-1;++c){var e=b.am(c,b[c],a,2*c,0,1);(a[c+b.t]+=b.am(c+1,2*b[c],a,2*c+1,e,b.t-c-1))>=b.DV&&(a[c+b.t]-=b.DV,a[c+b.t+1]=1)}0<a.t&&(a[a.t-1]+=b.am(c,b[c],a,2*c,0,1));a.s=0;a.clamp()};k.prototype.divRemTo=
function(a,b,c){var e=a.abs();if(!(0>=e.t)){var d=this.abs();if(d.t<e.t)null!=b&&b.fromInt(0),null!=c&&this.copyTo(c);else{null==c&&(c=q());var g=q(),h=this.s;a=a.s;var m=this.DB-F(e[e.t-1]);0<m?(e.lShiftTo(m,g),d.lShiftTo(m,c)):(e.copyTo(g),d.copyTo(c));e=g.t;d=g[e-1];if(0!=d){var l=d*(1<<this.F1)+(1<e?g[e-2]>>this.F2:0),n=this.FV/l;l=(1<<this.F1)/l;var r=1<<this.F2,p=c.t,t=p-e,u=null==b?q():b;g.dlShiftTo(t,u);0<=c.compareTo(u)&&(c[c.t++]=1,c.subTo(u,c));k.ONE.dlShiftTo(e,u);for(u.subTo(g,g);g.t<
e;)g[g.t++]=0;for(;0<=--t;){var v=c[--p]==d?this.DM:Math.floor(c[p]*n+(c[p-1]+r)*l);if((c[p]+=g.am(0,v,c,t,0,e))<v)for(g.dlShiftTo(t,u),c.subTo(u,c);c[p]<--v;)c.subTo(u,c)}null!=b&&(c.drShiftTo(e,b),h!=a&&k.ZERO.subTo(b,b));c.t=e;c.clamp();0<m&&c.rShiftTo(m,c);0>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 0<b?
this.DV-b:-b};k.prototype.isEven=function(){return 0==(0<this.t?this[0]&1:this.s)};k.prototype.exp=function(a,b){if(4294967295<a||1>a)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<<g))b.mulTo(e,d,c);else{var h=c;c=e;e=h}return b.revert(c)};k.prototype.toString=function(a){if(0>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<<a)-
1,c,e=!1,d="",g=this.t,h=this.DB-g*this.DB%a;if(0<g--)for(h<this.DB&&0<(c=this[g]>>h)&&(e=!0,d="0123456789abcdefghijklmnopqrstuvwxyz".charAt(c));0<=g;)h<a?(c=(this[g]&(1<<h)-1)<<a-h,c|=this[--g]>>(h+=this.DB-a)):(c=this[g]>>(h-=a)&b,0>=h&&(h+=this.DB,--g)),0<c&&(e=!0),e&&(d+="0123456789abcdefghijklmnopqrstuvwxyz".charAt(c));return e?d:"0"};k.prototype.negate=function(){var a=q();k.ZERO.subTo(this,a);return a};k.prototype.abs=function(){return 0>this.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&&0<b.compareTo(k.ZERO)&&a.subTo(b,b);return b};k.prototype.modPowInt=function(a,b){var c=256>a||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||36<a)return"0";var b=this.chunkSize(a);b=Math.pow(a,b);var c=y(b),e=q(),d=q(),g="";for(this.divRemTo(c,e,d);0<e.signum();)g=(b+d.intValue()).toString(a).substr(1)+g,e.divRemTo(c,e,d);return d.intValue().toString(a)+g};k.prototype.fromRadix=function(a,b){this.fromInt(0);null==b&&(b=10);for(var c=this.chunkSize(b),e=Math.pow(b,c),d=!1,g=0,h=0,m=0;m<a.length;++m){var l=M(a,m);0>l?"-"==a.charAt(m)&&0==this.signum()&&
(d=!0):(h=b*h+l,++g>=c&&(this.dMultiply(e),this.dAddOffset(h,0),h=g=0))}0<g&&(this.dMultiply(Math.pow(b,g)),this.dAddOffset(h,0));d&&k.ZERO.subTo(this,this)};k.prototype.fromNumber=function(a,b,c){if("number"==typeof b)if(2>a)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<e?c[0]&(1<<e)-1:0;this.fromString(c,256)}};k.prototype.bitwiseTo=function(a,b,c){var e,d=Math.min(a.t,this.t);for(e=0;e<d;++e)c[e]=b(this[e],a[e]);if(a.t<this.t){var g=a.s&this.DM;for(e=d;e<this.t;++e)c[e]=b(this[e],g);c.t=this.t}else{g=this.s&this.DM;for(e=d;e<a.t;++e)c[e]=b(g,a[e]);c.t=a.t}c.s=b(this.s,a.s);c.clamp()};k.prototype.changeBit=function(a,b){var c=k.ONE.shiftLeft(a);this.bitwiseTo(c,b,c);return c};k.prototype.addTo=function(a,b){for(var c=0,e=0,d=Math.min(a.t,
this.t);c<d;)e+=this[c]+a[c],b[c++]=e&this.DM,e>>=this.DB;if(a.t<this.t){for(e+=a.s;c<this.t;)e+=this[c],b[c++]=e&this.DM,e>>=this.DB;e+=this.s}else{for(e+=this.s;c<a.t;)e+=a[c],b[c++]=e&this.DM,e>>=this.DB;e+=a.s}b.s=0>e?-1:0;0<e?b[c++]=e:-1>e&&(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<e;)c[--e]=0;var d;for(d=c.t-this.t;e<d;++e)c[e+this.t]=this.am(0,a[e],c,e,0,this.t);for(d=Math.min(a.t,b);e<d;++e)this.am(0,a[e],c,e,0,b-e);c.clamp()};k.prototype.multiplyUpperTo=function(a,b,c){--b;var e=c.t=this.t+a.t-b;for(c.s=0;0<=--e;)c[e]=0;for(e=Math.max(b-this.t,0);e<a.t;++e)c[this.t+e-b]=this.am(b-e,a[e],c,0,0,this.t+e-b);c.clamp();c.drShiftTo(1,c)};
k.prototype.modInt=function(a){if(0>=a)return 0;var b=this.DV%a,c=0>this.s?a-1:0;if(0<this.t)if(0==b)c=this[0]%a;else for(var e=this.t-1;0<=e;--e)c=(b*c+this[e])%a;return c};k.prototype.millerRabin=function(a){var b=this.subtract(k.ONE),c=b.getLowestSetBit();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;g<a;++g){d.fromInt(w[Math.floor(Math.random()*w.length)]);var h=d.modPow(e,this);if(0!=h.compareTo(k.ONE)&&0!=h.compareTo(b)){for(var m=1;m++<c&&0!=h.compareTo(b);)if(h=
h.modPowInt(2,this),0==h.compareTo(k.ONE))return!1;if(0!=h.compareTo(b))return!1}}return!0};k.prototype.clone=function(){var a=q();this.copyTo(a);return a};k.prototype.intValue=function(){if(0>this.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)<<this.DB|this[0]};k.prototype.byteValue=function(){return 0==this.t?this.s:this[0]<<24>>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<a--)for(c<this.DB&&(e=this[a]>>c)!=(this.s&this.DM)>>c&&(b[d++]=e|this.s<<this.DB-c);0<=a;)if(8>c?(e=(this[a]&(1<<c)-1)<<8-c,e|=this[--a]>>(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,0<d||e!=this.s)b[d++]=e;return b};k.prototype.equals=
function(a){return 0==this.compareTo(a)};k.prototype.min=function(a){return 0>this.compareTo(a)?this:a};k.prototype.max=function(a){return 0<this.compareTo(a)?this:a};k.prototype.and=function(a){var b=q();this.bitwiseTo(a,U,b);return b};k.prototype.or=function(a){var b=q();this.bitwiseTo(a,H,b);return b};k.prototype.xor=function(a){var b=q();this.bitwiseTo(a,N,b);return b};k.prototype.andNot=function(a){var b=q();this.bitwiseTo(a,O,b);return b};k.prototype.not=function(){for(var a=q(),b=0;b<this.t;++b)a[b]=
this.DM&~this[b];a.t=this.t;a.s=~this.s;return a};k.prototype.shiftLeft=function(a){var b=q();0>a?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<this.t;++a)if(0!=this[a]){var b=a*this.DB;a=this[a];if(0==a)a=-1;else{var c=0;0==(a&65535)&&(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;++c){for(var e=this[c]^b,d=0;0!=e;)e&=e-1,++d;a+=d}return a};k.prototype.testBit=function(a){var b=Math.floor(a/this.DB);return b>=this.t?0!=this.s:0!=(this[b]&1<<a%this.DB)};k.prototype.setBit=function(a){return this.changeBit(a,H)};k.prototype.clearBit=function(a){return this.changeBit(a,O)};k.prototype.flipBit=function(a){return this.changeBit(a,N)};k.prototype.add=function(a){var b=q();
this.addTo(a,b);return b};k.prototype.subtract=function(a){var b=q();this.subTo(a,b);return b};k.prototype.multiply=function(a){var b=q();this.multiplyTo(a,b);return b};k.prototype.divide=function(a){var b=q();this.divRemTo(a,b,null);return b};k.prototype.remainder=function(a){var b=q();this.divRemTo(a,null,b);return b};k.prototype.divideAndRemainder=function(a){var b=q(),c=q();this.divRemTo(a,b,c);return[b,c]};k.prototype.modPow=function(a,b){var c=a.bitLength(),e=y(1);if(0>=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<<d)-1;h[1]=g.convert(this);if(1<d)for(c=q(),g.sqrTo(h[1],c);m<=n;)h[m]=q(),g.mulTo(c,h[m-2],h[m]),m+=2;var r=a.t-1,p=!0,t=q();for(c=F(a[r])-1;0<=r;){if(c>=k)var u=a[r]>>c-k&n;else u=(a[r]&(1<<c+1)-1)<<k-c,0<r&&(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<m;)g.sqrTo(e,t),g.sqrTo(t,e),m-=2;0<m?g.sqrTo(e,t):(m=e,e=t,t=m);g.mulTo(t,
h[u],e)}for(;0<=r&&0==(a[r]&1<<c);)g.sqrTo(e,t),m=e,e=t,t=m,0>--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<e&&(e=c);0<e&&(b.rShiftTo(e,b),a.rShiftTo(e,a));for(;0<b.signum();)0<(c=b.getLowestSetBit())&&b.rShiftTo(c,b),0<(c=a.getLowestSetBit())&&a.rShiftTo(c,a),0<=b.compareTo(a)?(b.subTo(a,b),b.rShiftTo(1,b)):(a.subTo(b,a),a.rShiftTo(1,a));0<e&&a.lShiftTo(e,a);return a};k.prototype.isProbablePrime=function(a){var b,c=this.abs();if(1==c.t&&c[0]<=w[w.length-1]){for(b=0;b<w.length;++b)if(c[0]==w[b])return!0;return!1}if(c.isEven())return!1;
for(b=1;b<w.length;){for(var e=w[b],d=b+1;d<w.length&&e<W;)e*=w[d++];for(e=c.modInt(e);b<d;)if(0==e%w[b++])return!1}return c.millerRabin(a)};k.prototype.square=function(){var a=q();this.squareTo(a);return a};k.prototype.IsNegative=function(){return-1==this.compareTo(k.ZERO)?!0:!1};k.op_Equality=function(a,b){return 0==a.compareTo(b)?!0:!1};k.op_Inequality=function(a,b){return 0!=a.compareTo(b)?!0:!1};k.op_GreaterThan=function(a,b){return 0<a.compareTo(b)?!0:!1};k.op_LessThan=function(a,b){return 0>
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;d<e.length;d++)"undefined"===typeof Object.getOwnPropertyDescriptor(a.prototype,e[d])&&Object.defineProperty(a.prototype,e[d],Object.getOwnPropertyDescriptor(b.prototype,e[d]));for(c in b)"undefined"===typeof a[c]&&(a[c]=b[c]);a.$baseCtor=b};d.Path=function(){return[]};d.Path.prototype.push=Array.prototype.push;d.Paths=function(){return[]};d.Paths.prototype.push=
Array.prototype.push;d.DoublePoint=function(){var a=arguments;this.y=this.x=0;1===a.length?(this.x=a[0].x,this.y=a[0].y):2===a.length&&(this.x=a[0],this.y=a[1])};d.DoublePoint0=function(){this.y=this.x=0};d.DoublePoint0.prototype=d.DoublePoint.prototype;d.DoublePoint1=function(a){this.x=a.x;this.y=a.y};d.DoublePoint1.prototype=d.DoublePoint.prototype;d.DoublePoint2=function(a,b){this.x=a;this.y=b};d.DoublePoint2.prototype=d.DoublePoint.prototype;d.PolyNode=function(){this.m_Parent=null;this.m_polygon=
new d.Path;this.m_endtype=this.m_jointype=this.m_Index=0;this.m_Childs=[];this.IsOpen=!1};d.PolyNode.prototype.IsHoleNode=function(){for(var a=!0,b=this.m_Parent;null!==b;)a=!a,b=b.m_Parent;return a};d.PolyNode.prototype.ChildCount=function(){return this.m_Childs.length};d.PolyNode.prototype.Contour=function(){return this.m_polygon};d.PolyNode.prototype.AddChild=function(a){var b=this.m_Childs.length;this.m_Childs.push(a);a.m_Parent=this;a.m_Index=b};d.PolyNode.prototype.GetNext=function(){return 0<
this.m_Childs.length?this.m_Childs[0]:this.GetNextSiblingUp()};d.PolyNode.prototype.GetNextSiblingUp=function(){return null===this.m_Parent?null:this.m_Index===this.m_Parent.m_Childs.length-1?this.m_Parent.GetNextSiblingUp():this.m_Parent.m_Childs[this.m_Index+1]};d.PolyNode.prototype.Childs=function(){return this.m_Childs};d.PolyNode.prototype.Parent=function(){return this.m_Parent};d.PolyNode.prototype.IsHole=function(){return this.IsHoleNode()};d.PolyTree=function(){this.m_AllPolys=[];d.PolyNode.call(this)};
d.PolyTree.prototype.Clear=function(){for(var a=0,b=this.m_AllPolys.length;a<b;a++)this.m_AllPolys[a]=null;this.m_AllPolys.length=0;this.m_Childs.length=0};d.PolyTree.prototype.GetFirst=function(){return 0<this.m_Childs.length?this.m_Childs[0]:null};d.PolyTree.prototype.Total=function(){var a=this.m_AllPolys.length;0<a&&this.m_Childs[0]!==this.m_AllPolys[0]&&a--;return a};v(d.PolyTree,d.PolyNode);d.Math_Abs_Int64=d.Math_Abs_Int32=d.Math_Abs_Double=function(a){return Math.abs(a)};d.Math_Max_Int32_Int32=
function(a,b){return Math.max(a,b)};d.Cast_Int32=u||K||Q?function(a){return a|0}:function(a){return~~a};"undefined"===typeof Number.toInteger&&(Number.toInteger=null);d.Cast_Int64=I?function(a){return-2147483648>a||2147483647<a?0>a?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||2147483647<a?0>a?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 0<c?1:0>c?-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&&a<d.ClipperBase.tolerance};d.ClipperBase.IsHorizontal=function(a){return 0===a.Delta.y};d.ClipperBase.prototype.PointIsVertex=function(a,b){var c=b;do{if(d.IntPoint.op_Equality(c.Pt,a))return!0;c=c.Next}while(c!==b);return!1};d.ClipperBase.prototype.PointOnLineSegment=
function(a,b,c,e){return e?a.x===b.x&&a.y===b.y||a.x===c.x&&a.y===c.y||a.x>b.x===a.x<c.x&&a.y>b.y===a.y<c.y&&k.op_Equality(k.Int128Mul(a.x-b.x,c.y-b.y),k.Int128Mul(c.x-b.x,a.y-b.y)):a.x===b.x&&a.y===b.y||a.x===c.x&&a.y===c.y||a.x>b.x===a.x<c.x&&a.y>b.y===a.y<c.y&&(a.x-b.x)*(c.y-b.y)===(c.x-b.x)*(a.y-b.y)};d.ClipperBase.prototype.PointOnPolygon=function(a,b,c){for(var e=b;;){if(this.PointOnLineSegment(a,e.Pt,e.Next.Pt,c))return!0;e=e.Next;if(e===b)break}return!1};d.ClipperBase.prototype.SlopesEqual=
d.ClipperBase.SlopesEqual=function(){var a=arguments,b=a.length;if(3===b){b=a[0];var c=a[1];return(a=a[2])?k.op_Equality(k.Int128Mul(b.Delta.y,c.Delta.x),k.Int128Mul(b.Delta.x,c.Delta.y)):d.Cast_Int64(b.Delta.y*c.Delta.x)===d.Cast_Int64(b.Delta.x*c.Delta.y)}if(4===b){b=a[0];c=a[1];var e=a[2];return(a=a[3])?k.op_Equality(k.Int128Mul(b.y-c.y,c.x-e.x),k.Int128Mul(b.x-c.x,c.y-e.y)):0===d.Cast_Int64((b.y-c.y)*(c.x-e.x))-d.Cast_Int64((b.x-c.x)*(c.y-e.y))}b=a[0];c=a[1];e=a[2];var f=a[3];return(a=a[4])?k.op_Equality(k.Int128Mul(b.y-
c.y,e.x-f.x),k.Int128Mul(b.x-c.x,e.y-f.y)):0===d.Cast_Int64((b.y-c.y)*(e.x-f.x))-d.Cast_Int64((b.x-c.x)*(e.y-f.y))};d.ClipperBase.SlopesEqual3=function(a,b,c){return c?k.op_Equality(k.Int128Mul(a.Delta.y,b.Delta.x),k.Int128Mul(a.Delta.x,b.Delta.y)):d.Cast_Int64(a.Delta.y*b.Delta.x)===d.Cast_Int64(a.Delta.x*b.Delta.y)};d.ClipperBase.SlopesEqual4=function(a,b,c,e){return e?k.op_Equality(k.Int128Mul(a.y-b.y,b.x-c.x),k.Int128Mul(a.x-b.x,b.y-c.y)):0===d.Cast_Int64((a.y-b.y)*(b.x-c.x))-d.Cast_Int64((a.x-
b.x)*(b.y-c.y))};d.ClipperBase.SlopesEqual5=function(a,b,c,e,f){return f?k.op_Equality(k.Int128Mul(a.y-b.y,c.x-e.x),k.Int128Mul(a.x-b.x,c.y-e.y)):0===d.Cast_Int64((a.y-b.y)*(c.x-e.x))-d.Cast_Int64((a.x-b.x)*(c.y-e.y))};d.ClipperBase.prototype.Clear=function(){this.DisposeLocalMinimaList();for(var a=0,b=this.m_edges.length;a<b;++a){for(var c=0,e=this.m_edges[a].length;c<e;++c)this.m_edges[a][c]=null;d.Clear(this.m_edges[a])}d.Clear(this.m_edges);this.m_HasOpenPaths=this.m_UseFullRange=!1};d.ClipperBase.prototype.DisposeLocalMinimaList=
function(){for(;null!==this.m_MinimaList;){var a=this.m_MinimaList.Next;this.m_MinimaList=null;this.m_MinimaList=a}this.m_CurrentLM=null};d.ClipperBase.prototype.RangeTest=function(a,b){if(b.Value)(a.x>d.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.x<a.Bot.x&&(a=b);break}}return a};d.ClipperBase.prototype.ProcessBound=function(a,b){var c=a,e;if(c.OutIdx===
d.ClipperBase.Skip){a=c;if(b){for(;a.Top.y===a.Next.Bot.y;)a=a.Next;for(;a!==c&&a.Dx===d.ClipperBase.horizontal;)a=a.Prev}else{for(;a.Top.y===a.Prev.Bot.y;)a=a.Prev;for(;a!==c&&a.Dx===d.ClipperBase.horizontal;)a=a.Next}if(a===c)c=b?a.Next:a.Prev;else{a=b?c.Next:c.Prev;var f=new d.LocalMinima;f.Next=null;f.y=a.Bot.y;f.LeftBound=null;f.RightBound=a;a.WindDelta=0;c=this.ProcessBound(a,b);this.InsertLocalMinima(f)}return c}a.Dx===d.ClipperBase.horizontal&&(f=b?a.Prev:a.Next,f.Dx===d.ClipperBase.horizontal?
f.Bot.x!==a.Bot.x&&f.Top.x!==a.Bot.x&&this.ReverseHorizontal(a):f.Bot.x!==a.Bot.x&&this.ReverseHorizontal(a));f=a;if(b){for(;c.Top.y===c.Next.Bot.y&&c.Next.OutIdx!==d.ClipperBase.Skip;)c=c.Next;if(c.Dx===d.ClipperBase.horizontal&&c.Next.OutIdx!==d.ClipperBase.Skip){for(e=c;e.Prev.Dx===d.ClipperBase.horizontal;)e=e.Prev;e.Prev.Top.x>c.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(;0<e&&d.IntPoint.op_Equality(a[e],a[0]);)--e;for(;0<e&&d.IntPoint.op_Equality(a[e],a[e-1]);)--e;if(c&&2>e||!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.Dx<a.Prev.Dx?(b.LeftBound=a.Prev,b.RightBound=a,f=!1):(b.LeftBound=a,b.RightBound=a.Prev,f=!0);b.LeftBound.Side=d.EdgeSide.esLeft;b.RightBound.Side=d.EdgeSide.esRight;b.LeftBound.WindDelta=c?b.LeftBound.Next===b.RightBound?-1:1:0;b.RightBound.WindDelta=-b.LeftBound.WindDelta;a=this.ProcessBound(b.LeftBound,f);a.OutIdx===d.ClipperBase.Skip&&(a=this.ProcessBound(a,f));e=this.ProcessBound(b.RightBound,!f);e.OutIdx===d.ClipperBase.Skip&&(e=this.ProcessBound(e,
!f));b.LeftBound.OutIdx===d.ClipperBase.Skip?b.LeftBound=null:b.RightBound.OutIdx===d.ClipperBase.Skip&&(b.RightBound=null);this.InsertLocalMinima(b);f||(a=e)}return!0};d.ClipperBase.prototype.AddPaths=function(a,b,c){for(var e=!1,d=0,g=a.length;d<g;++d)this.AddPath(a[d],b,c)&&(e=!0);return e};d.ClipperBase.prototype.Pt2IsBetweenPt1AndPt3=function(a,b,c){return d.IntPoint.op_Equality(a,c)||d.IntPoint.op_Equality(a,b)||d.IntPoint.op_Equality(c,b)?!1:a.x!==c.x?b.x>a.x===b.x<c.x:b.y>a.y===b.y<c.y};d.ClipperBase.prototype.RemoveEdge=
function(a){a.Prev.Next=a.Next;a.Next.Prev=a.Prev;var b=a.Next;a.Prev=null;return b};d.ClipperBase.prototype.SetDx=function(a){a.Delta.x=a.Top.x-a.Bot.x;a.Delta.y=a.Top.y-a.Bot.y;a.Dx=0===a.Delta.y?d.ClipperBase.horizontal:a.Delta.x/a.Delta.y};d.ClipperBase.prototype.InsertLocalMinima=function(a){if(null===this.m_MinimaList)this.m_MinimaList=a;else if(a.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.y<b.Next.y;)b=b.Next;a.Next=
b.Next;b.Next=a}};d.ClipperBase.prototype.PopLocalMinima=function(a,b){b.v=this.m_CurrentLM;return null!==this.m_CurrentLM&&this.m_CurrentLM.y===a?(this.m_CurrentLM=this.m_CurrentLM.Next,!0):!1};d.ClipperBase.prototype.ReverseHorizontal=function(a){var b=a.Top.x;a.Top.x=a.Bot.x;a.Bot.x=b;d.use_xyz&&(b=a.Top.Z,a.Top.Z=a.Bot.Z,a.Bot.Z=b)};d.ClipperBase.prototype.Reset=function(){this.m_CurrentLM=this.m_MinimaList;if(null!==this.m_CurrentLM){this.m_Scanbeam=null;for(var a=this.m_MinimaList;null!==a;){this.InsertScanbeam(a.y);
var b=a.LeftBound;null!==b&&(b.Curr.x=b.Bot.x,b.Curr.y=b.Bot.y,d.use_xyz&&(b.Curr.Z=b.Bot.Z),b.OutIdx=d.ClipperBase.Unassigned);b=a.RightBound;null!==b&&(b.Curr.x=b.Bot.x,b.Curr.y=b.Bot.y,d.use_xyz&&(b.Curr.Z=b.Bot.Z),b.OutIdx=d.ClipperBase.Unassigned);a=a.Next}this.m_ActiveEdges=null}};d.ClipperBase.prototype.InsertScanbeam=function(a){if(null===this.m_Scanbeam)this.m_Scanbeam=new d.Scanbeam,this.m_Scanbeam.Next=null,this.m_Scanbeam.y=a;else if(a>this.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<this.m_Maxima.x)b.Next=this.m_Maxima,b.Prev=null,this.m_Maxima=b;else{for(var c=this.m_Maxima;null!==c.Next&&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;e<c;e++){var d=this.m_PolyOuts[e];null===d.Pts||d.IsOpen||(d.IsHole^this.ReverseSolution)==0<this.Area$1(d)&&this.ReversePolyPtLinks(d.Pts)}this.JoinCommonEdges();
e=0;for(c=this.m_PolyOuts.length;e<c;e++)d=this.m_PolyOuts[e],null!==d.Pts&&(d.IsOpen?this.FixupOutPolyline(d):this.FixupOutPolygon(d));this.StrictlySimple&&this.DoSimplePolygons();return!0}finally{this.m_Joins.length=0,this.m_GhostJoins.length=0}};d.Clipper.prototype.DisposeAllPolyPts=function(){for(var a=0,b=this.m_PolyOuts.length;a<b;++a)this.DisposeOutRec(a);d.Clear(this.m_PolyOuts)};d.Clipper.prototype.AddJoin=function(a,b,c){var e=new d.Join;e.OutPt1=a;e.OutPt2=b;e.OffPt.x=c.x;e.OffPt.y=c.y;
d.use_xyz&&(e.OffPt.Z=c.Z);this.m_Joins.push(e)};d.Clipper.prototype.AddGhostJoin=function(a,b){var c=new d.Join;c.OutPt1=a;c.OffPt.x=b.x;c.OffPt.y=b.y;d.use_xyz&&(c.OffPt.Z=b.Z);this.m_GhostJoins.push(c)};d.Clipper.prototype.SetZ=function(a,b,c){null!==this.ZFillFunction&&0===a.Z&&null!==this.ZFillFunction&&(d.IntPoint.op_Equality(a,b.Bot)?a.Z=b.Bot.Z:d.IntPoint.op_Equality(a,b.Top)?a.Z=b.Top.Z:d.IntPoint.op_Equality(a,c.Bot)?a.Z=c.Bot.Z:d.IntPoint.op_Equality(a,c.Top)?a.Z=c.Top.Z:this.ZFillFunction(b.Bot,
b.Top,c.Bot,c.Top,a))};d.Clipper.prototype.InsertLocalMinimaIntoAEL=function(a){for(var b,c={},e,f;this.PopLocalMinima(a,c);){e=c.v.LeftBound;f=c.v.RightBound;var g=null;null===e?(this.InsertEdgeIntoAEL(f,null),this.SetWindingCount(f),this.IsContributing(f)&&(g=this.AddOutPt(f,f.Bot))):(null===f?(this.InsertEdgeIntoAEL(e,null),this.SetWindingCount(e),this.IsContributing(e)&&(g=this.AddOutPt(e,e.Bot))):(this.InsertEdgeIntoAEL(e,null),this.InsertEdgeIntoAEL(f,e),this.SetWindingCount(e),f.WindCnt=e.WindCnt,
f.WindCnt2=e.WindCnt2,this.IsContributing(e)&&(g=this.AddLocalMinPoly(e,f,e.Bot))),this.InsertScanbeam(e.Top.y));null!==f&&(d.ClipperBase.IsHorizontal(f)?(null!==f.NextInLML&&this.InsertScanbeam(f.NextInLML.Top.y),this.AddEdgeToSEL(f)):this.InsertScanbeam(f.Top.y));if(null!==e&&null!==f){if(null!==g&&d.ClipperBase.IsHorizontal(f)&&0<this.m_GhostJoins.length&&0!==f.WindDelta){b=0;for(var h=this.m_GhostJoins.length;b<h;b++){var m=this.m_GhostJoins[b];this.HorzSegmentsOverlap(m.OutPt1.Pt.x,m.OffPt.x,
f.Bot.x,f.Top.x)&&this.AddJoin(m.OutPt1,g,m.OffPt)}}0<=e.OutIdx&&null!==e.PrevInAEL&&e.PrevInAEL.Curr.x===e.Bot.x&&0<=e.PrevInAEL.OutIdx&&d.ClipperBase.SlopesEqual5(e.PrevInAEL.Curr,e.PrevInAEL.Top,e.Curr,e.Top,this.m_UseFullRange)&&0!==e.WindDelta&&0!==e.PrevInAEL.WindDelta&&(b=this.AddOutPt(e.PrevInAEL,e.Bot),this.AddJoin(g,b,e.Top));if(e.NextInAEL!==f&&(0<=f.OutIdx&&0<=f.PrevInAEL.OutIdx&&d.ClipperBase.SlopesEqual5(f.PrevInAEL.Curr,f.PrevInAEL.Top,f.Curr,f.Top,this.m_UseFullRange)&&0!==f.WindDelta&&
0!==f.PrevInAEL.WindDelta&&(b=this.AddOutPt(f.PrevInAEL,f.Bot),this.AddJoin(g,b,f.Top)),g=e.NextInAEL,null!==g))for(;g!==f;)this.IntersectEdges(f,g,e.Curr),g=g.NextInAEL}}};d.Clipper.prototype.InsertEdgeIntoAEL=function(a,b){if(null===this.m_ActiveEdges)a.PrevInAEL=null,a.NextInAEL=null,this.m_ActiveEdges=a;else if(null===b&&this.E2InsertsBeforeE1(this.m_ActiveEdges,a))a.PrevInAEL=null,a.NextInAEL=this.m_ActiveEdges,this.m_ActiveEdges=this.m_ActiveEdges.PrevInAEL=a;else{null===b&&(b=this.m_ActiveEdges);
for(;null!==b.NextInAEL&&!this.E2InsertsBeforeE1(b.NextInAEL,a);)b=b.NextInAEL;a.NextInAEL=b.NextInAEL;null!==b.NextInAEL&&(b.NextInAEL.PrevInAEL=a);a.PrevInAEL=b;b.NextInAEL=a}};d.Clipper.prototype.E2InsertsBeforeE1=function(a,b){return b.Curr.x===a.Curr.x?b.Top.y>a.Top.y?b.Top.x<d.Clipper.TopX(a,b.Top.y):a.Top.x>d.Clipper.TopX(b,a.Top.y):b.Curr.x<a.Curr.x};d.Clipper.prototype.IsEvenOddFillType=function(a){return a.PolyTyp===d.PolyType.ptSubject?this.m_SubjFillType===d.PolyFillType.pftEvenOdd:this.m_ClipFillType===
d.PolyFillType.pftEvenOdd};d.Clipper.prototype.IsEvenOddAltFillType=function(a){return a.PolyTyp===d.PolyType.ptSubject?this.m_ClipFillType===d.PolyFillType.pftEvenOdd:this.m_SubjFillType===d.PolyFillType.pftEvenOdd};d.Clipper.prototype.IsContributing=function(a){if(a.PolyTyp===d.PolyType.ptSubject){var b=this.m_SubjFillType;var c=this.m_ClipFillType}else b=this.m_ClipFillType,c=this.m_SubjFillType;switch(b){case d.PolyFillType.pftEvenOdd:if(0===a.WindDelta&&1!==a.WindCnt)return!1;break;case d.PolyFillType.pftNonZero:if(1!==
Math.abs(a.WindCnt))return!1;break;case d.PolyFillType.pftPositive:if(1!==a.WindCnt)return!1;break;default:if(-1!==a.WindCnt)return!1}switch(this.m_ClipType){case d.ClipType.ctIntersection: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.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 0<a.WindCnt2;default:return 0>a.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?1<Math.abs(b.WindCnt)?0>b.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.OutIdx<b.OutIdx?this.AppendPolygon(a,b):this.AppendPolygon(b,a)};d.Clipper.prototype.AddLocalMinPoly=function(a,b,c){if(d.ClipperBase.IsHorizontal(b)||a.Dx>b.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.y<c.y&&f.Top.y<c.y){b=d.Clipper.TopX(a,c.y);var g=d.Clipper.TopX(f,c.y);b===g&&0!==f.WindDelta&&0!==a.WindDelta&&d.ClipperBase.SlopesEqual5(new d.IntPoint2(b,c.y),a.Top,new d.IntPoint2(g,c.y),f.Top,this.m_UseFullRange)&&(c=this.AddOutPt(a,c),this.AddJoin(e,c,f.Top))}return e};d.Clipper.prototype.AddOutPt=function(a,b){if(0>a.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<e&&c<b};d.Clipper.prototype.SetHoleState=function(a,b){for(var c=
a.PrevInAEL,e=null;null!==c;)0<=c.OutIdx&&0!==c.WindDelta&&(null===e?e=c:e.OutIdx===c.OutIdx&&(e=null)),c=c.PrevInAEL;null===e?(b.FirstLeft=null,b.IsHole=!1):(b.FirstLeft=this.m_PolyOuts[e.OutIdx],b.IsHole=!b.FirstLeft.IsHole)};d.Clipper.prototype.GetDx=function(a,b){return a.y===b.y?d.ClipperBase.horizontal:(b.x-a.x)/(b.y-a.y)};d.Clipper.prototype.FirstIsBottomPt=function(a,b){for(var c=a.Prev;d.IntPoint.op_Equality(c.Pt,a.Pt)&&c!==a;)c=c.Prev;var e=Math.abs(this.GetDx(a.Pt,c.Pt));for(c=a.Next;d.IntPoint.op_Equality(c.Pt,
a.Pt)&&c!==a;)c=c.Next;var f=Math.abs(this.GetDx(a.Pt,c.Pt));for(c=b.Prev;d.IntPoint.op_Equality(c.Pt,b.Pt)&&c!==b;)c=c.Prev;var g=Math.abs(this.GetDx(b.Pt,c.Pt));for(c=b.Next;d.IntPoint.op_Equality(c.Pt,b.Pt)&&c!==b;)c=c.Next;c=Math.abs(this.GetDx(b.Pt,c.Pt));return Math.max(e,f)===Math.max(g,c)&&Math.min(e,f)===Math.min(g,c)?0<this.Area(a):e>=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.x<a.Pt.x?(b=null,a=c):c.Next!==a&&c.Prev!==a&&(b=c)),c=c.Next;if(null!==b)for(;b!==c;)for(this.FirstIsBottomPt(c,b)||(a=b),b=b.Next;d.IntPoint.op_Inequality(b.Pt,a.Pt);)b=b.Next;return a};d.Clipper.prototype.GetLowermostRec=function(a,b){null===a.BottomPt&&(a.BottomPt=this.GetBottomPt(a.Pts));null===b.BottomPt&&(b.BottomPt=this.GetBottomPt(b.Pts));var c=a.BottomPt,e=b.BottomPt;return c.Pt.y>e.Pt.y?a:c.Pt.y<e.Pt.y?b:c.Pt.x<e.Pt.x?a:c.Pt.x>e.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.ctUnion:0>=e&&0>=f&&this.AddLocalMinPoly(a,b,c);break;case d.ClipType.ctDifference:(a.PolyTyp===d.PolyType.ptClip&&0<e&&
0<f||a.PolyTyp===d.PolyType.ptSubject&&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<a.Top.x?(b.Left=a.Bot.x,b.Right=a.Top.x,b.Dir=d.Direction.dLeftToRight):(b.Left=a.Top.x,b.Right=a.Bot.x,b.Dir=d.Direction.dRightToLeft)};d.Clipper.prototype.ProcessHorizontal=function(a){var b,c={Dir:null,Left:null,Right:null};this.GetHorzDirection(a,c);var e=c.Dir,f=c.Left;c=c.Right;for(var g=0===a.WindDelta,h=a,k=null;null!==h.NextInLML&&d.ClipperBase.IsHorizontal(h.NextInLML);)h=h.NextInLML;null===h.NextInLML&&(k=this.GetMaximaPair(h));
var l=this.m_Maxima;if(null!==l)if(e===d.Direction.dLeftToRight){for(;null!==l&&l.x<=a.Bot.x;)l=l.Next;null!==l&&l.x>=h.Top.x&&(l=null)}else{for(;null!==l.Next&&l.Next.x<a.Bot.x;)l=l.Next;l.x<=h.Top.x&&(l=null)}for(var n=null;;){for(var r=a===h,p=this.GetNextInAEL(a,e);null!==p;){if(null!==l)if(e===d.Direction.dLeftToRight)for(;null!==l&&l.x<p.Curr.x;)0<=a.OutIdx&&!g&&this.AddOutPt(a,new d.IntPoint2(l.x,a.Bot.y)),l=l.Next;else for(;null!==l&&l.x>p.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.x<f)break;if(p.Curr.x===a.Top.x&&null!==a.NextInLML&&p.Dx<a.NextInLML.Dx)break;if(0<=a.OutIdx&&!g){d.use_xyz&&(e===d.Direction.dLeftToRight?this.SetZ(p.Curr,a,p):this.SetZ(p.Curr,p,a));n=this.AddOutPt(a,p.Curr);for(b=this.m_SortedEdges;null!==b;){if(0<=b.OutIdx&&this.HorzSegmentsOverlap(a.Bot.x,a.Top.x,b.Bot.x,b.Top.x)){var t=this.GetLastOutPt(b);this.AddJoin(t,n,b.Top)}b=b.NextInSEL}this.AddGhostJoin(n,
a.Bot)}if(p===k&&r){0<=a.OutIdx&&this.AddLocalMaxPoly(a,k,a.Top);this.DeleteFromAEL(a);this.DeleteFromAEL(k);return}e===d.Direction.dLeftToRight?(t=new d.IntPoint2(p.Curr.x,a.Curr.y),this.IntersectEdges(a,p,t)):(t=new d.IntPoint2(p.Curr.x,a.Curr.y),this.IntersectEdges(p,a,t));t=this.GetNextInAEL(p,e);this.SwapPositionsInAEL(a,p);p=t}if(null===a.NextInLML||!d.ClipperBase.IsHorizontal(a.NextInLML))break;a=this.UpdateEdgeIntoAEL(a);0<=a.OutIdx&&this.AddOutPt(a,a.Bot);c={Dir:e,Left:f,Right:c};this.GetHorzDirection(a,
c);e=c.Dir;f=c.Left;c=c.Right}if(0<=a.OutIdx&&null===n){n=this.GetLastOutPt(a);for(b=this.m_SortedEdges;null!==b;)0<=b.OutIdx&&this.HorzSegmentsOverlap(a.Bot.x,a.Top.x,b.Bot.x,b.Top.x)&&(t=this.GetLastOutPt(b),this.AddJoin(t,n,b.Top)),b=b.NextInSEL;this.AddGhostJoin(n,a.Top)}null!==a.NextInLML?0<=a.OutIdx?(n=this.AddOutPt(a,a.Top),a=this.UpdateEdgeIntoAEL(a),0!==a.WindDelta&&(e=a.PrevInAEL,t=a.NextInAEL,null!==e&&e.Curr.x===a.Bot.x&&e.Curr.y===a.Bot.y&&0===e.WindDelta&&0<=e.OutIdx&&e.Curr.y>e.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.y<a&&(f=new d.IntPoint2(d.Clipper.TopX(b,a),a)),c=new d.IntersectNode,c.Edge1=
b,c.Edge2=e,c.Pt.x=f.x,c.Pt.y=f.y,d.use_xyz&&(c.Pt.Z=f.Z),this.m_IntersectList.push(c),this.SwapPositionsInSEL(b,e),c=!0):b=e}if(null!==b.PrevInSEL)b.PrevInSEL.NextInSEL=null;else break}this.m_SortedEdges=null}};d.Clipper.prototype.EdgesAdjacent=function(a){return a.Edge1.NextInSEL===a.Edge2||a.Edge1.PrevInSEL===a.Edge2};d.Clipper.IntersectNodeSort=function(a,b){return b.Pt.y-a.Pt.y};d.Clipper.prototype.FixupIntersectionOrder=function(){this.m_IntersectList.sort(this.m_IntersectNodeComparer);this.CopyAELToSEL();
for(var a=this.m_IntersectList.length,b=0;b<a;b++){if(!this.EdgesAdjacent(this.m_IntersectList[b])){for(var c=b+1;c<a&&!this.EdgesAdjacent(this.m_IntersectList[c]);)c++;if(c===a)return!1;var e=this.m_IntersectList[b];this.m_IntersectList[b]=this.m_IntersectList[c];this.m_IntersectList[c]=e}this.SwapPositionsInSEL(this.m_IntersectList[b].Edge1,this.m_IntersectList[b].Edge2)}return!0};d.Clipper.prototype.ProcessIntersectList=function(){for(var a=0,b=this.m_IntersectList.length;a<b;a++){var c=this.m_IntersectList[a];
this.IntersectEdges(c.Edge1,c.Edge2,c.Pt);this.SwapPositionsInAEL(c.Edge1,c.Edge2)}this.m_IntersectList.length=0};I=function(a){return 0>a?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 2147483647<a?Math.floor(a):a|0};d.Clipper.Round=u?I:G?K:Q?L:J;d.Clipper.TopX=function(a,b){return b===a.Top.y?a.Top.x:a.Bot.x+d.Clipper.Round(a.Dx*
(b-a.Bot.y))};d.Clipper.prototype.IntersectPoint=function(a,b,c){c.x=0;c.y=0;if(a.Dx===b.Dx)c.y=a.Curr.y,c.x=d.Clipper.TopX(a,c.y);else{if(0===a.Delta.x)if(c.x=a.Bot.x,d.ClipperBase.IsHorizontal(b))c.y=b.Bot.y;else{var e=b.Bot.y-b.Bot.x/b.Dx;c.y=d.Clipper.Round(c.x/b.Dx+e)}else if(0===b.Delta.x)if(c.x=b.Bot.x,d.ClipperBase.IsHorizontal(a))c.y=a.Bot.y;else{var f=a.Bot.y-a.Bot.x/a.Dx;c.y=d.Clipper.Round(c.x/a.Dx+f)}else{f=a.Bot.x-a.Bot.y*a.Dx;e=b.Bot.x-b.Bot.y*b.Dx;var g=(e-f)/(a.Dx-b.Dx);c.y=d.Clipper.Round(g);
c.x=Math.abs(a.Dx)<Math.abs(b.Dx)?d.Clipper.Round(a.Dx*g+f):d.Clipper.Round(b.Dx*g+e)}if(c.y<a.Top.y||c.y<b.Top.y){if(a.Top.y>b.Top.y)return c.y=a.Top.y,c.x=d.Clipper.TopX(b,a.Top.y),c.x<a.Top.x;c.y=b.Top.y;c.x=Math.abs(a.Dx)<Math.abs(b.Dx)?d.Clipper.TopX(a,c.y):d.Clipper.TopX(b,c.y)}c.y>a.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;b<c;b++)a[b].reverse()};d.Clipper.Orientation=function(a){return 0<=d.Clipper.Area(a)};d.Clipper.prototype.PointCount=function(a){if(null===a)return 0;var b=0,c=a;do b++,c=c.Next;while(c!==a);return b};d.Clipper.prototype.BuildResult=function(a){d.Clear(a);for(var b=0,c=this.m_PolyOuts.length;b<c;b++){var e=
this.m_PolyOuts[b];if(null!==e.Pts){e=e.Pts.Prev;var f=this.PointCount(e);if(!(2>f)){for(var g=Array(f),h=0;h<f;h++)g[h]=e.Pt,e=e.Prev;a.push(g)}}}};d.Clipper.prototype.BuildResult2=function(a){a.Clear();for(var b=0,c=this.m_PolyOuts.length;b<c;b++){var e=this.m_PolyOuts[b];var f=this.PointCount(e.Pts);if(!(e.IsOpen&&2>f||!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;h<f;h++)g.m_polygon[h]=e.Pt,e=e.Prev}}b=
0;for(c=this.m_PolyOuts.length;b<c;b++)e=this.m_PolyOuts[b],null!==e.PolyNode&&(e.IsOpen?(e.PolyNode.IsOpen=!0,a.AddChild(e.PolyNode)):null!==e.FirstLeft&&null!==e.FirstLeft.PolyNode?e.FirstLeft.PolyNode.AddChild(e.PolyNode):a.AddChild(e.PolyNode))};d.Clipper.prototype.FixupOutPolyline=function(a){for(var b=a.Pts,c=b.Prev;b!==c;)if(b=b.Next,d.IntPoint.op_Equality(b.Pt,b.Prev.Pt)){b===c&&(c=b.Prev);var e=b.Prev;e.Next=b.Next;b=b.Next.Prev=e}b===b.Prev&&(a.Pts=null)};d.Clipper.prototype.FixupOutPolygon=
function(a){var b=null;a.BottomPt=null;for(var c=a.Pts,e=this.PreserveCollinear||this.StrictlySimple;;){if(c.Prev===c||c.Prev===c.Next){a.Pts=null;return}if(d.IntPoint.op_Equality(c.Pt,c.Next.Pt)||d.IntPoint.op_Equality(c.Pt,c.Prev.Pt)||d.ClipperBase.SlopesEqual4(c.Prev.Pt,c.Pt,c.Next.Pt,this.m_UseFullRange)&&(!e||!this.Pt2IsBetweenPt1AndPt3(c.Prev.Pt,c.Pt,c.Next.Pt)))b=null,c.Prev.Next=c.Next,c=c.Next.Prev=c.Prev;else if(c===b)break;else null===b&&(b=c),c=c.Next}a.Pts=c};d.Clipper.prototype.DupOutPt=
function(a,b){var c=new d.OutPt;c.Pt.x=a.Pt.x;c.Pt.y=a.Pt.y;d.use_xyz&&(c.Pt.Z=a.Pt.Z);c.Idx=a.Idx;b?(c.Next=a.Next,c.Prev=a,a.Next.Prev=c,a.Next=c):(c.Prev=a.Prev,c.Next=a,a.Prev.Next=c,a.Prev=c);return c};d.Clipper.prototype.GetOverlap=function(a,b,c,e,d){a<b?c<e?(d.Left=Math.max(a,c),d.Right=Math.min(b,e)):(d.Left=Math.max(a,e),d.Right=Math.min(b,c)):c<e?(d.Left=Math.max(b,c),d.Right=Math.min(a,e)):(d.Left=Math.max(b,e),d.Right=Math.min(a,c));return d.Left<d.Right};d.Clipper.prototype.JoinHorz=
function(a,b,c,e,f,g){var h=a.Pt.x>b.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;b<c&&0===a[b].length;)b++;if(b===c)return new d.IntRect(0,0,0,0);var e=new d.IntRect;e.left=a[b][0].x;e.right=e.left;e.top=a[b][0].y;for(e.bottom=e.top;b<c;b++)for(var f=0,g=a[b].length;f<g;f++)a[b][f].x<e.left?e.left=a[b][f].x:a[b][f].x>e.right&&(e.right=a[b][f].x),a[b][f].y<e.top?e.top=a[b][f].y:a[b][f].y>e.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.x<c.left&&(c.left=a.Pt.x),a.Pt.x>c.right&&(c.right=a.Pt.x),a.Pt.y<c.top&&(c.top=a.Pt.y),a.Pt.y>c.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))return-1;if(d.y<a.y!==h.y<a.y)if(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;0<k===h.y>d.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;0<k===h.y>d.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))return-1;if(k<g!==n<g)if(h>=f)if(l>f)c=1-c;else{h=(h-f)*(n-g)-(l-f)*(k-g);if(0===h)return-1;0<h===n>k&&(c=1-c)}else if(l>f){h=
(h-f)*(n-g)-(l-f)*(k-g);if(0===h)return-1;0<h===n>k&&(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 0<d;c=c.Next}while(c!==a);return!0};d.Clipper.prototype.FixupFirstLefts1=function(a,b){for(var c,e,f=0,g=this.m_PolyOuts.length;f<g;f++)c=this.m_PolyOuts[f],e=d.Clipper.ParseFirstLeft(c.FirstLeft),null!==c.Pts&&e===a&&this.Poly2ContainsPoly1(c.Pts,b.Pts)&&(c.FirstLeft=b)};d.Clipper.prototype.FixupFirstLefts2=
function(a,b){for(var c=b.FirstLeft,e,f,g=0,h=this.m_PolyOuts.length;g<h;g++)if(e=this.m_PolyOuts[g],null!==e.Pts&&e!==b&&e!==a&&(f=d.Clipper.ParseFirstLeft(e.FirstLeft),f===c||f===a||f===b))if(this.Poly2ContainsPoly1(e.Pts,a.Pts))e.FirstLeft=a;else if(this.Poly2ContainsPoly1(e.Pts,b.Pts))e.FirstLeft=b;else if(e.FirstLeft===a||e.FirstLeft===b)e.FirstLeft=c};d.Clipper.prototype.FixupFirstLefts3=function(a,b){for(var c,e,f=0,g=this.m_PolyOuts.length;f<g;f++)c=this.m_PolyOuts[f],e=d.Clipper.ParseFirstLeft(c.FirstLeft),
null!==c.Pts&&e===a&&(c.FirstLeft=b)};d.Clipper.ParseFirstLeft=function(a){for(;null!==a&&null===a.Pts;)a=a.FirstLeft;return a};d.Clipper.prototype.JoinCommonEdges=function(){for(var a=0,b=this.m_Joins.length;a<b;a++){var c=this.m_Joins[a],d=this.GetOutRec(c.OutPt1.Idx),f=this.GetOutRec(c.OutPt2.Idx);if(null!==d.Pts&&null!==f.Pts&&!d.IsOpen&&!f.IsOpen){var g=d===f?d:this.OutRec1RightOfOutRec2(d,f)?f:this.OutRec1RightOfOutRec2(f,d)?d:this.GetLowermostRec(d,f);this.JoinPoints(c,d,f)&&(d===f?(d.Pts=
c.OutPt1,d.BottomPt=null,f=this.CreateOutRec(),f.Pts=c.OutPt2,this.UpdateOutPtIdxs(f),this.Poly2ContainsPoly1(f.Pts,d.Pts)?(f.IsHole=!d.IsHole,f.FirstLeft=d,this.m_UsingPolyTree&&this.FixupFirstLefts2(f,d),(f.IsHole^this.ReverseSolution)==0<this.Area$1(f)&&this.ReversePolyPtLinks(f.Pts)):this.Poly2ContainsPoly1(d.Pts,f.Pts)?(f.IsHole=d.IsHole,d.IsHole=!f.IsHole,f.FirstLeft=d.FirstLeft,d.FirstLeft=f,this.m_UsingPolyTree&&this.FixupFirstLefts2(d,f),(d.IsHole^this.ReverseSolution)==0<this.Area$1(d)&&
this.ReversePolyPtLinks(d.Pts)):(f.IsHole=d.IsHole,f.FirstLeft=d.FirstLeft,this.m_UsingPolyTree&&this.FixupFirstLefts1(d,f))):(f.Pts=null,f.BottomPt=null,f.Idx=d.Idx,d.IsHole=g.IsHole,g===f&&(d.FirstLeft=f.FirstLeft),f.FirstLeft=d,this.m_UsingPolyTree&&this.FixupFirstLefts3(f,d)))}}};d.Clipper.prototype.UpdateOutPtIdxs=function(a){var b=a.Pts;do b.Idx=a.Idx,b=b.Prev;while(b!==a.Pts)};d.Clipper.prototype.DoSimplePolygons=function(){for(var a=0;a<this.m_PolyOuts.length;){var b=this.m_PolyOuts[a++],
c=b.Pts;if(null!==c&&!b.IsOpen){do{for(var e=c.Next;e!==b.Pts;){if(d.IntPoint.op_Equality(c.Pt,e.Pt)&&e.Next!==c&&e.Prev!==c){var f=c.Prev,g=e.Prev;c.Prev=g;g.Next=c;e.Prev=f;f.Next=e;b.Pts=c;f=this.CreateOutRec();f.Pts=e;this.UpdateOutPtIdxs(f);this.Poly2ContainsPoly1(f.Pts,b.Pts)?(f.IsHole=!b.IsHole,f.FirstLeft=b,this.m_UsingPolyTree&&this.FixupFirstLefts2(f,b)):this.Poly2ContainsPoly1(b.Pts,f.Pts)?(f.IsHole=b.IsHole,b.IsHole=!f.IsHole,f.FirstLeft=b.FirstLeft,b.FirstLeft=f,this.m_UsingPolyTree&&
this.FixupFirstLefts2(b,f)):(f.IsHole=b.IsHole,f.FirstLeft=b.FirstLeft,this.m_UsingPolyTree&&this.FixupFirstLefts1(b,f));e=c}e=e.Next}c=c.Next}while(c!==b.Pts)}}};d.Clipper.Area=function(a){if(!Array.isArray(a))return 0;var b=a.length;if(3>b)return 0;for(var c=0,d=0,f=b-1;d<b;++d)c+=(a[f].x+a[d].x)*(a[f].y-a[d].y),f=d;return.5*-c};d.Clipper.prototype.Area=function(a){var b=a;if(null===a)return 0;var c=0;do c+=(a.Prev.Pt.x+a.Pt.x)*(a.Prev.Pt.y-a.Pt.y),a=a.Next;while(a!==b);return.5*c};d.Clipper.prototype.Area$1=
function(a){return this.Area(a.Pts)};d.Clipper.SimplifyPolygon=function(a,b){var c=[],e=new d.Clipper(0);e.StrictlySimple=!0;e.AddPath(a,d.PolyType.ptSubject,!0);e.Execute(d.ClipType.ctUnion,c,b,b);return c};d.Clipper.SimplifyPolygons=function(a,b){"undefined"===typeof b&&(b=d.PolyFillType.pftEvenOdd);var c=[],e=new d.Clipper(0);e.StrictlySimple=!0;e.AddPaths(a,d.PolyType.ptSubject,!0);e.Execute(d.ClipType.ctUnion,c,b,b);return c};d.Clipper.DistanceSqrd=function(a,b){var c=a.x-b.x,d=a.y-b.y;return c*
c+d*d};d.Clipper.DistanceFromLineSqrd=function(a,b,c){var d=b.y-c.y;c=c.x-b.x;b=d*b.x+c*b.y;b=d*a.x+c*a.y-b;return b*b/(d*d+c*c)};d.Clipper.SlopesNearCollinear=function(a,b,c,e){return Math.abs(a.x-b.x)>Math.abs(a.y-b.y)?a.x>b.x===a.x<c.x?d.Clipper.DistanceFromLineSqrd(a,b,c)<e:b.x>a.x===b.x<c.x?d.Clipper.DistanceFromLineSqrd(b,a,c)<e:d.Clipper.DistanceFromLineSqrd(c,a,b)<e:a.y>b.y===a.y<c.y?d.Clipper.DistanceFromLineSqrd(a,b,c)<e:b.y>a.y===b.y<c.y?d.Clipper.DistanceFromLineSqrd(b,a,c)<e:d.Clipper.DistanceFromLineSqrd(c,
a,b)<e};d.Clipper.PointsAreClose=function(a,b,c){var d=a.x-b.x;a=a.y-b.y;return d*d+a*a<=c};d.Clipper.ExcludeOp=function(a){var b=a.Prev;b.Next=a.Next;a.Next.Prev=b;b.Idx=0;return b};d.Clipper.CleanPolygon=function(a,b){"undefined"===typeof b&&(b=1.415);var c=a.length;if(0===c)return[];for(var e=Array(c),f=0;f<c;++f)e[f]=new d.OutPt;for(f=0;f<c;++f)e[f].Pt=a[f],e[f].Next=e[(f+1)%c],e[f].Next.Prev=e[f],e[f].Idx=0;f=b*b;for(e=e[0];0===e.Idx&&e.Next!==e.Prev;)d.Clipper.PointsAreClose(e.Pt,e.Prev.Pt,
f)?(e=d.Clipper.ExcludeOp(e),c--):d.Clipper.PointsAreClose(e.Prev.Pt,e.Next.Pt,f)?(d.Clipper.ExcludeOp(e.Next),e=d.Clipper.ExcludeOp(e),c-=2):d.Clipper.SlopesNearCollinear(e.Prev.Pt,e.Pt,e.Next.Pt,f)?(e=d.Clipper.ExcludeOp(e),c--):(e.Idx=1,e=e.Next);3>c&&(c=0);var g=Array(c);for(f=0;f<c;++f)g[f]=new d.IntPoint1(e.Pt),e=e.Next;return g};d.Clipper.CleanPolygons=function(a,b){for(var c=Array(a.length),e=0,f=a.length;e<f;e++)c[e]=d.Clipper.CleanPolygon(a[e],b);return c};d.Clipper.Minkowski=function(a,
b,c,e){e=e?1:0;var f=a.length,g=b.length,h=[];if(c)for(c=0;c<g;c++){var k=Array(f);for(var l=0,n=a.length,r=a[l];l<n;l++,r=a[l])k[l]=new d.IntPoint2(b[c].x+r.x,b[c].y+r.y);h.push(k)}else for(c=0;c<g;c++){k=Array(f);l=0;n=a.length;for(r=a[l];l<n;l++,r=a[l])k[l]=new d.IntPoint2(b[c].x-r.x,b[c].y-r.y);h.push(k)}a=[];for(c=0;c<g-1+e;c++)for(l=0;l<f;l++)b=[],b.push(h[c%g][l%f]),b.push(h[(c+1)%g][l%f]),b.push(h[(c+1)%g][(l+1)%f]),b.push(h[c%g][(l+1)%f]),d.Clipper.Orientation(b)||b.reverse(),a.push(b);return a};
d.Clipper.MinkowskiSum=function(a,b,c){if(b[0]instanceof Array){var e=b;var f=new d.Paths;b=new d.Clipper;for(var g=0;g<e.length;++g){var h=d.Clipper.Minkowski(a,e[g],!0,c);b.AddPaths(h,d.PolyType.ptSubject,!0);c&&(h=d.Clipper.TranslatePath(e[g],a[0]),b.AddPath(h,d.PolyType.ptClip,!0))}b.Execute(d.ClipType.ctUnion,f,d.PolyFillType.pftNonZero,d.PolyFillType.pftNonZero);return f}e=d.Clipper.Minkowski(a,b,!0,c);b=new d.Clipper;b.AddPaths(e,d.PolyType.ptSubject,!0);b.Execute(d.ClipType.ctUnion,e,d.PolyFillType.pftNonZero,
d.PolyFillType.pftNonZero);return e};d.Clipper.TranslatePath=function(a,b){for(var c=new d.Path,e=0;e<a.length;e++)c.push(new d.IntPoint2(a[e].x+b.x,a[e].y+b.y));return c};d.Clipper.MinkowskiDiff=function(a,b){var c=d.Clipper.Minkowski(a,b,!1,!0),e=new d.Clipper;e.AddPaths(c,d.PolyType.ptSubject,!0);e.Execute(d.ClipType.ctUnion,c,d.PolyFillType.pftNonZero,d.PolyFillType.pftNonZero);return c};d.Clipper.PolyTreeToPaths=function(a){var b=[];d.Clipper.AddPolyNodeToPaths(a,d.Clipper.NodeType.ntAny,b);
return b};d.Clipper.AddPolyNodeToPaths=function(a,b,c){var e=!0;switch(b){case d.Clipper.NodeType.ntOpen:return;case d.Clipper.NodeType.ntClosed:e=!a.IsOpen}0<a.m_polygon.length&&e&&c.push(a.m_polygon);e=0;a=a.Childs();for(var f=a.length,g=a[e];e<f;e++,g=a[e])d.Clipper.AddPolyNodeToPaths(g,b,c)};d.Clipper.OpenPathsFromPolyTree=function(a){for(var b=new d.Paths,c=0,e=a.ChildCount();c<e;c++)a.Childs()[c].IsOpen&&b.push(a.Childs()[c].m_polygon);return b};d.Clipper.ClosedPathsFromPolyTree=function(a){var b=
new d.Paths;d.Clipper.AddPolyNodeToPaths(a,d.Clipper.NodeType.ntClosed,b);return b};v(d.Clipper,d.ClipperBase);d.Clipper.NodeType={ntAny:0,ntOpen:1,ntClosed:2};d.ClipperOffset=function(a,b){"undefined"===typeof a&&(a=2);"undefined"===typeof b&&(b=d.ClipperOffset.def_arc_tolerance);this.m_destPolys=new d.Paths;this.m_srcPoly=new d.Path;this.m_destPoly=new d.Path;this.m_normals=[];this.m_StepsPerRad=this.m_miterLim=this.m_cos=this.m_sin=this.m_sinA=this.m_delta=0;this.m_lowest=new d.IntPoint0;this.m_polyNodes=
new d.PolyNode;this.MiterLimit=a;this.ArcTolerance=b;this.m_lowest.x=-1};d.ClipperOffset.two_pi=6.28318530717959;d.ClipperOffset.def_arc_tolerance=.25;d.ClipperOffset.prototype.Clear=function(){d.Clear(this.m_polyNodes.Childs());this.m_lowest.x=-1};d.ClipperOffset.Round=d.Clipper.Round;d.ClipperOffset.prototype.AddPath=function(a,b,c){var e=a.length-1;if(!(0>e)){var f=new d.PolyNode;f.m_jointype=b;f.m_endtype=c;if(c===d.EndType.etClosedLine||c===d.EndType.etClosedPolygon)for(;0<e&&d.IntPoint.op_Equality(a[0],
a[e]);)e--;f.m_polygon.push(a[0]);var g=0;b=0;for(var h=1;h<=e;h++)d.IntPoint.op_Inequality(f.m_polygon[g],a[h])&&(g++,f.m_polygon.push(a[h]),a[h].y>f.m_polygon[b].y||a[h].y===f.m_polygon[b].y&&a[h].x<f.m_polygon[b].x)&&(b=g);if(!(c===d.EndType.etClosedPolygon&&2>g)&&(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<a.x)this.m_lowest=new d.IntPoint2(this.m_polyNodes.ChildCount()-1,b)}};d.ClipperOffset.prototype.AddPaths=function(a,b,c){for(var d=0,f=a.length;d<f;d++)this.AddPath(a[d],b,c)};d.ClipperOffset.prototype.FixOrientations=function(){if(0<=this.m_lowest.x&&!d.Clipper.Orientation(this.m_polyNodes.Childs()[this.m_lowest.x].m_polygon))for(var a=0;a<this.m_polyNodes.ChildCount();a++){var b=this.m_polyNodes.Childs()[a];(b.m_endtype===d.EndType.etClosedPolygon||
b.m_endtype===d.EndType.etClosedLine&&d.Clipper.Orientation(b.m_polygon))&&b.m_polygon.reverse()}else for(a=0;a<this.m_polyNodes.ChildCount();a++)b=this.m_polyNodes.Childs()[a],b.m_endtype!==d.EndType.etClosedLine||d.Clipper.Orientation(b.m_polygon)||b.m_polygon.reverse()};d.ClipperOffset.GetUnitNormal=function(a,b){var c=b.x-a.x,e=b.y-a.y;if(0===c&&0===e)return new d.DoublePoint2(0,0);var f=1/Math.sqrt(c*c+e*e);return new d.DoublePoint2(e*f,-(c*f))};d.ClipperOffset.prototype.DoOffset=function(a){var b;
this.m_destPolys=[];this.m_delta=a;if(d.ClipperBase.near_zero(a))for(var c=0;c<this.m_polyNodes.ChildCount();c++){var e=this.m_polyNodes.Childs()[c];e.m_endtype===d.EndType.etClosedPolygon&&this.m_destPolys.push(e.m_polygon)}else{this.m_miterLim=2<this.MiterLimit?2/(this.MiterLimit*this.MiterLimit):.5;var f=3.14159265358979/Math.acos(1-(0>=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<this.m_polyNodes.ChildCount();c++){e=this.m_polyNodes.Childs()[c];this.m_srcPoly=e.m_polygon;var g=this.m_srcPoly.length;if(!(0===g||0>=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;b<g-1;b++)this.m_normals.push(d.ClipperOffset.GetUnitNormal(this.m_srcPoly[b],this.m_srcPoly[b+1]));e.m_endtype===d.EndType.etClosedLine||e.m_endtype===d.EndType.etClosedPolygon?
this.m_normals.push(d.ClipperOffset.GetUnitNormal(this.m_srcPoly[g-1],this.m_srcPoly[0])):this.m_normals.push(new d.DoublePoint1(this.m_normals[g-2]));if(e.m_endtype===d.EndType.etClosedPolygon)for(h=g-1,b=0;b<g;b++)h=this.OffsetPoint(b,h,e.m_jointype);else if(e.m_endtype===d.EndType.etClosedLine){h=g-1;for(b=0;b<g;b++)h=this.OffsetPoint(b,h,e.m_jointype);this.m_destPolys.push(this.m_destPoly);this.m_destPoly=[];h=this.m_normals[g-1];for(b=g-1;0<b;b--)this.m_normals[b]=new d.DoublePoint2(-this.m_normals[b-
1].x,-this.m_normals[b-1].y);this.m_normals[0]=new d.DoublePoint2(-h.x,-h.y);h=0;for(b=g-1;0<=b;b--)h=this.OffsetPoint(b,h,e.m_jointype)}else{h=0;for(b=1;b<g-1;++b)h=this.OffsetPoint(b,h,e.m_jointype);e.m_endtype===d.EndType.etOpenButt?(b=g-1,h=new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[b].x+this.m_normals[b].x*a),d.ClipperOffset.Round(this.m_srcPoly[b].y+this.m_normals[b].y*a)),this.m_destPoly.push(h),h=new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[b].x-this.m_normals[b].x*a),d.ClipperOffset.Round(this.m_srcPoly[b].y-
this.m_normals[b].y*a)),this.m_destPoly.push(h)):(b=g-1,h=g-2,this.m_sinA=0,this.m_normals[b]=new d.DoublePoint2(-this.m_normals[b].x,-this.m_normals[b].y),e.m_endtype===d.EndType.etOpenSquare?this.DoSquare(b,h):this.DoRound(b,h));for(b=g-1;0<b;b--)this.m_normals[b]=new d.DoublePoint2(-this.m_normals[b-1].x,-this.m_normals[b-1].y);this.m_normals[0]=new d.DoublePoint2(-this.m_normals[1].x,-this.m_normals[1].y);h=g-1;for(b=h-1;0<b;--b)h=this.OffsetPoint(b,h,e.m_jointype);e.m_endtype===d.EndType.etOpenButt?
(h=new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[0].x-this.m_normals[0].x*a),d.ClipperOffset.Round(this.m_srcPoly[0].y-this.m_normals[0].y*a)),this.m_destPoly.push(h),h=new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[0].x+this.m_normals[0].x*a),d.ClipperOffset.Round(this.m_srcPoly[0].y+this.m_normals[0].y*a)),this.m_destPoly.push(h)):(this.m_sinA=0,e.m_endtype===d.EndType.etOpenSquare?this.DoSquare(0,1):this.DoRound(0,1))}}this.m_destPolys.push(this.m_destPoly)}}}};d.ClipperOffset.prototype.Execute=
function(){var a=arguments;if(a[0]instanceof d.PolyTree){var b=a[0];var c=a[1];b.Clear();this.FixOrientations();this.DoOffset(c);a=new d.Clipper(0);a.AddPaths(this.m_destPolys,d.PolyType.ptSubject,!0);if(0<c)a.Execute(d.ClipType.ctUnion,b,d.PolyFillType.pftPositive,d.PolyFillType.pftPositive);else{var e=d.Clipper.GetBounds(this.m_destPolys);c=new d.Path;c.push(new d.IntPoint2(e.left-10,e.bottom+10));c.push(new d.IntPoint2(e.right+10,e.bottom+10));c.push(new d.IntPoint2(e.right+10,e.top-10));c.push(new d.IntPoint2(e.left-
10,e.top-10));a.AddPath(c,d.PolyType.ptSubject,!0);a.ReverseSolution=!0;a.Execute(d.ClipType.ctUnion,b,d.PolyFillType.pftNegative,d.PolyFillType.pftNegative);if(1===b.ChildCount()&&0<b.Childs()[0].ChildCount())for(a=b.Childs()[0],b.Childs()[0]=a.Childs()[0],b.Childs()[0].m_Parent=b,c=1;c<a.ChildCount();c++)b.AddChild(a.Childs()[c]);else b.Clear()}}else b=a[0],c=a[1],d.Clear(b),this.FixOrientations(),this.DoOffset(c),a=new d.Clipper(0),a.AddPaths(this.m_destPolys,d.PolyType.ptSubject,!0),0<c?a.Execute(d.ClipType.ctUnion,
b,d.PolyFillType.pftPositive,d.PolyFillType.pftPositive):(e=d.Clipper.GetBounds(this.m_destPolys),c=new d.Path,c.push(new d.IntPoint2(e.left-10,e.bottom+10)),c.push(new d.IntPoint2(e.right+10,e.bottom+10)),c.push(new d.IntPoint2(e.right+10,e.top-10)),c.push(new d.IntPoint2(e.left-10,e.top-10)),a.AddPath(c,d.PolyType.ptSubject,!0),a.ReverseSolution=!0,a.Execute(d.ClipType.ctUnion,b,d.PolyFillType.pftNegative,d.PolyFillType.pftNegative),0<b.length&&b.splice(0,1))};d.ClipperOffset.prototype.OffsetPoint=
function(a,b,c){this.m_sinA=this.m_normals[b].x*this.m_normals[a].y-this.m_normals[a].x*this.m_normals[b].y;if(1>Math.abs(this.m_sinA*this.m_delta)){if(0<this.m_normals[b].x*this.m_normals[a].x+this.m_normals[a].y*this.m_normals[b].y)return 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))),b}else 1<this.m_sinA?this.m_sinA=1:-1>this.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;h<c;++h)this.m_destPoly.push(new d.IntPoint2(d.ClipperOffset.Round(this.m_srcPoly[a].x+
e*this.m_delta),d.ClipperOffset.Round(this.m_srcPoly[a].y+f*this.m_delta))),g=e,e=e*this.m_cos-this.m_sin*f,f=g*this.m_sin+f*this.m_cos;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)))};d.Error=function(a){try{throw Error(a);}catch(b){alert(b.message)}};d.JS={};d.JS.AreaOfPolygon=function(a,b){b||(b=1);return d.Clipper.Area(a)/(b*b)};d.JS.AreaOfPolygons=function(a,
b){b||(b=1);for(var c=0,e=0;e<a.length;e++)c+=d.Clipper.Area(a[e]);return c/(b*b)};d.JS.BoundsOfPath=function(a,b){return d.JS.BoundsOfPaths([a],b)};d.JS.BoundsOfPaths=function(a,b){b||(b=1);var c=d.Clipper.GetBounds(a);c.left/=b;c.bottom/=b;c.right/=b;c.top/=b;return c};d.JS.Clean=function(a,b){if(!(a instanceof Array))return[];var c=a[0]instanceof Array;a=d.JS.Clone(a);if("number"!==typeof b||null===b)return d.Error("Delta is not a number in Clean()."),a;if(0===a.length||1===a.length&&0===a[0].length||
0>b)return a;c||(a=[a]);for(var e=a.length,f,g,h,k,l,n,r,p=[],t=0;t<e;t++)if(g=a[t],f=g.length,0!==f)if(3>f)h=g,p.push(h);else{h=g;k=b*b;l=g[0];for(r=n=1;r<f;r++)(g[r].x-l.x)*(g[r].x-l.x)+(g[r].y-l.y)*(g[r].y-l.y)<=k||(h[n]=g[r],l=g[r],n++);l=g[n-1];(g[0].x-l.x)*(g[0].x-l.x)+(g[0].y-l.y)*(g[0].y-l.y)<=k&&n--;n<f&&h.splice(n,f-n);h.length&&p.push(h)}!c&&p.length?p=p[0]:c||0!==p.length?c&&0===p.length&&(p=[[]]):p=[];return p};d.JS.Clone=function(a){if(!(a instanceof Array)||0===a.length)return[];if(1===
a.length&&0===a[0].length)return[[]];var b=a[0]instanceof Array;b||(a=[a]);var c=a.length,d,f,g=Array(c);for(d=0;d<c;d++){var h=a[d].length;var k=Array(h);for(f=0;f<h;f++)k[f]={X:a[d][f].x,Y:a[d][f].y};g[d]=k}b||(g=g[0]);return g};d.JS.Lighten=function(a,b){if(!(a instanceof Array))return[];if("number"!==typeof b||null===b)return d.Error("Tolerance is not a number in Lighten()."),d.JS.Clone(a);if(0===a.length||1===a.length&&0===a[0].length||0>b)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;e<h;e++){var n=a[e];var r=n.length;if(0!==r){for(g=0;1E6>g;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;f<r-2;f++){var q=n[f];var v=n[f+1];var w=n[f+2];var x=q.x;var y=q.y;q=w.x-x;var A=w.y-y;if(0!==q||0!==A){var z=((v.x-x)*q+(v.y-y)*A)/(q*q+A*A);1<z?(x=w.x,y=w.y):0<z&&(x+=q*z,y+=A*z)}q=v.x-x;A=v.y-y;w=q*q+A*A;w<=k&&(u[f+1]=1,f++)}p.push({X:n[0].x,Y:n[0].y});for(f=
1;f<r-1;f++)u[f]||p.push({X:n[f].x,Y:n[f].y});p.push({X:n[r-1].x,Y:n[r-1].y});t&&n.pop();if(u.length)n=p;else break}r=p.length;p[r-1].x===p[0].x&&p[r-1].y===p[0].y&&p.pop();2<p.length&&l.push(p)}}c||(l=l[0]);"undefined"===typeof l&&(l=[]);return l};d.JS.PerimeterOfPath=function(a,b,c){if("undefined"===typeof a)return 0;var d=Math.sqrt,f=0,g=a.length;if(2>g)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<a.length;f++)e+=d.JS.PerimeterOfPath(a[f],b,c);return e};d.JS.ScaleDownPath=function(a,b){var c;b||(b=1);for(c=a.length;c--;){var d=a[c];d.x/=b;d.y/=b}};d.JS.ScaleDownPaths=function(a,b){var c,d;b||(b=1);for(c=a.length;c--;)for(d=a[c].length;d--;){var f=a[c][d];f.x/=b;f.y/=b}};d.JS.ScaleUpPath=function(a,b){var c,d=Math.round;b||(b=1);for(c=a.length;c--;){var f=a[c];f.x=d(f.x*b);f.y=d(f.y*b)}};d.JS.ScaleUpPaths=function(a,b){var c,
d,f=Math.round;b||(b=1);for(c=a.length;c--;)for(d=a[c].length;d--;){var g=a[c][d];g.x=f(g.x*b);g.y=f(g.y*b)}};d.ExPolygons=function(){return[]};d.ExPolygon=function(){this.holes=this.outer=null};d.JS.AddOuterPolyNodeToExPolygons=function(a,b){var c=new d.ExPolygon;c.outer=a.Contour();var e=a.Childs(),f=e.length;c.holes=Array(f);var g,h;for(g=0;g<f;g++){var k=e[g];c.holes[g]=k.Contour();var l=0;var n=k.Childs();for(h=n.length;l<h;l++)k=n[l],d.JS.AddOuterPolyNodeToExPolygons(k,b)}b.push(c)};d.JS.ExPolygonsToPaths=
function(a){var b,c,e=new d.Paths;var f=0;for(b=a.length;f<b;f++){e.push(a[f].outer);var g=0;for(c=a[f].holes.length;g<c;g++)e.push(a[f].holes[g])}return e};d.JS.PolyTreeToExPolygons=function(a){var b=new d.ExPolygons,c;var e=0;var f=a.Childs();for(c=f.length;e<c;e++)a=f[e],d.JS.AddOuterPolyNodeToExPolygons(a,b);return b}})();

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,5 @@
/* eslint-disable */
/*
The Rocket JavaScript library.
This library is free software: you can redistribute it and/or modify

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
/* eslint-disable */
!function(){"use strict";var b=Math.PI,d=Math.sin,e=Math.cos,f=Math.tan,g=Math.asin,h=Math.atan2,i=Math.acos,c=b/180;function j(a){return new Date((a+.5-2440588)*864e5)}function k(a){var b;return a.valueOf()/864e5-.5+2440588-2451545}var l=23.4397*c;function m(a,b){return h(d(a)*e(l)-f(b)*d(l),e(a))}function n(b,a){return g(d(a)*e(l)+e(a)*d(l)*d(b))}function o(a,b,c){return h(d(a),e(a)*d(b)-f(c)*e(b))}function p(c,a,b){return g(d(a)*d(b)+e(a)*e(b)*e(c))}function q(a,b){return c*(280.16+360.9856235*a)-b}function r(a){return c*(357.5291+.98560028*a)}function s(a){var e=c*(1.9148*d(a)+.02*d(2*a)+3e-4*d(3*a));return a+e+102.9372*c+b}function t(b){var c=r(b),a=s(c);return{dec:n(a,0),ra:m(a,0)}}var a={};a.getPosition=function(f,g,h){var b=c*g,d=k(f),a=t(d),e=q(d,-(c*h))-a.ra;return{azimuth:o(e,b,a.dec),altitude:p(e,b,a.dec)}};var u=a.times=[[-0.833,"sunrise","sunset"],[-0.3,"sunriseEnd","sunsetStart"],[-6,"dawn","dusk"],[-12,"nauticalDawn","nauticalDusk"],[-18,"nightEnd","night"],[6,"goldenHourEnd","goldenHour"]];function v(a,c,d){return 9e-4+(a+c)/(2*b)+d}function w(a,b,c){return 2451545+a+.0053*d(b)-.0069*d(2*c)}function x(f,g,h,j,k,l,m){var c,a,b,n=(c=f,a=h,b=j,i((d(c)-d(a)*d(b))/(e(a)*e(b)))),o=v(n,g,k);return w(o,l,m)}function y(a){var b=c*(134.963+13.064993*a),f=c*(218.316+13.176396*a)+6.289*c*d(b),g=5.128*c*d(c*(93.272+13.22935*a)),h=385001-20905*e(b);return{ra:m(f,g),dec:n(f,g),dist:h}}function z(a,b){return new Date(a.valueOf()+864e5*b/24)}a.addTime=function(a,b,c){u.push([a,b,c])},a.getTimes=function(y,z,A,e){e=e||0;var F,B,G,a,o,f,g,p,h=-(c*A),C=c*z,D=-2.076*Math.sqrt(e)/60,q=Math.round((B=k(y))-9e-4-h/(2*b)),t=v(0,h,q),i=r(t),l=s(i),E=n(l,0),d=w(t,i,l),m={solarNoon:j(d),nadir:j(d-.5)};for(a=0,o=u.length;a<o;a+=1)g=x(((f=u[a])[0]+D)*c,h,C,E,q,i,l),p=d-(g-d),m[f[1]]=j(p),m[f[2]]=j(g);return m},a.getMoonPosition=function(m,n,r){var b,s=-(c*r),i=c*n,l=k(m),a=y(l),g=q(l,s)-a.ra,j=p(g,i,a.dec),t=h(d(g),f(i)*e(a.dec)-d(a.dec)*e(g));return j+=((b=j)<0&&(b=0),2967e-7/Math.tan(b+.00312536/(b+.08901179))),{azimuth:o(g,i,a.dec),altitude:j,distance:a.dist,parallacticAngle:t}},a.getMoonIllumination=function(l){var c=k(l||new Date),a=t(c),b=y(c),f=i(d(a.dec)*d(b.dec)+e(a.dec)*e(b.dec)*e(a.ra-b.ra)),g=h(149598e3*d(f),b.dist-149598e3*e(f)),j=h(e(a.dec)*d(a.ra-b.ra),d(a.dec)*e(b.dec)-e(a.dec)*d(b.dec)*e(a.ra-b.ra));return{fraction:(1+e(g))/2,phase:.5+.5*g*(j<0?-1:1)/Math.PI,angle:j}},a.getMoonTimes=function(w,r,s,x){var e=new Date(w);x?e.setUTCHours(0,0,0,0):e.setHours(0,0,0,0);for(var m,n,f,g,h,i,j,o,t,k,b,l,v,u=.133*c,p=a.getMoonPosition(e,r,s).altitude-u,d=1;d<=24&&(m=a.getMoonPosition(z(e,d),r,s).altitude-u,n=a.getMoonPosition(z(e,d+1),r,s).altitude-u,h=(p+n)/2-m,i=(n-p)/2,j=-i/(2*h),o=(h*j+i)*j+m,t=i*i-4*h*m,k=0,t>=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}()

136
js/lib/threejs/threejs.js Normal file

File diff suppressed because one or more lines are too long