{"version":3,"file":"xkt-DHSc23ad.js","sources":["../../node_modules/@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js"],"sourcesContent":["/** @private */\nclass Map$1 {\n\n constructor(items, baseId) {\n this.items = items || [];\n this._lastUniqueId = (baseId || 0) + 1;\n }\n\n /**\n * Usage:\n *\n * id = myMap.addItem(\"foo\") // ID internally generated\n * id = myMap.addItem(\"foo\", \"bar\") // ID is \"foo\"\n */\n addItem() {\n let item;\n if (arguments.length === 2) {\n const id = arguments[0];\n item = arguments[1];\n if (this.items[id]) { // Won't happen if given ID is string\n throw \"ID clash: '\" + id + \"'\";\n }\n this.items[id] = item;\n return id;\n\n } else {\n item = arguments[0] || {};\n while (true) {\n const findId = this._lastUniqueId++;\n if (!this.items[findId]) {\n this.items[findId] = item;\n return findId;\n }\n }\n }\n }\n\n removeItem(id) {\n const item = this.items[id];\n delete this.items[id];\n return item;\n }\n}\n\nconst idMap = new Map$1();\n\n/**\n * Internal data class that represents the state of a menu or a submenu.\n * @private\n */\nclass Menu {\n constructor(id) {\n this.id = id;\n this.parentItem = null; // Set to an Item when this Menu is a submenu\n this.groups = [];\n this.menuElement = null;\n this.shown = false;\n this.mouseOver = 0;\n }\n}\n\n/**\n * Internal data class that represents a group of Items in a Menu.\n * @private\n */\nclass Group {\n constructor() {\n this.items = [];\n }\n}\n\n/**\n * Internal data class that represents the state of a menu item.\n * @private\n */\nclass Item {\n constructor(id, getTitle, doAction, getEnabled, getShown) {\n this.id = id;\n this.getTitle = getTitle;\n this.doAction = doAction;\n this.getEnabled = getEnabled;\n this.getShown = getShown;\n this.itemElement = null;\n this.subMenu = null;\n this.enabled = true;\n }\n}\n\n/**\n * @desc A customizable HTML context menu.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_TreeViewPlugin_Custom)\n *\n * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_TreeViewPlugin_Custom)]\n *\n * ## Overview\n *\n * * A pure JavaScript, lightweight context menu\n * * Dynamically configure menu items\n * * Dynamically enable or disable items\n * * Dynamically show or hide items\n * * Supports cascading sub-menus\n * * Configure custom style with CSS (see examples above)\n *\n * ## Usage\n *\n * In the example below we'll create a ````ContextMenu```` that pops up whenever we right-click on an {@link Entity} within\n * our {@link Scene}.\n *\n * First, we'll create the ````ContextMenu````, configuring it with a list of menu items.\n *\n * Each item has:\n *\n * * a ````title```` for the item,\n * * a ````doAction()```` callback to fire when the item's title is clicked,\n * * an optional ````getShown()```` callback that indicates if the item should shown in the menu or not, and\n * * an optional ````getEnabled()```` callback that indicates if the item should be shown enabled in the menu or not.\n *\n *
\n *\n * The ````getShown()```` and ````getEnabled()```` callbacks are invoked whenever the menu is shown.\n *\n * When an item's ````getShown()```` callback\n * returns ````true````, then the item is shown. When it returns ````false````, then the item is hidden. An item without\n * a ````getShown()```` callback is always shown.\n *\n * When an item's ````getEnabled()```` callback returns ````true````, then the item is enabled and clickable (as long as it's also shown). When it\n * returns ````false````, then the item is disabled and cannot be clicked. An item without a ````getEnabled()````\n * callback is always enabled and clickable.\n *\n * Note how the ````doAction()````, ````getShown()```` and ````getEnabled()```` callbacks accept a ````context````\n * object. That must be set on the ````ContextMenu```` before we're able to we show it. The context object can be anything. In this example,\n * we'll use the context object to provide the callbacks with the Entity that we right-clicked.\n *\n * We'll also initially enable the ````ContextMenu````.\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_Custom)]\n *\n * ````javascript\n * const canvasContextMenu = new ContextMenu({\n *\n * enabled: true,\n *\n * items: [\n * [\n * {\n * title: \"Hide Object\",\n * getEnabled: (context) => {\n * return context.entity.visible; // Can't hide entity if already hidden\n * },\n * doAction: function (context) {\n * context.entity.visible = false;\n * }\n * }\n * ],\n * [\n * {\n * title: \"Select Object\",\n * getEnabled: (context) => {\n * return (!context.entity.selected); // Can't select an entity that's already selected\n * },\n * doAction: function (context) {\n * context.entity.selected = true;\n * }\n * }\n * ],\n * [\n * {\n * title: \"X-Ray Object\",\n * getEnabled: (context) => {\n * return (!context.entity.xrayed); // Can't X-ray an entity that's already X-rayed\n * },\n * doAction: (context) => {\n * context.entity.xrayed = true;\n * }\n * }\n * ]\n * ]\n * });\n * ````\n *\n * Next, we'll make the ````ContextMenu```` appear whenever we right-click on an Entity. Whenever we right-click\n * on the canvas, we'll attempt to pick the Entity at those mouse coordinates. If we succeed, we'll feed the\n * Entity into ````ContextMenu```` via the context object, then show the ````ContextMenu````.\n *\n * From there, each ````ContextMenu```` item's ````getEnabled()```` callback will be invoked (if provided), to determine if the item should\n * be enabled. If we click an item, its ````doAction()```` callback will be invoked with our context object.\n *\n * Remember that we must set the context on our ````ContextMenu```` before we show it, otherwise it will log an error to the console,\n * and ignore our attempt to show it.\n *\n * ````javascript*\n * viewer.scene.canvas.canvas.oncontextmenu = (e) => { // Right-clicked on the canvas\n *\n * if (!objectContextMenu.enabled) {\n * return;\n * }\n *\n * var hit = viewer.scene.pick({ // Try to pick an Entity at the coordinates\n * canvasPos: [e.pageX, e.pageY]\n * });\n *\n * if (hit) { // Picked an Entity\n *\n * objectContextMenu.context = { // Feed entity to ContextMenu\n * entity: hit.entity\n * };\n *\n * objectContextMenu.show(e.pageX, e.pageY); // Show the ContextMenu\n * }\n *\n * e.preventDefault();\n * });\n * ````\n *\n * Note how we only show the ````ContextMenu```` if it's enabled. We can use that mechanism to switch between multiple\n * ````ContextMenu```` instances depending on what we clicked.\n *\n * ## Dynamic Item Titles\n *\n * To make an item dynamically regenerate its title text whenever we show the ````ContextMenu````, provide its title with a\n * ````getTitle()```` callback. The callback will fire each time you show ````ContextMenu````, which will dynamically\n * set the item title text.\n *\n * In the example below, we'll create a simple ````ContextMenu```` that allows us to toggle the selection of an object\n * via its first item, which changes text depending on whether we are selecting or deselecting the object.\n *\n * [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_dynamicItemTitles)]\n *\n * ````javascript\n * const canvasContextMenu = new ContextMenu({\n *\n * enabled: true,\n *\n * items: [\n * [\n * {\n * getTitle: (context) => {\n * return (!context.entity.selected) ? \"Select\" : \"Undo Select\";\n * },\n * doAction: function (context) {\n * context.entity.selected = !context.entity.selected;\n * }\n * },\n * {\n * title: \"Clear Selection\",\n * getEnabled: function (context) {\n * return (context.viewer.scene.numSelectedObjects > 0);\n * },\n * doAction: function (context) {\n * context.viewer.scene.setObjectsSelected(context.viewer.scene.selectedObjectIds, false);\n * }\n * }\n * ]\n * ]\n * });\n * ````\n *\n * ## Sub-menus\n *\n * Each menu item can optionally have a sub-menu, which will appear when we hover over the item.\n *\n * In the example below, we'll create a much simpler ````ContextMenu```` that has only one item, called \"Effects\", which\n * will open a cascading sub-menu whenever we hover over that item.\n *\n * Note that our \"Effects\" item has no ````doAction```` callback, because an item with a sub-menu performs no\n * action of its own.\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_subMenus)]\n *\n * ````javascript\n * const canvasContextMenu = new ContextMenu({\n * items: [ // Top level items\n * [\n * {\n * getTitle: (context) => {\n * return \"Effects\";\n * },\n *\n * items: [ // Sub-menu\n * [\n * {\n * getTitle: (context) => {\n * return (!context.entity.visible) ? \"Show\" : \"Hide\";\n * },\n * doAction: function (context) {\n * context.entity.visible = !context.entity.visible;\n * }\n * },\n * {\n * getTitle: (context) => {\n * return (!context.entity.selected) ? \"Select\" : \"Undo Select\";\n * },\n * doAction: function (context) {\n * context.entity.selected = !context.entity.selected;\n * }\n * },\n * {\n * getTitle: (context) => {\n * return (!context.entity.highlighted) ? \"Highlight\" : \"Undo Highlight\";\n * },\n * doAction: function (context) {\n * context.entity.highlighted = !context.entity.highlighted;\n * }\n * }\n * ]\n * ]\n * }\n * ]\n * ]\n * });\n * ````\n */\nclass ContextMenu {\n\n /**\n * Creates a ````ContextMenu````.\n *\n * The ````ContextMenu```` will be initially hidden.\n *\n * @param {Object} [cfg] ````ContextMenu```` configuration.\n * @param {Object} [cfg.items] The context menu items. These can also be dynamically set on {@link ContextMenu#items}. See the class documentation for an example.\n * @param {Object} [cfg.context] The context, which is passed into the item callbacks. This can also be dynamically set on {@link ContextMenu#context}. This must be set before calling {@link ContextMenu#show}.\n * @param {Boolean} [cfg.enabled=true] Whether this ````ContextMenu```` is initially enabled. {@link ContextMenu#show} does nothing while this is ````false````.\n * @param {Boolean} [cfg.hideOnMouseDown=true] Whether this ````ContextMenu```` automatically hides whenever we mouse-down or tap anywhere in the page.\n * @param {Boolean} [cfg.hideOnAction=true] Whether this ````ContextMenu```` automatically hides after we select a menu item. Se false if we want the menu to remain shown and show any updates to its item titles, after we've selected an item.\n */\n constructor(cfg = {}) {\n\n this._id = idMap.addItem();\n this._context = null;\n this._enabled = false; // True when the ContextMenu is enabled\n this._itemsCfg = []; // Items as given as configs\n this._rootMenu = null; // The root Menu in the tree\n this._menuList = []; // List of Menus\n this._menuMap = {}; // Menus mapped to their IDs\n this._itemList = []; // List of Items\n this._itemMap = {}; // Items mapped to their IDs\n this._shown = false; // True when the ContextMenu is visible\n this._nextId = 0;\n\n /**\n * Subscriptions to events fired at this ContextMenu.\n * @private\n */\n this._eventSubs = {};\n\n if (cfg.hideOnMouseDown !== false) {\n document.addEventListener(\"mousedown\", (event) => {\n if (!event.target.classList.contains(\"xeokit-context-menu-item\")) {\n this.hide();\n }\n });\n document.addEventListener(\"touchstart\", this._canvasTouchStartHandler = (event) => {\n if (!event.target.classList.contains(\"xeokit-context-menu-item\")) {\n this.hide();\n }\n });\n }\n\n if (cfg.items) {\n this.items = cfg.items;\n }\n\n this._hideOnAction = (cfg.hideOnAction !== false);\n\n this.context = cfg.context;\n this.enabled = cfg.enabled !== false;\n this.hide();\n }\n\n\n /**\n Subscribes to an event fired at this ````ContextMenu````.\n\n @param {String} event The event\n @param {Function} callback Callback fired on the event\n */\n on(event, callback) {\n let subs = this._eventSubs[event];\n if (!subs) {\n subs = [];\n this._eventSubs[event] = subs;\n }\n subs.push(callback);\n }\n\n /**\n Fires an event at this ````ContextMenu````.\n\n @param {String} event The event type name\n @param {Object} value The event parameters\n */\n fire(event, value) {\n const subs = this._eventSubs[event];\n if (subs) {\n for (let i = 0, len = subs.length; i < len; i++) {\n subs[i](value);\n }\n }\n }\n\n /**\n * Sets the ````ContextMenu```` items.\n *\n * These can be updated dynamically at any time.\n *\n * See class documentation for an example.\n *\n * @type {Object[]}\n */\n set items(itemsCfg) {\n this._clear();\n this._itemsCfg = itemsCfg || [];\n this._parseItems(itemsCfg);\n this._createUI();\n }\n\n /**\n * Gets the ````ContextMenu```` items.\n *\n * @type {Object[]}\n */\n get items() {\n return this._itemsCfg;\n }\n\n /**\n * Sets whether this ````ContextMenu```` is enabled.\n *\n * Hides the menu when disabling.\n *\n * @type {Boolean}\n */\n set enabled(enabled) {\n enabled = (!!enabled);\n if (enabled === this._enabled) {\n return;\n }\n this._enabled = enabled;\n if (!this._enabled) {\n this.hide();\n }\n }\n\n /**\n * Gets whether this ````ContextMenu```` is enabled.\n *\n * {@link ContextMenu#show} does nothing while this is ````false````.\n *\n * @type {Boolean}\n */\n get enabled() {\n return this._enabled;\n }\n\n /**\n * Sets the ````ContextMenu```` context.\n *\n * The context can be any object that you need to be provides to the callbacks configured on {@link ContextMenu#items}.\n *\n * This must be set before calling {@link ContextMenu#show}.\n *\n * @type {Object}\n */\n set context(context) {\n this._context = context;\n }\n\n /**\n * Gets the ````ContextMenu```` context.\n *\n * @type {Object}\n */\n get context() {\n return this._context;\n }\n\n /**\n * Shows this ````ContextMenu```` at the given page coordinates.\n *\n * Does nothing when {@link ContextMenu#enabled} is ````false````.\n *\n * Logs error to console and does nothing if {@link ContextMenu#context} has not been set.\n *\n * Fires a \"shown\" event when shown.\n *\n * @param {Number} pageX Page X-coordinate.\n * @param {Number} pageY Page Y-coordinate.\n */\n show(pageX, pageY) {\n if (!this._context) {\n console.error(\"ContextMenu cannot be shown without a context - set context first\");\n return;\n }\n if (!this._enabled) {\n return;\n }\n if (this._shown) {\n return;\n }\n this._hideAllMenus();\n this._updateItemsTitles();\n this._updateItemsEnabledStatus();\n this._showMenu(this._rootMenu.id, pageX, pageY);\n this._shown = true;\n this.fire(\"shown\", {});\n }\n\n /**\n * Gets whether this ````ContextMenu```` is currently shown or not.\n *\n * @returns {Boolean} Whether this ````ContextMenu```` is shown.\n */\n get shown() {\n return this._shown;\n }\n\n /**\n * Hides this ````ContextMenu````.\n *\n * Fires a \"hidden\" event when hidden.\n */\n hide() {\n if (!this._enabled) {\n return;\n }\n if (!this._shown) {\n return;\n }\n this._hideAllMenus();\n this._shown = false;\n this.fire(\"hidden\", {});\n }\n\n /**\n * Destroys this ````ContextMenu````.\n */\n destroy() {\n this._context = null;\n this._clear();\n if (this._id !== null) {\n idMap.removeItem(this._id);\n this._id = null;\n }\n }\n\n _clear() { // Destroys DOM elements, clears menu data\n for (let i = 0, len = this._menuList.length; i < len; i++) {\n const menu = this._menuList[i];\n const menuElement = menu.menuElement;\n menuElement.parentElement.removeChild(menuElement);\n }\n this._itemsCfg = [];\n this._rootMenu = null;\n this._menuList = [];\n this._menuMap = {};\n this._itemList = [];\n this._itemMap = {};\n }\n\n _parseItems(itemsCfg) { // Parses \"items\" config into menu data\n\n const visitItems = (itemsCfg) => {\n\n const menuId = this._getNextId();\n const menu = new Menu(menuId);\n\n for (let i = 0, len = itemsCfg.length; i < len; i++) {\n\n const itemsGroupCfg = itemsCfg[i];\n\n const group = new Group();\n\n menu.groups.push(group);\n\n for (let j = 0, lenj = itemsGroupCfg.length; j < lenj; j++) {\n\n const itemCfg = itemsGroupCfg[j];\n const subItemsCfg = itemCfg.items;\n const hasSubItems = (subItemsCfg && (subItemsCfg.length > 0));\n const itemId = this._getNextId();\n\n const getTitle = itemCfg.getTitle || (() => {\n return (itemCfg.title || \"\");\n });\n\n const doAction = itemCfg.doAction || itemCfg.callback || (() => {\n });\n\n const getEnabled = itemCfg.getEnabled || (() => {\n return true;\n });\n\n const getShown = itemCfg.getShown || (() => {\n return true;\n });\n\n const item = new Item(itemId, getTitle, doAction, getEnabled, getShown);\n\n item.parentMenu = menu;\n\n group.items.push(item);\n\n if (hasSubItems) {\n const subMenu = visitItems(subItemsCfg);\n item.subMenu = subMenu;\n subMenu.parentItem = item;\n }\n\n this._itemList.push(item);\n this._itemMap[item.id] = item;\n }\n }\n\n this._menuList.push(menu);\n this._menuMap[menu.id] = menu;\n\n return menu;\n };\n\n this._rootMenu = visitItems(itemsCfg);\n }\n\n _getNextId() { // Returns a unique ID\n return \"ContextMenu_\" + this._id + \"_\" + this._nextId++; // Start ID with alpha chars to make a valid DOM element selector\n }\n\n _createUI() { // Builds DOM elements for the entire menu tree\n\n const visitMenu = (menu) => {\n\n this._createMenuUI(menu);\n\n const groups = menu.groups;\n for (let i = 0, len = groups.length; i < len; i++) {\n const group = groups[i];\n const groupItems = group.items;\n for (let j = 0, lenj = groupItems.length; j < lenj; j++) {\n const item = groupItems[j];\n const subMenu = item.subMenu;\n if (subMenu) {\n visitMenu(subMenu);\n }\n }\n }\n };\n\n visitMenu(this._rootMenu);\n }\n\n _createMenuUI(menu) { // Builds DOM elements for a menu\n\n const groups = menu.groups;\n const html = [];\n\n html.push('
');\n\n html.push('');\n html.push('
');\n\n const htmlString = html.join(\"\");\n\n document.body.insertAdjacentHTML('beforeend', htmlString);\n\n const menuElement = document.querySelector(\".\" + menu.id);\n\n menu.menuElement = menuElement;\n\n menuElement.style[\"border-radius\"] = 4 + \"px\";\n menuElement.style.display = 'none';\n menuElement.style[\"z-index\"] = 300000;\n menuElement.style.background = \"white\";\n menuElement.style.border = \"1px solid black\";\n menuElement.style[\"box-shadow\"] = \"0 4px 5px 0 gray\";\n menuElement.oncontextmenu = (e) => {\n e.preventDefault();\n };\n\n // Bind event handlers\n\n const self = this;\n\n let lastSubMenu = null;\n\n if (groups) {\n\n for (let i = 0, len = groups.length; i < len; i++) {\n\n const group = groups[i];\n const groupItems = group.items;\n\n if (groupItems) {\n\n for (let j = 0, lenj = groupItems.length; j < lenj; j++) {\n\n const item = groupItems[j];\n const itemSubMenu = item.subMenu;\n\n item.itemElement = document.getElementById(item.id);\n\n if (!item.itemElement) {\n console.error(\"ContextMenu item element not found: \" + item.id);\n continue;\n }\n\n item.itemElement.addEventListener(\"mouseenter\", (event) => {\n event.preventDefault();\n\n const subMenu = item.subMenu;\n if (!subMenu) {\n if (lastSubMenu) {\n self._hideMenu(lastSubMenu.id);\n lastSubMenu = null;\n }\n return;\n }\n if (lastSubMenu && (lastSubMenu.id !== subMenu.id)) {\n self._hideMenu(lastSubMenu.id);\n lastSubMenu = null;\n }\n\n if (item.enabled === false) {\n return;\n }\n\n const itemElement = item.itemElement;\n const subMenuElement = subMenu.menuElement;\n\n const itemRect = itemElement.getBoundingClientRect();\n subMenuElement.getBoundingClientRect();\n\n const subMenuWidth = 200; // TODO\n const showOnLeft = ((itemRect.right + subMenuWidth) > window.innerWidth);\n\n if (showOnLeft) {\n self._showMenu(subMenu.id, itemRect.left - subMenuWidth, itemRect.top - 1);\n } else {\n self._showMenu(subMenu.id, itemRect.right - 5, itemRect.top - 1);\n }\n\n lastSubMenu = subMenu;\n });\n\n if (!itemSubMenu) {\n\n // Item without sub-menu\n // clicking item fires the item's action callback\n\n item.itemElement.addEventListener(\"click\", (event) => {\n event.preventDefault();\n if (!self._context) {\n return;\n }\n if (item.enabled === false) {\n return;\n }\n if (item.doAction) {\n item.doAction(self._context);\n }\n if (this._hideOnAction) {\n self.hide();\n } else {\n self._updateItemsTitles();\n self._updateItemsEnabledStatus();\n }\n });\n item.itemElement.addEventListener(\"mouseup\", (event) => {\n if (event.which !== 3) {\n return;\n }\n event.preventDefault();\n if (!self._context) {\n return;\n }\n if (item.enabled === false) {\n return;\n }\n if (item.doAction) {\n item.doAction(self._context);\n }\n if (this._hideOnAction) {\n self.hide();\n } else {\n self._updateItemsTitles();\n self._updateItemsEnabledStatus();\n }\n });\n item.itemElement.addEventListener(\"mouseenter\", (event) => {\n event.preventDefault();\n if (item.enabled === false) {\n return;\n }\n if (item.doHover) {\n item.doHover(self._context);\n }\n });\n\n }\n }\n }\n }\n }\n }\n\n _updateItemsTitles() { // Dynamically updates the title of each Item to the result of Item#getTitle()\n if (!this._context) {\n return;\n }\n for (let i = 0, len = this._itemList.length; i < len; i++) {\n const item = this._itemList[i];\n const itemElement = item.itemElement;\n if (!itemElement) {\n continue;\n }\n const getShown = item.getShown;\n if (!getShown || !getShown(this._context)) {\n continue;\n }\n const title = item.getTitle(this._context);\n if (item.subMenu) {\n itemElement.innerText = title;\n } else {\n itemElement.innerText = title;\n }\n }\n }\n\n _updateItemsEnabledStatus() { // Enables or disables each Item, depending on the result of Item#getEnabled()\n if (!this._context) {\n return;\n }\n for (let i = 0, len = this._itemList.length; i < len; i++) {\n const item = this._itemList[i];\n const itemElement = item.itemElement;\n if (!itemElement) {\n continue;\n }\n const getEnabled = item.getEnabled;\n if (!getEnabled) {\n continue;\n }\n const getShown = item.getShown;\n if (!getShown) {\n continue;\n }\n const shown = getShown(this._context);\n item.shown = shown;\n if (!shown) {\n itemElement.style.visibility = \"hidden\";\n itemElement.style.height = \"0\";\n itemElement.style.padding = \"0\";\n continue;\n } else {\n itemElement.style.visibility = \"visible\";\n itemElement.style.height = \"auto\";\n itemElement.style.padding = null;\n }\n const enabled = getEnabled(this._context);\n item.enabled = enabled;\n if (!enabled) {\n itemElement.classList.add(\"disabled\");\n } else {\n itemElement.classList.remove(\"disabled\");\n }\n }\n }\n\n _showMenu(menuId, pageX, pageY) { // Shows the given menu, at the specified page coordinates\n const menu = this._menuMap[menuId];\n if (!menu) {\n console.error(\"Menu not found: \" + menuId);\n return;\n }\n if (menu.shown) {\n return;\n }\n const menuElement = menu.menuElement;\n if (menuElement) {\n this._showMenuElement(menuElement, pageX, pageY);\n menu.shown = true;\n }\n }\n\n _hideMenu(menuId) { // Hides the given menu\n const menu = this._menuMap[menuId];\n if (!menu) {\n console.error(\"Menu not found: \" + menuId);\n return;\n }\n if (!menu.shown) {\n return;\n }\n const menuElement = menu.menuElement;\n if (menuElement) {\n this._hideMenuElement(menuElement);\n menu.shown = false;\n }\n }\n\n _hideAllMenus() {\n for (let i = 0, len = this._menuList.length; i < len; i++) {\n const menu = this._menuList[i];\n this._hideMenu(menu.id);\n }\n }\n\n _showMenuElement(menuElement, pageX, pageY) { // Shows the given menu element, at the specified page coordinates\n menuElement.style.display = 'block';\n const menuHeight = menuElement.offsetHeight;\n const menuWidth = menuElement.offsetWidth;\n if ((pageY + menuHeight) > window.innerHeight) {\n pageY = window.innerHeight - menuHeight;\n }\n if ((pageX + menuWidth) > window.innerWidth) {\n pageX = window.innerWidth - menuWidth;\n }\n menuElement.style.left = pageX + 'px';\n menuElement.style.top = pageY + 'px';\n }\n\n _hideMenuElement(menuElement) {\n menuElement.style.display = 'none';\n }\n}\n\n/**\n * A PointerLens shows a magnified view of a {@link Viewer}'s canvas, centered at the position of the\n * mouse or touch pointer.\n *\n * This component is used by {@link DistanceMeasurementsControl} and {@link AngleMeasurementsControl}\n * to help position the pointer when snap-to-vertex or snap-toedge is enabled.\n *\n * [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#distance_createWithMouse_snapping)]\n *\n * ````JavaScript\n *\n * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin, AngleMeasurementsMouseControl, PointerLens} from \"../../dist/xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * dtxEnabled: true\n * });\n *\n * viewer.camera.eye = [-3.93, 2.85, 27.01];\n * viewer.camera.look = [4.40, 3.72, 8.89];\n * viewer.camera.up = [-0.01, 0.99, 0.039];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const sceneModel = xktLoader.load({\n * id: \"myModel\",\n * src: \"../../assets/models/xkt/v10/glTF-Embedded/Duplex_A_20110505.glTFEmbedded.xkt\",\n * edges: true\n * });\n *\n * const angleMeasurements = new AngleMeasurementsPlugin(viewer);\n *\n * const angleMeasurementsMouseControl = new AngleMeasurementsMouseControl(angleMeasurements, {\n * pointerLens : new PointerLens(viewer, {\n * zoomFactor: 2\n * })\n * })\n *\n * angleMeasurementsMouseControl.activate();\n * ````\n */\nclass PointerLens {\n\n /**\n * Constructs a new PointerLens.\n * @param viewer The Viewer\n * @param [cfg] PointerLens configuration.\n * @param [cfg.active=true] Whether PointerLens is active. The PointerLens can only be shown when this is `true` (default).\n */\n constructor(viewer, cfg={}) {\n\n this.viewer = viewer;\n this.scene = this.viewer.scene;\n\n this._lensCursorDiv = document.createElement('div');\n this.viewer.scene.canvas.canvas.parentNode.insertBefore(this._lensCursorDiv, this.viewer.scene.canvas.canvas);\n this._lensCursorDiv.style.background = \"pink\";\n this._lensCursorDiv.style.border = \"2px solid red\";\n this._lensCursorDiv.style.borderRadius = \"20px\";\n this._lensCursorDiv.style.width = \"10px\";\n this._lensCursorDiv.style.height = \"10px\";\n this._lensCursorDiv.style.margin = \"-200px -200px\";\n this._lensCursorDiv.style.zIndex = \"100000\";\n this._lensCursorDiv.style.position = \"absolute\";\n this._lensCursorDiv.style.pointerEvents = \"none\";\n\n this._lensContainer = document.createElement('div');\n this._lensContainer.style.border = \"1px solid black\";\n this._lensContainer.style.background = \"white\";\n // this._lensContainer.style.opacity = \"0\";\n this._lensContainer.style.borderRadius = \"50%\";\n this._lensContainer.style.width = \"300px\";\n this._lensContainer.style.height = \"300px\";\n this._lensContainer.style.marginTop = \"85px\";\n this._lensContainer.style.marginLeft = \"25px\";\n this._lensContainer.style.zIndex = \"15000\";\n this._lensContainer.style.position = \"absolute\";\n this._lensContainer.style.pointerEvents = \"none\";\n this._lensContainer.style.visibility = \"hidden\";\n\n this._lensCanvas = document.createElement('canvas');\n // this._lensCanvas.style.background = \"darkblue\";\n this._lensCanvas.style.borderRadius = \"50%\";\n\n this._lensCanvas.style.width = \"300px\";\n this._lensCanvas.style.height = \"300px\";\n this._lensCanvas.style.zIndex = \"15000\";\n this._lensCanvas.style.pointerEvents = \"none\";\n\n document.body.appendChild(this._lensContainer);\n this._lensContainer.appendChild(this._lensCanvas);\n\n this._lensCanvasContext = this._lensCanvas.getContext('2d');\n this._canvasElement = this.viewer.scene.canvas.canvas;\n\n this._canvasPos = null;\n this._snappedCanvasPos = null;\n this._lensPosToggle = true;\n\n this._zoomLevel = cfg.zoomLevel || 2;\n\n this._active = (cfg.active !== false);\n this._visible = false;\n this._snapped = false;\n\n this._onViewerRendering = this.viewer.scene.on(\"rendering\", () => {\n if (this._active && this._visible) {\n this.update();\n }\n });\n }\n\n /**\n * Updates this PointerLens.\n */\n update() {\n if (!this._active || !this._visible) {\n return;\n }\n if (!this._canvasPos) {\n return;\n }\n const lensRect = this._lensContainer.getBoundingClientRect();\n const canvasRect = this._canvasElement.getBoundingClientRect();\n const pointerOnLens =\n this._canvasPos[0] < lensRect.right && this._canvasPos[0] > lensRect.left &&\n this._canvasPos[1] < lensRect.bottom && this._canvasPos[1] > lensRect.top;\n this._lensContainer.style.marginLeft = `25px`;\n if (pointerOnLens) {\n if (this._lensPosToggle) {\n this._lensContainer.style.marginTop = `${canvasRect.bottom - canvasRect.top - this._lensCanvas.height - 85}px`;\n } else {\n this._lensContainer.style.marginTop = `85px`;\n }\n this._lensPosToggle = !this._lensPosToggle;\n }\n this._lensCanvasContext.clearRect(0, 0, this._lensCanvas.width, this._lensCanvas.height);\n const size = Math.max(this._lensCanvas.width, this._lensCanvas.height) / this._zoomLevel;\n this._lensCanvasContext.drawImage(\n this._canvasElement, // source canvas\n this._canvasPos[0] - size / 2, // source x (zoom center)\n this._canvasPos[1] - size / 2, // source y (zoom center)\n size, // source width\n size, // source height\n 0, // destination x\n 0, // destination y\n this._lensCanvas.width, // destination width\n this._lensCanvas.height // destination height\n );\n\n const centerLensCanvas = [\n (lensRect.left + lensRect.right) / 2,\n (lensRect.top + lensRect.bottom) / 2\n ];\n\n if (this._snappedCanvasPos) {\n const deltaX = this._snappedCanvasPos[0] - this._canvasPos[0];\n const deltaY = this._snappedCanvasPos[1] - this._canvasPos[1];\n\n this._lensCursorDiv.style.marginLeft = `${centerLensCanvas[0] + deltaX * this._zoomLevel - 10}px`;\n this._lensCursorDiv.style.marginTop = `${centerLensCanvas[1] + deltaY * this._zoomLevel - 10}px`;\n } else {\n this._lensCursorDiv.style.marginLeft = `${centerLensCanvas[0] - 10}px`;\n this._lensCursorDiv.style.marginTop = `${centerLensCanvas[1] - 10}px`;\n }\n }\n\n\n /**\n * Sets the zoom factor for the lens.\n *\n * This is `2` by default.\n *\n * @param zoomFactor\n */\n set zoomFactor(zoomFactor) {\n this._zoomFactor = zoomFactor;\n this.update();\n }\n\n /**\n * Gets the zoom factor for the lens.\n *\n * This is `2` by default.\n *\n * @returns Number\n */\n get zoomFactor() {\n return this._zoomFactor;\n }\n\n /**\n * Sets the canvas central position of the lens.\n * @param canvasPos\n */\n set canvasPos(canvasPos) {\n this._canvasPos = canvasPos;\n this.update();\n }\n\n /**\n * Gets the canvas central position of the lens.\n * @returns {Number[]}\n */\n get canvasPos() {\n return this._canvasPos;\n }\n\n /**\n * Sets the canvas coordinates of the pointer.\n * @param snappedCanvasPos\n */\n set snappedCanvasPos(snappedCanvasPos) {\n this._snappedCanvasPos = snappedCanvasPos;\n this.update();\n }\n\n /**\n * Gets the canvas coordinates of the snapped pointer.\n * @returns {Number[]}\n */\n get snappedCanvasPos() {\n return this._snappedCanvasPos;\n }\n\n /**\n * Sets if the cursor has snapped to anything.\n * This is set by plugins.\n * @param snapped\n * @private\n */\n set snapped(snapped) {\n this._snapped = snapped;\n if (snapped) {\n this._lensCursorDiv.style.background = \"greenyellow\";\n this._lensCursorDiv.style.border = \"2px solid green\";\n } else {\n this._lensCursorDiv.style.background = \"pink\";\n this._lensCursorDiv.style.border = \"2px solid red\";\n }\n }\n\n /**\n * Gets if the cursor has snapped to anything.\n * This is called by plugins.\n * @returns {Boolean}\n * @private\n */\n get snapped() {\n return this._snapped;\n }\n \n /**\n * Sets if this PointerLens is active.\n * @param active\n */\n set active(active) {\n this._active = active;\n this._lensContainer.style.visibility = (active && this._visible) ? \"visible\" : \"hidden\";\n if (!active || !this._visible ) {\n this._lensCursorDiv.style.marginLeft = `-100px`;\n this._lensCursorDiv.style.marginTop = `-100px`;\n }\n this.update();\n }\n\n /**\n * Gets if this PointerLens is active.\n * @returns {Boolean}\n */\n get active() {\n return this._active;\n }\n\n /**\n * Sets if this PointerLens is visible.\n * This is set by plugins.\n * @param visible\n * @private\n */\n set visible(visible) {\n this._visible = visible;\n this._lensContainer.style.visibility = (visible && this._active) ? \"visible\" : \"hidden\";\n if (!visible || !this._active) {\n this._lensCursorDiv.style.marginLeft = `-100px`;\n this._lensCursorDiv.style.marginTop = `-100px`;\n }\n this.update();\n }\n\n /**\n * Gets if this PointerLens is visible.\n * This is called by plugins.\n * @returns {Boolean}\n * @private\n */\n get visible() {\n return this._visible;\n }\n\n /**\n * Destroys this PointerLens.\n */\n destroy() {\n if (!this._destroyed) {\n this.viewer.scene.off(this._onViewerRendering);\n this._lensContainer.removeChild(this._lensCanvas);\n document.body.removeChild(this._lensContainer);\n this._destroyed = true;\n }\n }\n}\n\n// Some temporary vars to help avoid garbage collection\n\nlet doublePrecision = true;\nlet FloatArrayType = doublePrecision ? Float64Array : Float32Array;\n\nconst tempVec3a$N = new FloatArrayType(3);\n\nconst tempMat1 = new FloatArrayType(16);\nconst tempMat2 = new FloatArrayType(16);\nconst tempVec4$1 = new FloatArrayType(4);\n\n\n/**\n * @private\n */\nconst math = {\n\n setDoublePrecisionEnabled(enable) {\n doublePrecision = enable;\n FloatArrayType = doublePrecision ? Float64Array : Float32Array;\n },\n\n getDoublePrecisionEnabled() {\n return doublePrecision;\n },\n\n MIN_DOUBLE: -Number.MAX_SAFE_INTEGER,\n MAX_DOUBLE: Number.MAX_SAFE_INTEGER,\n\n MAX_INT: 10000000,\n\n /**\n * The number of radiians in a degree (0.0174532925).\n * @property DEGTORAD\n * @type {Number}\n */\n DEGTORAD: 0.0174532925,\n\n /**\n * The number of degrees in a radian.\n * @property RADTODEG\n * @type {Number}\n */\n RADTODEG: 57.295779513,\n\n unglobalizeObjectId(modelId, globalId) {\n const idx = globalId.indexOf(\"#\");\n return (idx === modelId.length && globalId.startsWith(modelId)) ? globalId.substring(idx + 1) : globalId;\n },\n\n globalizeObjectId(modelId, objectId) {\n return (modelId + \"#\" + objectId)\n },\n\n /**\n * Returns:\n * - x != 0 => 1/x,\n * - x == 1 => 1\n *\n * @param {number} x\n */\n safeInv(x) {\n const retVal = 1 / x;\n if (isNaN(retVal) || !isFinite(retVal)) {\n return 1;\n }\n return retVal;\n },\n\n /**\n * Returns a new, uninitialized two-element vector.\n * @method vec2\n * @param [values] Initial values.\n * @static\n * @returns {Number[]}\n */\n vec2(values) {\n return new FloatArrayType(values || 2);\n },\n\n /**\n * Returns a new, uninitialized three-element vector.\n * @method vec3\n * @param [values] Initial values.\n * @static\n * @returns {Number[]}\n */\n vec3(values) {\n return new FloatArrayType(values || 3);\n },\n\n /**\n * Returns a new, uninitialized four-element vector.\n * @method vec4\n * @param [values] Initial values.\n * @static\n * @returns {Number[]}\n */\n vec4(values) {\n return new FloatArrayType(values || 4);\n },\n\n /**\n * Returns a new, uninitialized 3x3 matrix.\n * @method mat3\n * @param [values] Initial values.\n * @static\n * @returns {Number[]}\n */\n mat3(values) {\n return new FloatArrayType(values || 9);\n },\n\n /**\n * Converts a 3x3 matrix to 4x4\n * @method mat3ToMat4\n * @param mat3 3x3 matrix.\n * @param mat4 4x4 matrix\n * @static\n * @returns {Number[]}\n */\n mat3ToMat4(mat3, mat4 = new FloatArrayType(16)) {\n mat4[0] = mat3[0];\n mat4[1] = mat3[1];\n mat4[2] = mat3[2];\n mat4[3] = 0;\n mat4[4] = mat3[3];\n mat4[5] = mat3[4];\n mat4[6] = mat3[5];\n mat4[7] = 0;\n mat4[8] = mat3[6];\n mat4[9] = mat3[7];\n mat4[10] = mat3[8];\n mat4[11] = 0;\n mat4[12] = 0;\n mat4[13] = 0;\n mat4[14] = 0;\n mat4[15] = 1;\n return mat4;\n },\n\n /**\n * Returns a new, uninitialized 4x4 matrix.\n * @method mat4\n * @param [values] Initial values.\n * @static\n * @returns {Number[]}\n */\n mat4(values) {\n return new FloatArrayType(values || 16);\n },\n\n /**\n * Converts a 4x4 matrix to 3x3\n * @method mat4ToMat3\n * @param mat4 4x4 matrix.\n * @param mat3 3x3 matrix\n * @static\n * @returns {Number[]}\n */\n mat4ToMat3(mat4, mat3) { // TODO\n //return new FloatArrayType(values || 9);\n },\n\n /**\n * Converts a list of double-precision values to a list of high-part floats and a list of low-part floats.\n * @param doubleVals\n * @param floatValsHigh\n * @param floatValsLow\n */\n doublesToFloats(doubleVals, floatValsHigh, floatValsLow) {\n const floatPair = new FloatArrayType(2);\n for (let i = 0, len = doubleVals.length; i < len; i++) {\n math.splitDouble(doubleVals[i], floatPair);\n floatValsHigh[i] = floatPair[0];\n floatValsLow[i] = floatPair[1];\n }\n },\n\n /**\n * Splits a double value into two floats.\n * @param value\n * @param floatPair\n */\n splitDouble(value, floatPair) {\n const hi = FloatArrayType.from([value])[0];\n const low = value - hi;\n floatPair[0] = hi;\n floatPair[1] = low;\n },\n\n /**\n * Returns a new UUID.\n * @method createUUID\n * @static\n * @return string The new UUID\n */\n createUUID: ((() => {\n const lut = [];\n for (let i = 0; i < 256; i++) {\n lut[i] = (i < 16 ? '0' : '') + (i).toString(16);\n }\n return () => {\n const d0 = Math.random() * 0xffffffff | 0;\n const d1 = Math.random() * 0xffffffff | 0;\n const d2 = Math.random() * 0xffffffff | 0;\n const d3 = Math.random() * 0xffffffff | 0;\n return `${lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff]}-${lut[d1 & 0xff]}${lut[d1 >> 8 & 0xff]}-${lut[d1 >> 16 & 0x0f | 0x40]}${lut[d1 >> 24 & 0xff]}-${lut[d2 & 0x3f | 0x80]}${lut[d2 >> 8 & 0xff]}-${lut[d2 >> 16 & 0xff]}${lut[d2 >> 24 & 0xff]}${lut[d3 & 0xff]}${lut[d3 >> 8 & 0xff]}${lut[d3 >> 16 & 0xff]}${lut[d3 >> 24 & 0xff]}`;\n };\n }))(),\n\n /**\n * Clamps a value to the given range.\n * @param {Number} value Value to clamp.\n * @param {Number} min Lower bound.\n * @param {Number} max Upper bound.\n * @returns {Number} Clamped result.\n */\n clamp(value, min, max) {\n return Math.max(min, Math.min(max, value));\n },\n\n /**\n * Floating-point modulus\n * @method fmod\n * @static\n * @param {Number} a\n * @param {Number} b\n * @returns {*}\n */\n fmod(a, b) {\n if (a < b) {\n console.error(\"math.fmod : Attempting to find modulus within negative range - would be infinite loop - ignoring\");\n return a;\n }\n while (b <= a) {\n a -= b;\n }\n return a;\n },\n\n /**\n * Returns true if the two 3-element vectors are the same.\n * @param v1\n * @param v2\n * @returns {Boolean}\n */\n compareVec3(v1, v2) {\n return (v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2]);\n },\n\n /**\n * Negates a three-element vector.\n * @method negateVec3\n * @static\n * @param {Array(Number)} v Vector to negate\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n negateVec3(v, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = -v[0];\n dest[1] = -v[1];\n dest[2] = -v[2];\n return dest;\n },\n\n /**\n * Negates a four-element vector.\n * @method negateVec4\n * @static\n * @param {Array(Number)} v Vector to negate\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n negateVec4(v, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = -v[0];\n dest[1] = -v[1];\n dest[2] = -v[2];\n dest[3] = -v[3];\n return dest;\n },\n\n /**\n * Adds one four-element vector to another.\n * @method addVec4\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n addVec4(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] + v[0];\n dest[1] = u[1] + v[1];\n dest[2] = u[2] + v[2];\n dest[3] = u[3] + v[3];\n return dest;\n },\n\n /**\n * Adds a scalar value to each element of a four-element vector.\n * @method addVec4Scalar\n * @static\n * @param {Array(Number)} v The vector\n * @param {Number} s The scalar\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n addVec4Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] + s;\n dest[1] = v[1] + s;\n dest[2] = v[2] + s;\n dest[3] = v[3] + s;\n return dest;\n },\n\n /**\n * Adds one three-element vector to another.\n * @method addVec3\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n addVec3(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] + v[0];\n dest[1] = u[1] + v[1];\n dest[2] = u[2] + v[2];\n return dest;\n },\n\n /**\n * Adds a scalar value to each element of a three-element vector.\n * @method addVec4Scalar\n * @static\n * @param {Array(Number)} v The vector\n * @param {Number} s The scalar\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n addVec3Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] + s;\n dest[1] = v[1] + s;\n dest[2] = v[2] + s;\n return dest;\n },\n\n /**\n * Subtracts one four-element vector from another.\n * @method subVec4\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Vector to subtract\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n subVec4(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] - v[0];\n dest[1] = u[1] - v[1];\n dest[2] = u[2] - v[2];\n dest[3] = u[3] - v[3];\n return dest;\n },\n\n /**\n * Subtracts one three-element vector from another.\n * @method subVec3\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Vector to subtract\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n subVec3(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] - v[0];\n dest[1] = u[1] - v[1];\n dest[2] = u[2] - v[2];\n return dest;\n },\n\n /**\n * Subtracts one two-element vector from another.\n * @method subVec2\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Vector to subtract\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n subVec2(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] - v[0];\n dest[1] = u[1] - v[1];\n return dest;\n },\n\n /**\n * Get the geometric mean of the vectors.\n * @method geometricMeanVec2\n * @static\n * @param {...Array(Number)} vectors Vec2 to mean\n * @return {Array(Number)} The geometric mean vec2\n */\n geometricMeanVec2(...vectors) {\n const geometricMean = new FloatArrayType(vectors[0]);\n for (let i = 1; i < vectors.length; i++) {\n geometricMean[0] += vectors[i][0];\n geometricMean[1] += vectors[i][1];\n }\n geometricMean[0] /= vectors.length;\n geometricMean[1] /= vectors.length;\n return geometricMean;\n },\n\n /**\n * Subtracts a scalar value from each element of a four-element vector.\n * @method subVec4Scalar\n * @static\n * @param {Array(Number)} v The vector\n * @param {Number} s The scalar\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n subVec4Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] - s;\n dest[1] = v[1] - s;\n dest[2] = v[2] - s;\n dest[3] = v[3] - s;\n return dest;\n },\n\n /**\n * Sets each element of a 4-element vector to a scalar value minus the value of that element.\n * @method subScalarVec4\n * @static\n * @param {Array(Number)} v The vector\n * @param {Number} s The scalar\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n subScalarVec4(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = s - v[0];\n dest[1] = s - v[1];\n dest[2] = s - v[2];\n dest[3] = s - v[3];\n return dest;\n },\n\n /**\n * Multiplies one three-element vector by another.\n * @method mulVec3\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n mulVec4(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] * v[0];\n dest[1] = u[1] * v[1];\n dest[2] = u[2] * v[2];\n dest[3] = u[3] * v[3];\n return dest;\n },\n\n /**\n * Multiplies each element of a four-element vector by a scalar.\n * @method mulVec34calar\n * @static\n * @param {Array(Number)} v The vector\n * @param {Number} s The scalar\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n mulVec4Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] * s;\n dest[1] = v[1] * s;\n dest[2] = v[2] * s;\n dest[3] = v[3] * s;\n return dest;\n },\n\n /**\n * Multiplies each element of a three-element vector by a scalar.\n * @method mulVec3Scalar\n * @static\n * @param {Array(Number)} v The vector\n * @param {Number} s The scalar\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n mulVec3Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] * s;\n dest[1] = v[1] * s;\n dest[2] = v[2] * s;\n return dest;\n },\n\n /**\n * Multiplies each element of a two-element vector by a scalar.\n * @method mulVec2Scalar\n * @static\n * @param {Array(Number)} v The vector\n * @param {Number} s The scalar\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, v otherwise\n */\n mulVec2Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] * s;\n dest[1] = v[1] * s;\n return dest;\n },\n\n /**\n * Divides one three-element vector by another.\n * @method divVec3\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n divVec3(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] / v[0];\n dest[1] = u[1] / v[1];\n dest[2] = u[2] / v[2];\n return dest;\n },\n\n /**\n * Divides one four-element vector by another.\n * @method divVec4\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @param {Array(Number)} [dest] Destination vector\n * @return {Array(Number)} dest if specified, u otherwise\n */\n divVec4(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n dest[0] = u[0] / v[0];\n dest[1] = u[1] / v[1];\n dest[2] = u[2] / v[2];\n dest[3] = u[3] / v[3];\n return dest;\n },\n\n /**\n * Divides a scalar by a three-element vector, returning a new vector.\n * @method divScalarVec3\n * @static\n * @param v vec3\n * @param s scalar\n * @param dest vec3 - optional destination\n * @return [] dest if specified, v otherwise\n */\n divScalarVec3(s, v, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = s / v[0];\n dest[1] = s / v[1];\n dest[2] = s / v[2];\n return dest;\n },\n\n /**\n * Divides a three-element vector by a scalar.\n * @method divVec3Scalar\n * @static\n * @param v vec3\n * @param s scalar\n * @param dest vec3 - optional destination\n * @return [] dest if specified, v otherwise\n */\n divVec3Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] / s;\n dest[1] = v[1] / s;\n dest[2] = v[2] / s;\n return dest;\n },\n\n /**\n * Divides a four-element vector by a scalar.\n * @method divVec4Scalar\n * @static\n * @param v vec4\n * @param s scalar\n * @param dest vec4 - optional destination\n * @return [] dest if specified, v otherwise\n */\n divVec4Scalar(v, s, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = v[0] / s;\n dest[1] = v[1] / s;\n dest[2] = v[2] / s;\n dest[3] = v[3] / s;\n return dest;\n },\n\n\n /**\n * Divides a scalar by a four-element vector, returning a new vector.\n * @method divScalarVec4\n * @static\n * @param s scalar\n * @param v vec4\n * @param dest vec4 - optional destination\n * @return [] dest if specified, v otherwise\n */\n divScalarVec4(s, v, dest) {\n if (!dest) {\n dest = v;\n }\n dest[0] = s / v[0];\n dest[1] = s / v[1];\n dest[2] = s / v[2];\n dest[3] = s / v[3];\n return dest;\n },\n\n /**\n * Returns the dot product of two four-element vectors.\n * @method dotVec4\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @return The dot product\n */\n dotVec4(u, v) {\n return (u[0] * v[0] + u[1] * v[1] + u[2] * v[2] + u[3] * v[3]);\n },\n\n /**\n * Returns the cross product of two four-element vectors.\n * @method cross3Vec4\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @return The cross product\n */\n cross3Vec4(u, v) {\n const u0 = u[0];\n const u1 = u[1];\n const u2 = u[2];\n const v0 = v[0];\n const v1 = v[1];\n const v2 = v[2];\n return [\n u1 * v2 - u2 * v1,\n u2 * v0 - u0 * v2,\n u0 * v1 - u1 * v0,\n 0.0];\n },\n\n /**\n * Returns the cross product of two three-element vectors.\n * @method cross3Vec3\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @return The cross product\n */\n cross3Vec3(u, v, dest) {\n if (!dest) {\n dest = u;\n }\n const x = u[0];\n const y = u[1];\n const z = u[2];\n const x2 = v[0];\n const y2 = v[1];\n const z2 = v[2];\n dest[0] = y * z2 - z * y2;\n dest[1] = z * x2 - x * z2;\n dest[2] = x * y2 - y * x2;\n return dest;\n },\n\n\n sqLenVec4(v) { // TODO\n return math.dotVec4(v, v);\n },\n\n /**\n * Returns the length of a four-element vector.\n * @method lenVec4\n * @static\n * @param {Array(Number)} v The vector\n * @return The length\n */\n lenVec4(v) {\n return Math.sqrt(math.sqLenVec4(v));\n },\n\n /**\n * Returns the dot product of two three-element vectors.\n * @method dotVec3\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @return The dot product\n */\n dotVec3(u, v) {\n return (u[0] * v[0] + u[1] * v[1] + u[2] * v[2]);\n },\n\n /**\n * Returns the dot product of two two-element vectors.\n * @method dotVec4\n * @static\n * @param {Array(Number)} u First vector\n * @param {Array(Number)} v Second vector\n * @return The dot product\n */\n dotVec2(u, v) {\n return (u[0] * v[0] + u[1] * v[1]);\n },\n\n\n sqLenVec3(v) {\n return math.dotVec3(v, v);\n },\n\n\n sqLenVec2(v) {\n return math.dotVec2(v, v);\n },\n\n /**\n * Returns the length of a three-element vector.\n * @method lenVec3\n * @static\n * @param {Array(Number)} v The vector\n * @return The length\n */\n lenVec3(v) {\n return Math.sqrt(math.sqLenVec3(v));\n },\n\n distVec3: ((() => {\n const vec = new FloatArrayType(3);\n return (v, w) => math.lenVec3(math.subVec3(v, w, vec));\n }))(),\n\n /**\n * Returns the length of a two-element vector.\n * @method lenVec2\n * @static\n * @param {Array(Number)} v The vector\n * @return The length\n */\n lenVec2(v) {\n return Math.sqrt(math.sqLenVec2(v));\n },\n\n distVec2: ((() => {\n const vec = new FloatArrayType(2);\n return (v, w) => math.lenVec2(math.subVec2(v, w, vec));\n }))(),\n\n /**\n * @method rcpVec3\n * @static\n * @param v vec3\n * @param dest vec3 - optional destination\n * @return [] dest if specified, v otherwise\n *\n */\n rcpVec3(v, dest) {\n return math.divScalarVec3(1.0, v, dest);\n },\n\n /**\n * Normalizes a four-element vector\n * @method normalizeVec4\n * @static\n * @param v vec4\n * @param dest vec4 - optional destination\n * @return [] dest if specified, v otherwise\n *\n */\n normalizeVec4(v, dest) {\n const f = 1.0 / math.lenVec4(v);\n return math.mulVec4Scalar(v, f, dest);\n },\n\n /**\n * Normalizes a three-element vector\n * @method normalizeVec4\n * @static\n */\n normalizeVec3(v, dest) {\n const f = 1.0 / math.lenVec3(v);\n return math.mulVec3Scalar(v, f, dest);\n },\n\n /**\n * Normalizes a two-element vector\n * @method normalizeVec2\n * @static\n */\n normalizeVec2(v, dest) {\n const f = 1.0 / math.lenVec2(v);\n return math.mulVec2Scalar(v, f, dest);\n },\n\n /**\n * Gets the angle between two vectors\n * @method angleVec3\n * @param v\n * @param w\n * @returns {number}\n */\n angleVec3(v, w) {\n let theta = math.dotVec3(v, w) / (Math.sqrt(math.sqLenVec3(v) * math.sqLenVec3(w)));\n theta = theta < -1 ? -1 : (theta > 1 ? 1 : theta); // Clamp to handle numerical problems\n return Math.acos(theta);\n },\n\n /**\n * Creates a three-element vector from the rotation part of a sixteen-element matrix.\n * @param m\n * @param dest\n */\n vec3FromMat4Scale: ((() => {\n\n const tempVec3 = new FloatArrayType(3);\n\n return (m, dest) => {\n\n tempVec3[0] = m[0];\n tempVec3[1] = m[1];\n tempVec3[2] = m[2];\n\n dest[0] = math.lenVec3(tempVec3);\n\n tempVec3[0] = m[4];\n tempVec3[1] = m[5];\n tempVec3[2] = m[6];\n\n dest[1] = math.lenVec3(tempVec3);\n\n tempVec3[0] = m[8];\n tempVec3[1] = m[9];\n tempVec3[2] = m[10];\n\n dest[2] = math.lenVec3(tempVec3);\n\n return dest;\n };\n }))(),\n\n /**\n * Converts an n-element vector to a JSON-serializable\n * array with values rounded to two decimal places.\n */\n vecToArray: ((() => {\n function trunc(v) {\n return Math.round(v * 100000) / 100000\n }\n\n return v => {\n v = Array.prototype.slice.call(v);\n for (let i = 0, len = v.length; i < len; i++) {\n v[i] = trunc(v[i]);\n }\n return v;\n };\n }))(),\n\n /**\n * Converts a 3-element vector from an array to an object of the form ````{x:999, y:999, z:999}````.\n * @param arr\n * @returns {{x: *, y: *, z: *}}\n */\n xyzArrayToObject(arr) {\n return {\"x\": arr[0], \"y\": arr[1], \"z\": arr[2]};\n },\n\n /**\n * Converts a 3-element vector object of the form ````{x:999, y:999, z:999}```` to an array.\n * @param xyz\n * @param [arry]\n * @returns {*[]}\n */\n xyzObjectToArray(xyz, arry) {\n arry = arry || math.vec3();\n arry[0] = xyz.x;\n arry[1] = xyz.y;\n arry[2] = xyz.z;\n return arry;\n },\n\n /**\n * Duplicates a 4x4 identity matrix.\n * @method dupMat4\n * @static\n */\n dupMat4(m) {\n return m.slice(0, 16);\n },\n\n /**\n * Extracts a 3x3 matrix from a 4x4 matrix.\n * @method mat4To3\n * @static\n */\n mat4To3(m) {\n return [\n m[0], m[1], m[2],\n m[4], m[5], m[6],\n m[8], m[9], m[10]\n ];\n },\n\n /**\n * Returns a 4x4 matrix with each element set to the given scalar value.\n * @method m4s\n * @static\n */\n m4s(s) {\n return [\n s, s, s, s,\n s, s, s, s,\n s, s, s, s,\n s, s, s, s\n ];\n },\n\n /**\n * Returns a 4x4 matrix with each element set to zero.\n * @method setMat4ToZeroes\n * @static\n */\n setMat4ToZeroes() {\n return math.m4s(0.0);\n },\n\n /**\n * Returns a 4x4 matrix with each element set to 1.0.\n * @method setMat4ToOnes\n * @static\n */\n setMat4ToOnes() {\n return math.m4s(1.0);\n },\n\n /**\n * Returns a 4x4 matrix with each element set to 1.0.\n * @method setMat4ToOnes\n * @static\n */\n diagonalMat4v(v) {\n return new FloatArrayType([\n v[0], 0.0, 0.0, 0.0,\n 0.0, v[1], 0.0, 0.0,\n 0.0, 0.0, v[2], 0.0,\n 0.0, 0.0, 0.0, v[3]\n ]);\n },\n\n /**\n * Returns a 4x4 matrix with diagonal elements set to the given vector.\n * @method diagonalMat4c\n * @static\n */\n diagonalMat4c(x, y, z, w) {\n return math.diagonalMat4v([x, y, z, w]);\n },\n\n /**\n * Returns a 4x4 matrix with diagonal elements set to the given scalar.\n * @method diagonalMat4s\n * @static\n */\n diagonalMat4s(s) {\n return math.diagonalMat4c(s, s, s, s);\n },\n\n /**\n * Returns a 4x4 identity matrix.\n * @method identityMat4\n * @static\n */\n identityMat4(mat = new FloatArrayType(16)) {\n mat[0] = 1.0;\n mat[1] = 0.0;\n mat[2] = 0.0;\n mat[3] = 0.0;\n\n mat[4] = 0.0;\n mat[5] = 1.0;\n mat[6] = 0.0;\n mat[7] = 0.0;\n\n mat[8] = 0.0;\n mat[9] = 0.0;\n mat[10] = 1.0;\n mat[11] = 0.0;\n\n mat[12] = 0.0;\n mat[13] = 0.0;\n mat[14] = 0.0;\n mat[15] = 1.0;\n\n return mat;\n },\n\n /**\n * Returns a 3x3 identity matrix.\n * @method identityMat3\n * @static\n */\n identityMat3(mat = new FloatArrayType(9)) {\n mat[0] = 1.0;\n mat[1] = 0.0;\n mat[2] = 0.0;\n\n mat[3] = 0.0;\n mat[4] = 1.0;\n mat[5] = 0.0;\n\n mat[6] = 0.0;\n mat[7] = 0.0;\n mat[8] = 1.0;\n\n return mat;\n },\n\n /**\n * Tests if the given 4x4 matrix is the identity matrix.\n * @method isIdentityMat4\n * @static\n */\n isIdentityMat4(m) {\n if (m[0] !== 1.0 || m[1] !== 0.0 || m[2] !== 0.0 || m[3] !== 0.0 ||\n m[4] !== 0.0 || m[5] !== 1.0 || m[6] !== 0.0 || m[7] !== 0.0 ||\n m[8] !== 0.0 || m[9] !== 0.0 || m[10] !== 1.0 || m[11] !== 0.0 ||\n m[12] !== 0.0 || m[13] !== 0.0 || m[14] !== 0.0 || m[15] !== 1.0) {\n return false;\n }\n return true;\n },\n\n /**\n * Negates the given 4x4 matrix.\n * @method negateMat4\n * @static\n */\n negateMat4(m, dest) {\n if (!dest) {\n dest = m;\n }\n dest[0] = -m[0];\n dest[1] = -m[1];\n dest[2] = -m[2];\n dest[3] = -m[3];\n dest[4] = -m[4];\n dest[5] = -m[5];\n dest[6] = -m[6];\n dest[7] = -m[7];\n dest[8] = -m[8];\n dest[9] = -m[9];\n dest[10] = -m[10];\n dest[11] = -m[11];\n dest[12] = -m[12];\n dest[13] = -m[13];\n dest[14] = -m[14];\n dest[15] = -m[15];\n return dest;\n },\n\n /**\n * Adds the given 4x4 matrices together.\n * @method addMat4\n * @static\n */\n addMat4(a, b, dest) {\n if (!dest) {\n dest = a;\n }\n dest[0] = a[0] + b[0];\n dest[1] = a[1] + b[1];\n dest[2] = a[2] + b[2];\n dest[3] = a[3] + b[3];\n dest[4] = a[4] + b[4];\n dest[5] = a[5] + b[5];\n dest[6] = a[6] + b[6];\n dest[7] = a[7] + b[7];\n dest[8] = a[8] + b[8];\n dest[9] = a[9] + b[9];\n dest[10] = a[10] + b[10];\n dest[11] = a[11] + b[11];\n dest[12] = a[12] + b[12];\n dest[13] = a[13] + b[13];\n dest[14] = a[14] + b[14];\n dest[15] = a[15] + b[15];\n return dest;\n },\n\n /**\n * Adds the given scalar to each element of the given 4x4 matrix.\n * @method addMat4Scalar\n * @static\n */\n addMat4Scalar(m, s, dest) {\n if (!dest) {\n dest = m;\n }\n dest[0] = m[0] + s;\n dest[1] = m[1] + s;\n dest[2] = m[2] + s;\n dest[3] = m[3] + s;\n dest[4] = m[4] + s;\n dest[5] = m[5] + s;\n dest[6] = m[6] + s;\n dest[7] = m[7] + s;\n dest[8] = m[8] + s;\n dest[9] = m[9] + s;\n dest[10] = m[10] + s;\n dest[11] = m[11] + s;\n dest[12] = m[12] + s;\n dest[13] = m[13] + s;\n dest[14] = m[14] + s;\n dest[15] = m[15] + s;\n return dest;\n },\n\n /**\n * Adds the given scalar to each element of the given 4x4 matrix.\n * @method addScalarMat4\n * @static\n */\n addScalarMat4(s, m, dest) {\n return math.addMat4Scalar(m, s, dest);\n },\n\n /**\n * Subtracts the second 4x4 matrix from the first.\n * @method subMat4\n * @static\n */\n subMat4(a, b, dest) {\n if (!dest) {\n dest = a;\n }\n dest[0] = a[0] - b[0];\n dest[1] = a[1] - b[1];\n dest[2] = a[2] - b[2];\n dest[3] = a[3] - b[3];\n dest[4] = a[4] - b[4];\n dest[5] = a[5] - b[5];\n dest[6] = a[6] - b[6];\n dest[7] = a[7] - b[7];\n dest[8] = a[8] - b[8];\n dest[9] = a[9] - b[9];\n dest[10] = a[10] - b[10];\n dest[11] = a[11] - b[11];\n dest[12] = a[12] - b[12];\n dest[13] = a[13] - b[13];\n dest[14] = a[14] - b[14];\n dest[15] = a[15] - b[15];\n return dest;\n },\n\n /**\n * Subtracts the given scalar from each element of the given 4x4 matrix.\n * @method subMat4Scalar\n * @static\n */\n subMat4Scalar(m, s, dest) {\n if (!dest) {\n dest = m;\n }\n dest[0] = m[0] - s;\n dest[1] = m[1] - s;\n dest[2] = m[2] - s;\n dest[3] = m[3] - s;\n dest[4] = m[4] - s;\n dest[5] = m[5] - s;\n dest[6] = m[6] - s;\n dest[7] = m[7] - s;\n dest[8] = m[8] - s;\n dest[9] = m[9] - s;\n dest[10] = m[10] - s;\n dest[11] = m[11] - s;\n dest[12] = m[12] - s;\n dest[13] = m[13] - s;\n dest[14] = m[14] - s;\n dest[15] = m[15] - s;\n return dest;\n },\n\n /**\n * Subtracts the given scalar from each element of the given 4x4 matrix.\n * @method subScalarMat4\n * @static\n */\n subScalarMat4(s, m, dest) {\n if (!dest) {\n dest = m;\n }\n dest[0] = s - m[0];\n dest[1] = s - m[1];\n dest[2] = s - m[2];\n dest[3] = s - m[3];\n dest[4] = s - m[4];\n dest[5] = s - m[5];\n dest[6] = s - m[6];\n dest[7] = s - m[7];\n dest[8] = s - m[8];\n dest[9] = s - m[9];\n dest[10] = s - m[10];\n dest[11] = s - m[11];\n dest[12] = s - m[12];\n dest[13] = s - m[13];\n dest[14] = s - m[14];\n dest[15] = s - m[15];\n return dest;\n },\n\n /**\n * Multiplies the two given 4x4 matrix by each other.\n * @method mulMat4\n * @static\n */\n mulMat4(a, b, dest) {\n if (!dest) {\n dest = a;\n }\n\n // Cache the matrix values (makes for huge speed increases!)\n const a00 = a[0];\n\n const a01 = a[1];\n const a02 = a[2];\n const a03 = a[3];\n const a10 = a[4];\n const a11 = a[5];\n const a12 = a[6];\n const a13 = a[7];\n const a20 = a[8];\n const a21 = a[9];\n const a22 = a[10];\n const a23 = a[11];\n const a30 = a[12];\n const a31 = a[13];\n const a32 = a[14];\n const a33 = a[15];\n const b00 = b[0];\n const b01 = b[1];\n const b02 = b[2];\n const b03 = b[3];\n const b10 = b[4];\n const b11 = b[5];\n const b12 = b[6];\n const b13 = b[7];\n const b20 = b[8];\n const b21 = b[9];\n const b22 = b[10];\n const b23 = b[11];\n const b30 = b[12];\n const b31 = b[13];\n const b32 = b[14];\n const b33 = b[15];\n\n dest[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;\n dest[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;\n dest[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;\n dest[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;\n dest[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;\n dest[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;\n dest[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;\n dest[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;\n dest[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;\n dest[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;\n dest[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;\n dest[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;\n dest[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;\n dest[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;\n dest[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;\n dest[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33;\n\n return dest;\n },\n\n /**\n * Multiplies the two given 3x3 matrices by each other.\n * @method mulMat4\n * @static\n */\n mulMat3(a, b, dest) {\n if (!dest) {\n dest = new FloatArrayType(9);\n }\n\n const a11 = a[0];\n const a12 = a[3];\n const a13 = a[6];\n const a21 = a[1];\n const a22 = a[4];\n const a23 = a[7];\n const a31 = a[2];\n const a32 = a[5];\n const a33 = a[8];\n const b11 = b[0];\n const b12 = b[3];\n const b13 = b[6];\n const b21 = b[1];\n const b22 = b[4];\n const b23 = b[7];\n const b31 = b[2];\n const b32 = b[5];\n const b33 = b[8];\n\n dest[0] = a11 * b11 + a12 * b21 + a13 * b31;\n dest[3] = a11 * b12 + a12 * b22 + a13 * b32;\n dest[6] = a11 * b13 + a12 * b23 + a13 * b33;\n\n dest[1] = a21 * b11 + a22 * b21 + a23 * b31;\n dest[4] = a21 * b12 + a22 * b22 + a23 * b32;\n dest[7] = a21 * b13 + a22 * b23 + a23 * b33;\n\n dest[2] = a31 * b11 + a32 * b21 + a33 * b31;\n dest[5] = a31 * b12 + a32 * b22 + a33 * b32;\n dest[8] = a31 * b13 + a32 * b23 + a33 * b33;\n\n return dest;\n },\n\n /**\n * Multiplies each element of the given 4x4 matrix by the given scalar.\n * @method mulMat4Scalar\n * @static\n */\n mulMat4Scalar(m, s, dest) {\n if (!dest) {\n dest = m;\n }\n dest[0] = m[0] * s;\n dest[1] = m[1] * s;\n dest[2] = m[2] * s;\n dest[3] = m[3] * s;\n dest[4] = m[4] * s;\n dest[5] = m[5] * s;\n dest[6] = m[6] * s;\n dest[7] = m[7] * s;\n dest[8] = m[8] * s;\n dest[9] = m[9] * s;\n dest[10] = m[10] * s;\n dest[11] = m[11] * s;\n dest[12] = m[12] * s;\n dest[13] = m[13] * s;\n dest[14] = m[14] * s;\n dest[15] = m[15] * s;\n return dest;\n },\n\n /**\n * Multiplies the given 4x4 matrix by the given four-element vector.\n * @method mulMat4v4\n * @static\n */\n mulMat4v4(m, v, dest = math.vec4()) {\n const v0 = v[0];\n const v1 = v[1];\n const v2 = v[2];\n const v3 = v[3];\n dest[0] = m[0] * v0 + m[4] * v1 + m[8] * v2 + m[12] * v3;\n dest[1] = m[1] * v0 + m[5] * v1 + m[9] * v2 + m[13] * v3;\n dest[2] = m[2] * v0 + m[6] * v1 + m[10] * v2 + m[14] * v3;\n dest[3] = m[3] * v0 + m[7] * v1 + m[11] * v2 + m[15] * v3;\n return dest;\n },\n\n /**\n * Transposes the given 4x4 matrix.\n * @method transposeMat4\n * @static\n */\n transposeMat4(mat, dest) {\n // If we are transposing ourselves we can skip a few steps but have to cache some values\n const m4 = mat[4];\n\n const m14 = mat[14];\n const m8 = mat[8];\n const m13 = mat[13];\n const m12 = mat[12];\n const m9 = mat[9];\n if (!dest || mat === dest) {\n const a01 = mat[1];\n const a02 = mat[2];\n const a03 = mat[3];\n const a12 = mat[6];\n const a13 = mat[7];\n const a23 = mat[11];\n mat[1] = m4;\n mat[2] = m8;\n mat[3] = m12;\n mat[4] = a01;\n mat[6] = m9;\n mat[7] = m13;\n mat[8] = a02;\n mat[9] = a12;\n mat[11] = m14;\n mat[12] = a03;\n mat[13] = a13;\n mat[14] = a23;\n return mat;\n }\n dest[0] = mat[0];\n dest[1] = m4;\n dest[2] = m8;\n dest[3] = m12;\n dest[4] = mat[1];\n dest[5] = mat[5];\n dest[6] = m9;\n dest[7] = m13;\n dest[8] = mat[2];\n dest[9] = mat[6];\n dest[10] = mat[10];\n dest[11] = m14;\n dest[12] = mat[3];\n dest[13] = mat[7];\n dest[14] = mat[11];\n dest[15] = mat[15];\n return dest;\n },\n\n /**\n * Transposes the given 3x3 matrix.\n *\n * @method transposeMat3\n * @static\n */\n transposeMat3(mat, dest) {\n if (dest === mat) {\n const a01 = mat[1];\n const a02 = mat[2];\n const a12 = mat[5];\n dest[1] = mat[3];\n dest[2] = mat[6];\n dest[3] = a01;\n dest[5] = mat[7];\n dest[6] = a02;\n dest[7] = a12;\n } else {\n dest[0] = mat[0];\n dest[1] = mat[3];\n dest[2] = mat[6];\n dest[3] = mat[1];\n dest[4] = mat[4];\n dest[5] = mat[7];\n dest[6] = mat[2];\n dest[7] = mat[5];\n dest[8] = mat[8];\n }\n return dest;\n },\n\n /**\n * Returns the determinant of the given 4x4 matrix.\n * @method determinantMat4\n * @static\n */\n determinantMat4(mat) {\n // Cache the matrix values (makes for huge speed increases!)\n const a00 = mat[0];\n\n const a01 = mat[1];\n const a02 = mat[2];\n const a03 = mat[3];\n const a10 = mat[4];\n const a11 = mat[5];\n const a12 = mat[6];\n const a13 = mat[7];\n const a20 = mat[8];\n const a21 = mat[9];\n const a22 = mat[10];\n const a23 = mat[11];\n const a30 = mat[12];\n const a31 = mat[13];\n const a32 = mat[14];\n const a33 = mat[15];\n return a30 * a21 * a12 * a03 - a20 * a31 * a12 * a03 - a30 * a11 * a22 * a03 + a10 * a31 * a22 * a03 +\n a20 * a11 * a32 * a03 - a10 * a21 * a32 * a03 - a30 * a21 * a02 * a13 + a20 * a31 * a02 * a13 +\n a30 * a01 * a22 * a13 - a00 * a31 * a22 * a13 - a20 * a01 * a32 * a13 + a00 * a21 * a32 * a13 +\n a30 * a11 * a02 * a23 - a10 * a31 * a02 * a23 - a30 * a01 * a12 * a23 + a00 * a31 * a12 * a23 +\n a10 * a01 * a32 * a23 - a00 * a11 * a32 * a23 - a20 * a11 * a02 * a33 + a10 * a21 * a02 * a33 +\n a20 * a01 * a12 * a33 - a00 * a21 * a12 * a33 - a10 * a01 * a22 * a33 + a00 * a11 * a22 * a33;\n },\n\n /**\n * Returns the inverse of the given 4x4 matrix.\n * @method inverseMat4\n * @static\n */\n inverseMat4(mat, dest) {\n if (!dest) {\n dest = mat;\n }\n\n // Cache the matrix values (makes for huge speed increases!)\n const a00 = mat[0];\n\n const a01 = mat[1];\n const a02 = mat[2];\n const a03 = mat[3];\n const a10 = mat[4];\n const a11 = mat[5];\n const a12 = mat[6];\n const a13 = mat[7];\n const a20 = mat[8];\n const a21 = mat[9];\n const a22 = mat[10];\n const a23 = mat[11];\n const a30 = mat[12];\n const a31 = mat[13];\n const a32 = mat[14];\n const a33 = mat[15];\n const b00 = a00 * a11 - a01 * a10;\n const b01 = a00 * a12 - a02 * a10;\n const b02 = a00 * a13 - a03 * a10;\n const b03 = a01 * a12 - a02 * a11;\n const b04 = a01 * a13 - a03 * a11;\n const b05 = a02 * a13 - a03 * a12;\n const b06 = a20 * a31 - a21 * a30;\n const b07 = a20 * a32 - a22 * a30;\n const b08 = a20 * a33 - a23 * a30;\n const b09 = a21 * a32 - a22 * a31;\n const b10 = a21 * a33 - a23 * a31;\n const b11 = a22 * a33 - a23 * a32;\n\n // Calculate the determinant (inlined to avoid double-caching)\n const invDet = 1 / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06);\n\n dest[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;\n dest[1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet;\n dest[2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet;\n dest[3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet;\n dest[4] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet;\n dest[5] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet;\n dest[6] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet;\n dest[7] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet;\n dest[8] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet;\n dest[9] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet;\n dest[10] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet;\n dest[11] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet;\n dest[12] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet;\n dest[13] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet;\n dest[14] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet;\n dest[15] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet;\n\n return dest;\n },\n\n /**\n * Returns the trace of the given 4x4 matrix.\n * @method traceMat4\n * @static\n */\n traceMat4(m) {\n return (m[0] + m[5] + m[10] + m[15]);\n },\n\n /**\n * Returns 4x4 translation matrix.\n * @method translationMat4\n * @static\n */\n translationMat4v(v, dest) {\n const m = dest || math.identityMat4();\n m[12] = v[0];\n m[13] = v[1];\n m[14] = v[2];\n return m;\n },\n\n /**\n * Returns 3x3 translation matrix.\n * @method translationMat3\n * @static\n */\n translationMat3v(v, dest) {\n const m = dest || math.identityMat3();\n m[6] = v[0];\n m[7] = v[1];\n return m;\n },\n\n /**\n * Returns 4x4 translation matrix.\n * @method translationMat4c\n * @static\n */\n translationMat4c: ((() => {\n const xyz = new FloatArrayType(3);\n return (x, y, z, dest) => {\n xyz[0] = x;\n xyz[1] = y;\n xyz[2] = z;\n return math.translationMat4v(xyz, dest);\n };\n }))(),\n\n /**\n * Returns 4x4 translation matrix.\n * @method translationMat4s\n * @static\n */\n translationMat4s(s, dest) {\n return math.translationMat4c(s, s, s, dest);\n },\n\n /**\n * Efficiently post-concatenates a translation to the given matrix.\n * @param v\n * @param m\n */\n translateMat4v(xyz, m) {\n return math.translateMat4c(xyz[0], xyz[1], xyz[2], m);\n },\n\n /**\n * Efficiently post-concatenates a translation to the given matrix.\n * @param x\n * @param y\n * @param z\n * @param m\n */\n\n translateMat4c(x, y, z, m) {\n\n const m3 = m[3];\n m[0] += m3 * x;\n m[1] += m3 * y;\n m[2] += m3 * z;\n\n const m7 = m[7];\n m[4] += m7 * x;\n m[5] += m7 * y;\n m[6] += m7 * z;\n\n const m11 = m[11];\n m[8] += m11 * x;\n m[9] += m11 * y;\n m[10] += m11 * z;\n\n const m15 = m[15];\n m[12] += m15 * x;\n m[13] += m15 * y;\n m[14] += m15 * z;\n\n return m;\n },\n\n /**\n * Creates a new matrix that replaces the translation in the rightmost column of the given\n * affine matrix with the given translation.\n * @param m\n * @param translation\n * @param dest\n * @returns {*}\n */\n setMat4Translation(m, translation, dest) {\n\n dest[0] = m[0];\n dest[1] = m[1];\n dest[2] = m[2];\n dest[3] = m[3];\n\n dest[4] = m[4];\n dest[5] = m[5];\n dest[6] = m[6];\n dest[7] = m[7];\n\n dest[8] = m[8];\n dest[9] = m[9];\n dest[10] = m[10];\n dest[11] = m[11];\n\n dest[12] = translation[0];\n dest[13] = translation[1];\n dest[14] = translation[2];\n dest[15] = m[15];\n\n return dest;\n },\n\n /**\n * Returns 4x4 rotation matrix.\n * @method rotationMat4v\n * @static\n */\n rotationMat4v(anglerad, axis, m) {\n const ax = math.normalizeVec4([axis[0], axis[1], axis[2], 0.0], []);\n const s = Math.sin(anglerad);\n const c = Math.cos(anglerad);\n const q = 1.0 - c;\n\n const x = ax[0];\n const y = ax[1];\n const z = ax[2];\n\n let xy;\n let yz;\n let zx;\n let xs;\n let ys;\n let zs;\n\n //xx = x * x; used once\n //yy = y * y; used once\n //zz = z * z; used once\n xy = x * y;\n yz = y * z;\n zx = z * x;\n xs = x * s;\n ys = y * s;\n zs = z * s;\n\n m = m || math.mat4();\n\n m[0] = (q * x * x) + c;\n m[1] = (q * xy) + zs;\n m[2] = (q * zx) - ys;\n m[3] = 0.0;\n\n m[4] = (q * xy) - zs;\n m[5] = (q * y * y) + c;\n m[6] = (q * yz) + xs;\n m[7] = 0.0;\n\n m[8] = (q * zx) + ys;\n m[9] = (q * yz) - xs;\n m[10] = (q * z * z) + c;\n m[11] = 0.0;\n\n m[12] = 0.0;\n m[13] = 0.0;\n m[14] = 0.0;\n m[15] = 1.0;\n\n return m;\n },\n\n /**\n * Returns 4x4 rotation matrix.\n * @method rotationMat4c\n * @static\n */\n rotationMat4c(anglerad, x, y, z, mat) {\n return math.rotationMat4v(anglerad, [x, y, z], mat);\n },\n\n /**\n * Returns 4x4 scale matrix.\n * @method scalingMat4v\n * @static\n */\n scalingMat4v(v, m = math.identityMat4()) {\n m[0] = v[0];\n m[5] = v[1];\n m[10] = v[2];\n return m;\n },\n\n /**\n * Returns 3x3 scale matrix.\n * @method scalingMat3v\n * @static\n */\n scalingMat3v(v, m = math.identityMat3()) {\n m[0] = v[0];\n m[4] = v[1];\n return m;\n },\n\n /**\n * Returns 4x4 scale matrix.\n * @method scalingMat4c\n * @static\n */\n scalingMat4c: ((() => {\n const xyz = new FloatArrayType(3);\n return (x, y, z, dest) => {\n xyz[0] = x;\n xyz[1] = y;\n xyz[2] = z;\n return math.scalingMat4v(xyz, dest);\n };\n }))(),\n\n /**\n * Efficiently post-concatenates a scaling to the given matrix.\n * @method scaleMat4c\n * @param x\n * @param y\n * @param z\n * @param m\n */\n scaleMat4c(x, y, z, m) {\n\n m[0] *= x;\n m[4] *= y;\n m[8] *= z;\n\n m[1] *= x;\n m[5] *= y;\n m[9] *= z;\n\n m[2] *= x;\n m[6] *= y;\n m[10] *= z;\n\n m[3] *= x;\n m[7] *= y;\n m[11] *= z;\n return m;\n },\n\n /**\n * Efficiently post-concatenates a scaling to the given matrix.\n * @method scaleMat4c\n * @param xyz\n * @param m\n */\n scaleMat4v(xyz, m) {\n\n const x = xyz[0];\n const y = xyz[1];\n const z = xyz[2];\n\n m[0] *= x;\n m[4] *= y;\n m[8] *= z;\n m[1] *= x;\n m[5] *= y;\n m[9] *= z;\n m[2] *= x;\n m[6] *= y;\n m[10] *= z;\n m[3] *= x;\n m[7] *= y;\n m[11] *= z;\n\n return m;\n },\n\n /**\n * Returns 4x4 scale matrix.\n * @method scalingMat4s\n * @static\n */\n scalingMat4s(s) {\n return math.scalingMat4c(s, s, s);\n },\n\n /**\n * Creates a matrix from a quaternion rotation and vector translation\n *\n * @param {Number[]} q Rotation quaternion\n * @param {Number[]} v Translation vector\n * @param {Number[]} dest Destination matrix\n * @returns {Number[]} dest\n */\n rotationTranslationMat4(q, v, dest = math.mat4()) {\n const x = q[0];\n const y = q[1];\n const z = q[2];\n const w = q[3];\n\n const x2 = x + x;\n const y2 = y + y;\n const z2 = z + z;\n const xx = x * x2;\n const xy = x * y2;\n const xz = x * z2;\n const yy = y * y2;\n const yz = y * z2;\n const zz = z * z2;\n const wx = w * x2;\n const wy = w * y2;\n const wz = w * z2;\n\n dest[0] = 1 - (yy + zz);\n dest[1] = xy + wz;\n dest[2] = xz - wy;\n dest[3] = 0;\n dest[4] = xy - wz;\n dest[5] = 1 - (xx + zz);\n dest[6] = yz + wx;\n dest[7] = 0;\n dest[8] = xz + wy;\n dest[9] = yz - wx;\n dest[10] = 1 - (xx + yy);\n dest[11] = 0;\n dest[12] = v[0];\n dest[13] = v[1];\n dest[14] = v[2];\n dest[15] = 1;\n\n return dest;\n },\n\n /**\n * Gets Euler angles from a 4x4 matrix.\n *\n * @param {Number[]} mat The 4x4 matrix.\n * @param {String} order Desired Euler angle order: \"XYZ\", \"YXZ\", \"ZXY\" etc.\n * @param {Number[]} [dest] Destination Euler angles, created by default.\n * @returns {Number[]} The Euler angles.\n */\n mat4ToEuler(mat, order, dest = math.vec4()) {\n const clamp = math.clamp;\n\n // Assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n const m11 = mat[0];\n\n const m12 = mat[4];\n const m13 = mat[8];\n const m21 = mat[1];\n const m22 = mat[5];\n const m23 = mat[9];\n const m31 = mat[2];\n const m32 = mat[6];\n const m33 = mat[10];\n\n if (order === 'XYZ') {\n\n dest[1] = Math.asin(clamp(m13, -1, 1));\n\n if (Math.abs(m13) < 0.99999) {\n dest[0] = Math.atan2(-m23, m33);\n dest[2] = Math.atan2(-m12, m11);\n } else {\n dest[0] = Math.atan2(m32, m22);\n dest[2] = 0;\n\n }\n\n } else if (order === 'YXZ') {\n\n dest[0] = Math.asin(-clamp(m23, -1, 1));\n\n if (Math.abs(m23) < 0.99999) {\n dest[1] = Math.atan2(m13, m33);\n dest[2] = Math.atan2(m21, m22);\n } else {\n dest[1] = Math.atan2(-m31, m11);\n dest[2] = 0;\n }\n\n } else if (order === 'ZXY') {\n\n dest[0] = Math.asin(clamp(m32, -1, 1));\n\n if (Math.abs(m32) < 0.99999) {\n dest[1] = Math.atan2(-m31, m33);\n dest[2] = Math.atan2(-m12, m22);\n } else {\n dest[1] = 0;\n dest[2] = Math.atan2(m21, m11);\n }\n\n } else if (order === 'ZYX') {\n\n dest[1] = Math.asin(-clamp(m31, -1, 1));\n\n if (Math.abs(m31) < 0.99999) {\n dest[0] = Math.atan2(m32, m33);\n dest[2] = Math.atan2(m21, m11);\n } else {\n dest[0] = 0;\n dest[2] = Math.atan2(-m12, m22);\n }\n\n } else if (order === 'YZX') {\n\n dest[2] = Math.asin(clamp(m21, -1, 1));\n\n if (Math.abs(m21) < 0.99999) {\n dest[0] = Math.atan2(-m23, m22);\n dest[1] = Math.atan2(-m31, m11);\n } else {\n dest[0] = 0;\n dest[1] = Math.atan2(m13, m33);\n }\n\n } else if (order === 'XZY') {\n\n dest[2] = Math.asin(-clamp(m12, -1, 1));\n\n if (Math.abs(m12) < 0.99999) {\n dest[0] = Math.atan2(m32, m22);\n dest[1] = Math.atan2(m13, m11);\n } else {\n dest[0] = Math.atan2(-m23, m33);\n dest[1] = 0;\n }\n }\n\n return dest;\n },\n\n composeMat4(position, quaternion, scale, mat = math.mat4()) {\n math.quaternionToRotationMat4(quaternion, mat);\n math.scaleMat4v(scale, mat);\n math.translateMat4v(position, mat);\n\n return mat;\n },\n\n decomposeMat4: (() => {\n\n const vec = new FloatArrayType(3);\n const matrix = new FloatArrayType(16);\n\n return function decompose(mat, position, quaternion, scale) {\n\n vec[0] = mat[0];\n vec[1] = mat[1];\n vec[2] = mat[2];\n\n let sx = math.lenVec3(vec);\n\n vec[0] = mat[4];\n vec[1] = mat[5];\n vec[2] = mat[6];\n\n const sy = math.lenVec3(vec);\n\n vec[8] = mat[8];\n vec[9] = mat[9];\n vec[10] = mat[10];\n\n const sz = math.lenVec3(vec);\n\n // if determine is negative, we need to invert one scale\n const det = math.determinantMat4(mat);\n\n if (det < 0) {\n sx = -sx;\n }\n\n position[0] = mat[12];\n position[1] = mat[13];\n position[2] = mat[14];\n\n // scale the rotation part\n matrix.set(mat);\n\n const invSX = 1 / sx;\n const invSY = 1 / sy;\n const invSZ = 1 / sz;\n\n matrix[0] *= invSX;\n matrix[1] *= invSX;\n matrix[2] *= invSX;\n\n matrix[4] *= invSY;\n matrix[5] *= invSY;\n matrix[6] *= invSY;\n\n matrix[8] *= invSZ;\n matrix[9] *= invSZ;\n matrix[10] *= invSZ;\n\n math.mat4ToQuaternion(matrix, quaternion);\n\n scale[0] = sx;\n scale[1] = sy;\n scale[2] = sz;\n\n return this;\n\n };\n\n })(),\n\n /** @private */\n getColMat4(mat, c) {\n const i = c * 4;\n return [mat[i], mat[i + 1], mat[i + 2], mat[i + 3]];\n },\n\n /** @private */\n setRowMat4(mat, r, v) {\n mat[r] = v[0];\n mat[r + 4] = v[1];\n mat[r + 8] = v[2];\n mat[r + 12] = v[3];\n },\n\n /**\n * Returns a 4x4 'lookat' viewing transform matrix.\n * @method lookAtMat4v\n * @param pos vec3 position of the viewer\n * @param target vec3 point the viewer is looking at\n * @param up vec3 pointing \"up\"\n * @param dest mat4 Optional, mat4 matrix will be written into\n *\n * @return {mat4} dest if specified, a new mat4 otherwise\n */\n lookAtMat4v(pos, target, up, dest) {\n if (!dest) {\n dest = math.mat4();\n }\n\n const posx = pos[0];\n const posy = pos[1];\n const posz = pos[2];\n const upx = up[0];\n const upy = up[1];\n const upz = up[2];\n const targetx = target[0];\n const targety = target[1];\n const targetz = target[2];\n\n if (posx === targetx && posy === targety && posz === targetz) {\n return math.identityMat4();\n }\n\n let z0;\n let z1;\n let z2;\n let x0;\n let x1;\n let x2;\n let y0;\n let y1;\n let y2;\n let len;\n\n //vec3.direction(eye, center, z);\n z0 = posx - targetx;\n z1 = posy - targety;\n z2 = posz - targetz;\n\n // normalize (no check needed for 0 because of early return)\n len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);\n z0 *= len;\n z1 *= len;\n z2 *= len;\n\n //vec3.normalize(vec3.cross(up, z, x));\n x0 = upy * z2 - upz * z1;\n x1 = upz * z0 - upx * z2;\n x2 = upx * z1 - upy * z0;\n len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);\n if (!len) {\n x0 = 0;\n x1 = 0;\n x2 = 0;\n } else {\n len = 1 / len;\n x0 *= len;\n x1 *= len;\n x2 *= len;\n }\n\n //vec3.normalize(vec3.cross(z, x, y));\n y0 = z1 * x2 - z2 * x1;\n y1 = z2 * x0 - z0 * x2;\n y2 = z0 * x1 - z1 * x0;\n\n len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);\n if (!len) {\n y0 = 0;\n y1 = 0;\n y2 = 0;\n } else {\n len = 1 / len;\n y0 *= len;\n y1 *= len;\n y2 *= len;\n }\n\n dest[0] = x0;\n dest[1] = y0;\n dest[2] = z0;\n dest[3] = 0;\n dest[4] = x1;\n dest[5] = y1;\n dest[6] = z1;\n dest[7] = 0;\n dest[8] = x2;\n dest[9] = y2;\n dest[10] = z2;\n dest[11] = 0;\n dest[12] = -(x0 * posx + x1 * posy + x2 * posz);\n dest[13] = -(y0 * posx + y1 * posy + y2 * posz);\n dest[14] = -(z0 * posx + z1 * posy + z2 * posz);\n dest[15] = 1;\n\n return dest;\n },\n\n /**\n * Returns a 4x4 'lookat' viewing transform matrix.\n * @method lookAtMat4c\n * @static\n */\n lookAtMat4c(posx, posy, posz, targetx, targety, targetz, upx, upy, upz) {\n return math.lookAtMat4v([posx, posy, posz], [targetx, targety, targetz], [upx, upy, upz], []);\n },\n\n /**\n * Returns a 4x4 orthographic projection matrix.\n * @method orthoMat4c\n * @static\n */\n orthoMat4c(left, right, bottom, top, near, far, dest) {\n if (!dest) {\n dest = math.mat4();\n }\n const rl = (right - left);\n const tb = (top - bottom);\n const fn = (far - near);\n\n dest[0] = 2.0 / rl;\n dest[1] = 0.0;\n dest[2] = 0.0;\n dest[3] = 0.0;\n\n dest[4] = 0.0;\n dest[5] = 2.0 / tb;\n dest[6] = 0.0;\n dest[7] = 0.0;\n\n dest[8] = 0.0;\n dest[9] = 0.0;\n dest[10] = -2.0 / fn;\n dest[11] = 0.0;\n\n dest[12] = -(left + right) / rl;\n dest[13] = -(top + bottom) / tb;\n dest[14] = -(far + near) / fn;\n dest[15] = 1.0;\n\n return dest;\n },\n\n /**\n * Returns a 4x4 perspective projection matrix.\n * @method frustumMat4v\n * @static\n */\n frustumMat4v(fmin, fmax, m) {\n if (!m) {\n m = math.mat4();\n }\n\n const fmin4 = [fmin[0], fmin[1], fmin[2], 0.0];\n const fmax4 = [fmax[0], fmax[1], fmax[2], 0.0];\n\n math.addVec4(fmax4, fmin4, tempMat1);\n math.subVec4(fmax4, fmin4, tempMat2);\n\n const t = 2.0 * fmin4[2];\n\n const tempMat20 = tempMat2[0];\n const tempMat21 = tempMat2[1];\n const tempMat22 = tempMat2[2];\n\n m[0] = t / tempMat20;\n m[1] = 0.0;\n m[2] = 0.0;\n m[3] = 0.0;\n\n m[4] = 0.0;\n m[5] = t / tempMat21;\n m[6] = 0.0;\n m[7] = 0.0;\n\n m[8] = tempMat1[0] / tempMat20;\n m[9] = tempMat1[1] / tempMat21;\n m[10] = -tempMat1[2] / tempMat22;\n m[11] = -1.0;\n\n m[12] = 0.0;\n m[13] = 0.0;\n m[14] = -t * fmax4[2] / tempMat22;\n m[15] = 0.0;\n\n return m;\n },\n\n /**\n * Returns a 4x4 perspective projection matrix.\n * @method frustumMat4v\n * @static\n */\n frustumMat4(left, right, bottom, top, near, far, dest) {\n if (!dest) {\n dest = math.mat4();\n }\n const rl = (right - left);\n const tb = (top - bottom);\n const fn = (far - near);\n dest[0] = (near * 2) / rl;\n dest[1] = 0;\n dest[2] = 0;\n dest[3] = 0;\n dest[4] = 0;\n dest[5] = (near * 2) / tb;\n dest[6] = 0;\n dest[7] = 0;\n dest[8] = (right + left) / rl;\n dest[9] = (top + bottom) / tb;\n dest[10] = -(far + near) / fn;\n dest[11] = -1;\n dest[12] = 0;\n dest[13] = 0;\n dest[14] = -(far * near * 2) / fn;\n dest[15] = 0;\n return dest;\n },\n\n /**\n * Returns a 4x4 perspective projection matrix.\n * @method perspectiveMat4v\n * @static\n */\n perspectiveMat4(fovyrad, aspectratio, znear, zfar, m) {\n const pmin = [];\n const pmax = [];\n\n pmin[2] = znear;\n pmax[2] = zfar;\n\n pmax[1] = pmin[2] * Math.tan(fovyrad / 2.0);\n pmin[1] = -pmax[1];\n\n pmax[0] = pmax[1] * aspectratio;\n pmin[0] = -pmax[0];\n\n return math.frustumMat4v(pmin, pmax, m);\n },\n\n /**\n * Returns true if the two 4x4 matrices are the same.\n * @param m1\n * @param m2\n * @returns {Boolean}\n */\n compareMat4(m1, m2) {\n return m1[0] === m2[0] &&\n m1[1] === m2[1] &&\n m1[2] === m2[2] &&\n m1[3] === m2[3] &&\n m1[4] === m2[4] &&\n m1[5] === m2[5] &&\n m1[6] === m2[6] &&\n m1[7] === m2[7] &&\n m1[8] === m2[8] &&\n m1[9] === m2[9] &&\n m1[10] === m2[10] &&\n m1[11] === m2[11] &&\n m1[12] === m2[12] &&\n m1[13] === m2[13] &&\n m1[14] === m2[14] &&\n m1[15] === m2[15];\n },\n\n /**\n * Transforms a three-element position by a 4x4 matrix.\n * @method transformPoint3\n * @static\n */\n transformPoint3(m, p, dest = math.vec3()) {\n\n const x = p[0];\n const y = p[1];\n const z = p[2];\n\n dest[0] = (m[0] * x) + (m[4] * y) + (m[8] * z) + m[12];\n dest[1] = (m[1] * x) + (m[5] * y) + (m[9] * z) + m[13];\n dest[2] = (m[2] * x) + (m[6] * y) + (m[10] * z) + m[14];\n\n return dest;\n },\n\n /**\n * Transforms a homogeneous coordinate by a 4x4 matrix.\n * @method transformPoint3\n * @static\n */\n transformPoint4(m, v, dest = math.vec4()) {\n dest[0] = m[0] * v[0] + m[4] * v[1] + m[8] * v[2] + m[12] * v[3];\n dest[1] = m[1] * v[0] + m[5] * v[1] + m[9] * v[2] + m[13] * v[3];\n dest[2] = m[2] * v[0] + m[6] * v[1] + m[10] * v[2] + m[14] * v[3];\n dest[3] = m[3] * v[0] + m[7] * v[1] + m[11] * v[2] + m[15] * v[3];\n\n return dest;\n },\n\n\n /**\n * Transforms an array of three-element positions by a 4x4 matrix.\n * @method transformPoints3\n * @static\n */\n transformPoints3(m, points, points2) {\n const result = points2 || [];\n const len = points.length;\n let p0;\n let p1;\n let p2;\n let pi;\n\n // cache values\n const m0 = m[0];\n\n const m1 = m[1];\n const m2 = m[2];\n const m3 = m[3];\n const m4 = m[4];\n const m5 = m[5];\n const m6 = m[6];\n const m7 = m[7];\n const m8 = m[8];\n const m9 = m[9];\n const m10 = m[10];\n const m11 = m[11];\n const m12 = m[12];\n const m13 = m[13];\n const m14 = m[14];\n const m15 = m[15];\n\n let r;\n\n for (let i = 0; i < len; ++i) {\n\n // cache values\n pi = points[i];\n\n p0 = pi[0];\n p1 = pi[1];\n p2 = pi[2];\n\n r = result[i] || (result[i] = [0, 0, 0]);\n\n r[0] = (m0 * p0) + (m4 * p1) + (m8 * p2) + m12;\n r[1] = (m1 * p0) + (m5 * p1) + (m9 * p2) + m13;\n r[2] = (m2 * p0) + (m6 * p1) + (m10 * p2) + m14;\n r[3] = (m3 * p0) + (m7 * p1) + (m11 * p2) + m15;\n }\n\n result.length = len;\n\n return result;\n },\n\n /**\n * Transforms an array of positions by a 4x4 matrix.\n * @method transformPositions3\n * @static\n */\n transformPositions3(m, p, p2 = p) {\n let i;\n const len = p.length;\n\n let x;\n let y;\n let z;\n\n const m0 = m[0];\n const m1 = m[1];\n const m2 = m[2];\n m[3];\n const m4 = m[4];\n const m5 = m[5];\n const m6 = m[6];\n m[7];\n const m8 = m[8];\n const m9 = m[9];\n const m10 = m[10];\n m[11];\n const m12 = m[12];\n const m13 = m[13];\n const m14 = m[14];\n m[15];\n\n for (i = 0; i < len; i += 3) {\n\n x = p[i + 0];\n y = p[i + 1];\n z = p[i + 2];\n\n p2[i + 0] = (m0 * x) + (m4 * y) + (m8 * z) + m12;\n p2[i + 1] = (m1 * x) + (m5 * y) + (m9 * z) + m13;\n p2[i + 2] = (m2 * x) + (m6 * y) + (m10 * z) + m14;\n }\n\n return p2;\n },\n\n /**\n * Transforms an array of positions by a 4x4 matrix.\n * @method transformPositions4\n * @static\n */\n transformPositions4(m, p, p2 = p) {\n let i;\n const len = p.length;\n\n let x;\n let y;\n let z;\n\n const m0 = m[0];\n const m1 = m[1];\n const m2 = m[2];\n const m3 = m[3];\n const m4 = m[4];\n const m5 = m[5];\n const m6 = m[6];\n const m7 = m[7];\n const m8 = m[8];\n const m9 = m[9];\n const m10 = m[10];\n const m11 = m[11];\n const m12 = m[12];\n const m13 = m[13];\n const m14 = m[14];\n const m15 = m[15];\n\n for (i = 0; i < len; i += 4) {\n\n x = p[i + 0];\n y = p[i + 1];\n z = p[i + 2];\n\n p2[i + 0] = (m0 * x) + (m4 * y) + (m8 * z) + m12;\n p2[i + 1] = (m1 * x) + (m5 * y) + (m9 * z) + m13;\n p2[i + 2] = (m2 * x) + (m6 * y) + (m10 * z) + m14;\n p2[i + 3] = (m3 * x) + (m7 * y) + (m11 * z) + m15;\n }\n\n return p2;\n },\n\n /**\n * Transforms a three-element vector by a 4x4 matrix.\n * @method transformVec3\n * @static\n */\n transformVec3(m, v, dest) {\n const v0 = v[0];\n const v1 = v[1];\n const v2 = v[2];\n dest = dest || this.vec3();\n dest[0] = (m[0] * v0) + (m[4] * v1) + (m[8] * v2);\n dest[1] = (m[1] * v0) + (m[5] * v1) + (m[9] * v2);\n dest[2] = (m[2] * v0) + (m[6] * v1) + (m[10] * v2);\n return dest;\n },\n\n /**\n * Transforms a four-element vector by a 4x4 matrix.\n * @method transformVec4\n * @static\n */\n transformVec4(m, v, dest) {\n const v0 = v[0];\n const v1 = v[1];\n const v2 = v[2];\n const v3 = v[3];\n dest = dest || math.vec4();\n dest[0] = m[0] * v0 + m[4] * v1 + m[8] * v2 + m[12] * v3;\n dest[1] = m[1] * v0 + m[5] * v1 + m[9] * v2 + m[13] * v3;\n dest[2] = m[2] * v0 + m[6] * v1 + m[10] * v2 + m[14] * v3;\n dest[3] = m[3] * v0 + m[7] * v1 + m[11] * v2 + m[15] * v3;\n return dest;\n },\n\n /**\n * Rotate a 2D vector around a center point.\n *\n * @param a\n * @param center\n * @param angle\n * @returns {math}\n */\n rotateVec2(a, center, angle, dest = a) {\n const c = Math.cos(angle);\n const s = Math.sin(angle);\n const x = a[0] - center[0];\n const y = a[1] - center[1];\n dest[0] = x * c - y * s + center[0];\n dest[1] = x * s + y * c + center[1];\n return a;\n },\n\n /**\n * Rotate a 3D vector around the x-axis\n *\n * @method rotateVec3X\n * @param {Number[]} a The vec3 point to rotate\n * @param {Number[]} b The origin of the rotation\n * @param {Number} c The angle of rotation\n * @param {Number[]} dest The receiving vec3\n * @returns {Number[]} dest\n * @static\n */\n rotateVec3X(a, b, c, dest) {\n const p = [];\n const r = [];\n\n //Translate point to the origin\n p[0] = a[0] - b[0];\n p[1] = a[1] - b[1];\n p[2] = a[2] - b[2];\n\n //perform rotation\n r[0] = p[0];\n r[1] = p[1] * Math.cos(c) - p[2] * Math.sin(c);\n r[2] = p[1] * Math.sin(c) + p[2] * Math.cos(c);\n\n //translate to correct position\n dest[0] = r[0] + b[0];\n dest[1] = r[1] + b[1];\n dest[2] = r[2] + b[2];\n\n return dest;\n },\n\n /**\n * Rotate a 3D vector around the y-axis\n *\n * @method rotateVec3Y\n * @param {Number[]} a The vec3 point to rotate\n * @param {Number[]} b The origin of the rotation\n * @param {Number} c The angle of rotation\n * @param {Number[]} dest The receiving vec3\n * @returns {Number[]} dest\n * @static\n */\n rotateVec3Y(a, b, c, dest) {\n const p = [];\n const r = [];\n\n //Translate point to the origin\n p[0] = a[0] - b[0];\n p[1] = a[1] - b[1];\n p[2] = a[2] - b[2];\n\n //perform rotation\n r[0] = p[2] * Math.sin(c) + p[0] * Math.cos(c);\n r[1] = p[1];\n r[2] = p[2] * Math.cos(c) - p[0] * Math.sin(c);\n\n //translate to correct position\n dest[0] = r[0] + b[0];\n dest[1] = r[1] + b[1];\n dest[2] = r[2] + b[2];\n\n return dest;\n },\n\n /**\n * Rotate a 3D vector around the z-axis\n *\n * @method rotateVec3Z\n * @param {Number[]} a The vec3 point to rotate\n * @param {Number[]} b The origin of the rotation\n * @param {Number} c The angle of rotation\n * @param {Number[]} dest The receiving vec3\n * @returns {Number[]} dest\n * @static\n */\n rotateVec3Z(a, b, c, dest) {\n const p = [];\n const r = [];\n\n //Translate point to the origin\n p[0] = a[0] - b[0];\n p[1] = a[1] - b[1];\n p[2] = a[2] - b[2];\n\n //perform rotation\n r[0] = p[0] * Math.cos(c) - p[1] * Math.sin(c);\n r[1] = p[0] * Math.sin(c) + p[1] * Math.cos(c);\n r[2] = p[2];\n\n //translate to correct position\n dest[0] = r[0] + b[0];\n dest[1] = r[1] + b[1];\n dest[2] = r[2] + b[2];\n\n return dest;\n },\n\n /**\n * Transforms a four-element vector by a 4x4 projection matrix.\n *\n * @method projectVec4\n * @param {Number[]} p 3D View-space coordinate\n * @param {Number[]} q 2D Projected coordinate\n * @returns {Number[]} 2D Projected coordinate\n * @static\n */\n projectVec4(p, q) {\n const f = 1.0 / p[3];\n q = q || math.vec2();\n q[0] = p[0] * f;\n q[1] = p[1] * f;\n return q;\n },\n\n /**\n * Unprojects a three-element vector.\n *\n * @method unprojectVec3\n * @param {Number[]} p 3D Projected coordinate\n * @param {Number[]} viewMat View matrix\n * @returns {Number[]} projMat Projection matrix\n * @static\n */\n unprojectVec3: ((() => {\n const mat = new FloatArrayType(16);\n const mat2 = new FloatArrayType(16);\n const mat3 = new FloatArrayType(16);\n return function (p, viewMat, projMat, q) {\n return this.transformVec3(this.mulMat4(this.inverseMat4(viewMat, mat), this.inverseMat4(projMat, mat2), mat3), p, q)\n };\n }))(),\n\n /**\n * Linearly interpolates between two 3D vectors.\n * @method lerpVec3\n * @static\n */\n lerpVec3(t, t1, t2, p1, p2, dest) {\n const result = dest || math.vec3();\n const f = (t - t1) / (t2 - t1);\n result[0] = p1[0] + (f * (p2[0] - p1[0]));\n result[1] = p1[1] + (f * (p2[1] - p1[1]));\n result[2] = p1[2] + (f * (p2[2] - p1[2]));\n return result;\n },\n\n /**\n * Linearly interpolates between two 4x4 matrices.\n * @method lerpMat4\n * @static\n */\n lerpMat4(t, t1, t2, m1, m2, dest) {\n const result = dest || math.mat4();\n const f = (t - t1) / (t2 - t1);\n result[0] = m1[0] + (f * (m2[0] - m1[0]));\n result[1] = m1[1] + (f * (m2[1] - m1[1]));\n result[2] = m1[2] + (f * (m2[2] - m1[2]));\n result[3] = m1[3] + (f * (m2[3] - m1[3]));\n result[4] = m1[4] + (f * (m2[4] - m1[4]));\n result[5] = m1[5] + (f * (m2[5] - m1[5]));\n result[6] = m1[6] + (f * (m2[6] - m1[6]));\n result[7] = m1[7] + (f * (m2[7] - m1[7]));\n result[8] = m1[8] + (f * (m2[8] - m1[8]));\n result[9] = m1[9] + (f * (m2[9] - m1[9]));\n result[10] = m1[10] + (f * (m2[10] - m1[10]));\n result[11] = m1[11] + (f * (m2[11] - m1[11]));\n result[12] = m1[12] + (f * (m2[12] - m1[12]));\n result[13] = m1[13] + (f * (m2[13] - m1[13]));\n result[14] = m1[14] + (f * (m2[14] - m1[14]));\n result[15] = m1[15] + (f * (m2[15] - m1[15]));\n return result;\n },\n\n\n /**\n * Flattens a two-dimensional array into a one-dimensional array.\n *\n * @method flatten\n * @static\n * @param {Array of Arrays} a A 2D array\n * @returns Flattened 1D array\n */\n flatten(a) {\n\n const result = [];\n\n let i;\n let leni;\n let j;\n let lenj;\n let item;\n\n for (i = 0, leni = a.length; i < leni; i++) {\n item = a[i];\n for (j = 0, lenj = item.length; j < lenj; j++) {\n result.push(item[j]);\n }\n }\n\n return result;\n },\n\n\n identityQuaternion(dest = math.vec4()) {\n dest[0] = 0.0;\n dest[1] = 0.0;\n dest[2] = 0.0;\n dest[3] = 1.0;\n return dest;\n },\n\n /**\n * Initializes a quaternion from Euler angles.\n *\n * @param {Number[]} euler The Euler angles.\n * @param {String} order Euler angle order: \"XYZ\", \"YXZ\", \"ZXY\" etc.\n * @param {Number[]} [dest] Destination quaternion, created by default.\n * @returns {Number[]} The quaternion.\n */\n eulerToQuaternion(euler, order, dest = math.vec4()) {\n // http://www.mathworks.com/matlabcentral/fileexchange/\n // \t20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/\n //\tcontent/SpinCalc.m\n\n const a = (euler[0] * math.DEGTORAD) / 2;\n const b = (euler[1] * math.DEGTORAD) / 2;\n const c = (euler[2] * math.DEGTORAD) / 2;\n\n const c1 = Math.cos(a);\n const c2 = Math.cos(b);\n const c3 = Math.cos(c);\n const s1 = Math.sin(a);\n const s2 = Math.sin(b);\n const s3 = Math.sin(c);\n\n if (order === 'XYZ') {\n\n dest[0] = s1 * c2 * c3 + c1 * s2 * s3;\n dest[1] = c1 * s2 * c3 - s1 * c2 * s3;\n dest[2] = c1 * c2 * s3 + s1 * s2 * c3;\n dest[3] = c1 * c2 * c3 - s1 * s2 * s3;\n\n } else if (order === 'YXZ') {\n\n dest[0] = s1 * c2 * c3 + c1 * s2 * s3;\n dest[1] = c1 * s2 * c3 - s1 * c2 * s3;\n dest[2] = c1 * c2 * s3 - s1 * s2 * c3;\n dest[3] = c1 * c2 * c3 + s1 * s2 * s3;\n\n } else if (order === 'ZXY') {\n\n dest[0] = s1 * c2 * c3 - c1 * s2 * s3;\n dest[1] = c1 * s2 * c3 + s1 * c2 * s3;\n dest[2] = c1 * c2 * s3 + s1 * s2 * c3;\n dest[3] = c1 * c2 * c3 - s1 * s2 * s3;\n\n } else if (order === 'ZYX') {\n\n dest[0] = s1 * c2 * c3 - c1 * s2 * s3;\n dest[1] = c1 * s2 * c3 + s1 * c2 * s3;\n dest[2] = c1 * c2 * s3 - s1 * s2 * c3;\n dest[3] = c1 * c2 * c3 + s1 * s2 * s3;\n\n } else if (order === 'YZX') {\n\n dest[0] = s1 * c2 * c3 + c1 * s2 * s3;\n dest[1] = c1 * s2 * c3 + s1 * c2 * s3;\n dest[2] = c1 * c2 * s3 - s1 * s2 * c3;\n dest[3] = c1 * c2 * c3 - s1 * s2 * s3;\n\n } else if (order === 'XZY') {\n\n dest[0] = s1 * c2 * c3 - c1 * s2 * s3;\n dest[1] = c1 * s2 * c3 - s1 * c2 * s3;\n dest[2] = c1 * c2 * s3 + s1 * s2 * c3;\n dest[3] = c1 * c2 * c3 + s1 * s2 * s3;\n }\n\n return dest;\n },\n\n mat4ToQuaternion(m, dest = math.vec4()) {\n // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm\n\n // Assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n const m11 = m[0];\n const m12 = m[4];\n const m13 = m[8];\n const m21 = m[1];\n const m22 = m[5];\n const m23 = m[9];\n const m31 = m[2];\n const m32 = m[6];\n const m33 = m[10];\n let s;\n\n const trace = m11 + m22 + m33;\n\n if (trace > 0) {\n\n s = 0.5 / Math.sqrt(trace + 1.0);\n\n dest[3] = 0.25 / s;\n dest[0] = (m32 - m23) * s;\n dest[1] = (m13 - m31) * s;\n dest[2] = (m21 - m12) * s;\n\n } else if (m11 > m22 && m11 > m33) {\n\n s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);\n\n dest[3] = (m32 - m23) / s;\n dest[0] = 0.25 * s;\n dest[1] = (m12 + m21) / s;\n dest[2] = (m13 + m31) / s;\n\n } else if (m22 > m33) {\n\n s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);\n\n dest[3] = (m13 - m31) / s;\n dest[0] = (m12 + m21) / s;\n dest[1] = 0.25 * s;\n dest[2] = (m23 + m32) / s;\n\n } else {\n\n s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);\n\n dest[3] = (m21 - m12) / s;\n dest[0] = (m13 + m31) / s;\n dest[1] = (m23 + m32) / s;\n dest[2] = 0.25 * s;\n }\n\n return dest;\n },\n\n vec3PairToQuaternion(u, v, dest = math.vec4()) {\n const norm_u_norm_v = Math.sqrt(math.dotVec3(u, u) * math.dotVec3(v, v));\n let real_part = norm_u_norm_v + math.dotVec3(u, v);\n\n if (real_part < 0.00000001 * norm_u_norm_v) {\n\n // If u and v are exactly opposite, rotate 180 degrees\n // around an arbitrary orthogonal axis. Axis normalisation\n // can happen later, when we normalise the quaternion.\n\n real_part = 0.0;\n\n if (Math.abs(u[0]) > Math.abs(u[2])) {\n\n dest[0] = -u[1];\n dest[1] = u[0];\n dest[2] = 0;\n\n } else {\n dest[0] = 0;\n dest[1] = -u[2];\n dest[2] = u[1];\n }\n\n } else {\n\n // Otherwise, build quaternion the standard way.\n math.cross3Vec3(u, v, dest);\n }\n\n dest[3] = real_part;\n\n return math.normalizeQuaternion(dest);\n },\n\n angleAxisToQuaternion(angleAxis, dest = math.vec4()) {\n const halfAngle = angleAxis[3] / 2.0;\n const fsin = Math.sin(halfAngle);\n dest[0] = fsin * angleAxis[0];\n dest[1] = fsin * angleAxis[1];\n dest[2] = fsin * angleAxis[2];\n dest[3] = Math.cos(halfAngle);\n return dest;\n },\n\n quaternionToEuler: ((() => {\n const mat = new FloatArrayType(16);\n return (q, order, dest) => {\n dest = dest || math.vec3();\n math.quaternionToRotationMat4(q, mat);\n math.mat4ToEuler(mat, order, dest);\n return dest;\n };\n }))(),\n\n mulQuaternions(p, q, dest = math.vec4()) {\n const p0 = p[0];\n const p1 = p[1];\n const p2 = p[2];\n const p3 = p[3];\n const q0 = q[0];\n const q1 = q[1];\n const q2 = q[2];\n const q3 = q[3];\n dest[0] = p3 * q0 + p0 * q3 + p1 * q2 - p2 * q1;\n dest[1] = p3 * q1 + p1 * q3 + p2 * q0 - p0 * q2;\n dest[2] = p3 * q2 + p2 * q3 + p0 * q1 - p1 * q0;\n dest[3] = p3 * q3 - p0 * q0 - p1 * q1 - p2 * q2;\n return dest;\n },\n\n vec3ApplyQuaternion(q, vec, dest = math.vec3()) {\n const x = vec[0];\n const y = vec[1];\n const z = vec[2];\n\n const qx = q[0];\n const qy = q[1];\n const qz = q[2];\n const qw = q[3];\n\n // calculate quat * vector\n\n const ix = qw * x + qy * z - qz * y;\n const iy = qw * y + qz * x - qx * z;\n const iz = qw * z + qx * y - qy * x;\n const iw = -qx * x - qy * y - qz * z;\n\n // calculate result * inverse quat\n\n dest[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;\n dest[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;\n dest[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;\n\n return dest;\n },\n\n quaternionToMat4(q, dest) {\n\n dest = math.identityMat4(dest);\n\n const q0 = q[0]; //x\n const q1 = q[1]; //y\n const q2 = q[2]; //z\n const q3 = q[3]; //w\n\n const tx = 2.0 * q0;\n const ty = 2.0 * q1;\n const tz = 2.0 * q2;\n\n const twx = tx * q3;\n const twy = ty * q3;\n const twz = tz * q3;\n\n const txx = tx * q0;\n const txy = ty * q0;\n const txz = tz * q0;\n\n const tyy = ty * q1;\n const tyz = tz * q1;\n const tzz = tz * q2;\n\n dest[0] = 1.0 - (tyy + tzz);\n dest[1] = txy + twz;\n dest[2] = txz - twy;\n\n dest[4] = txy - twz;\n dest[5] = 1.0 - (txx + tzz);\n dest[6] = tyz + twx;\n\n dest[8] = txz + twy;\n dest[9] = tyz - twx;\n\n dest[10] = 1.0 - (txx + tyy);\n\n return dest;\n },\n\n quaternionToRotationMat4(q, m) {\n const x = q[0];\n const y = q[1];\n const z = q[2];\n const w = q[3];\n\n const x2 = x + x;\n const y2 = y + y;\n const z2 = z + z;\n const xx = x * x2;\n const xy = x * y2;\n const xz = x * z2;\n const yy = y * y2;\n const yz = y * z2;\n const zz = z * z2;\n const wx = w * x2;\n const wy = w * y2;\n const wz = w * z2;\n\n m[0] = 1 - (yy + zz);\n m[4] = xy - wz;\n m[8] = xz + wy;\n\n m[1] = xy + wz;\n m[5] = 1 - (xx + zz);\n m[9] = yz - wx;\n\n m[2] = xz - wy;\n m[6] = yz + wx;\n m[10] = 1 - (xx + yy);\n\n // last column\n m[3] = 0;\n m[7] = 0;\n m[11] = 0;\n\n // bottom row\n m[12] = 0;\n m[13] = 0;\n m[14] = 0;\n m[15] = 1;\n\n return m;\n },\n\n normalizeQuaternion(q, dest = q) {\n const len = math.lenVec4([q[0], q[1], q[2], q[3]]);\n dest[0] = q[0] / len;\n dest[1] = q[1] / len;\n dest[2] = q[2] / len;\n dest[3] = q[3] / len;\n return dest;\n },\n\n conjugateQuaternion(q, dest = q) {\n dest[0] = -q[0];\n dest[1] = -q[1];\n dest[2] = -q[2];\n dest[3] = q[3];\n return dest;\n },\n\n inverseQuaternion(q, dest) {\n return math.normalizeQuaternion(math.conjugateQuaternion(q, dest));\n },\n\n quaternionToAngleAxis(q, angleAxis = math.vec4()) {\n q = math.normalizeQuaternion(q, tempVec4$1);\n const q3 = q[3];\n const angle = 2 * Math.acos(q3);\n const s = Math.sqrt(1 - q3 * q3);\n if (s < 0.001) { // test to avoid divide by zero, s is always positive due to sqrt\n angleAxis[0] = q[0];\n angleAxis[1] = q[1];\n angleAxis[2] = q[2];\n } else {\n angleAxis[0] = q[0] / s;\n angleAxis[1] = q[1] / s;\n angleAxis[2] = q[2] / s;\n }\n angleAxis[3] = angle; // * 57.295779579;\n return angleAxis;\n },\n\n //------------------------------------------------------------------------------------------------------------------\n // Boundaries\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Returns a new, uninitialized 3D axis-aligned bounding box.\n *\n * @private\n */\n AABB3(values) {\n return new FloatArrayType(values || 6);\n },\n\n /**\n * Returns a new, uninitialized 2D axis-aligned bounding box.\n *\n * @private\n */\n AABB2(values) {\n return new FloatArrayType(values || 4);\n },\n\n /**\n * Returns a new, uninitialized 3D oriented bounding box (OBB).\n *\n * @private\n */\n OBB3(values) {\n return new FloatArrayType(values || 32);\n },\n\n /**\n * Returns a new, uninitialized 2D oriented bounding box (OBB).\n *\n * @private\n */\n OBB2(values) {\n return new FloatArrayType(values || 16);\n },\n\n /** Returns a new 3D bounding sphere */\n Sphere3(x, y, z, r) {\n return new FloatArrayType([x, y, z, r]);\n },\n\n /**\n * Transforms an OBB3 by a 4x4 matrix.\n *\n * @private\n */\n transformOBB3(m, p, p2 = p) {\n let i;\n const len = p.length;\n\n let x;\n let y;\n let z;\n\n const m0 = m[0];\n const m1 = m[1];\n const m2 = m[2];\n const m3 = m[3];\n const m4 = m[4];\n const m5 = m[5];\n const m6 = m[6];\n const m7 = m[7];\n const m8 = m[8];\n const m9 = m[9];\n const m10 = m[10];\n const m11 = m[11];\n const m12 = m[12];\n const m13 = m[13];\n const m14 = m[14];\n const m15 = m[15];\n\n for (i = 0; i < len; i += 4) {\n\n x = p[i + 0];\n y = p[i + 1];\n z = p[i + 2];\n\n p2[i + 0] = (m0 * x) + (m4 * y) + (m8 * z) + m12;\n p2[i + 1] = (m1 * x) + (m5 * y) + (m9 * z) + m13;\n p2[i + 2] = (m2 * x) + (m6 * y) + (m10 * z) + m14;\n p2[i + 3] = (m3 * x) + (m7 * y) + (m11 * z) + m15;\n }\n\n return p2;\n },\n\n /** Returns true if the first AABB contains the second AABB.\n * @param aabb1\n * @param aabb2\n * @returns {Boolean}\n */\n containsAABB3: function (aabb1, aabb2) {\n const result = (\n aabb1[0] <= aabb2[0] && aabb2[3] <= aabb1[3] &&\n aabb1[1] <= aabb2[1] && aabb2[4] <= aabb1[4] &&\n aabb1[2] <= aabb2[2] && aabb2[5] <= aabb1[5]);\n return result;\n },\n\n\n /**\n * Gets the diagonal size of an AABB3 given as minima and maxima.\n *\n * @private\n */\n getAABB3Diag: ((() => {\n\n const min = new FloatArrayType(3);\n const max = new FloatArrayType(3);\n const tempVec3 = new FloatArrayType(3);\n\n return aabb => {\n\n min[0] = aabb[0];\n min[1] = aabb[1];\n min[2] = aabb[2];\n\n max[0] = aabb[3];\n max[1] = aabb[4];\n max[2] = aabb[5];\n\n math.subVec3(max, min, tempVec3);\n\n return Math.abs(math.lenVec3(tempVec3));\n };\n }))(),\n\n /**\n * Get a diagonal boundary size that is symmetrical about the given point.\n *\n * @private\n */\n getAABB3DiagPoint: ((() => {\n\n const min = new FloatArrayType(3);\n const max = new FloatArrayType(3);\n const tempVec3 = new FloatArrayType(3);\n\n return (aabb, p) => {\n\n min[0] = aabb[0];\n min[1] = aabb[1];\n min[2] = aabb[2];\n\n max[0] = aabb[3];\n max[1] = aabb[4];\n max[2] = aabb[5];\n\n const diagVec = math.subVec3(max, min, tempVec3);\n\n const xneg = p[0] - aabb[0];\n const xpos = aabb[3] - p[0];\n const yneg = p[1] - aabb[1];\n const ypos = aabb[4] - p[1];\n const zneg = p[2] - aabb[2];\n const zpos = aabb[5] - p[2];\n\n diagVec[0] += (xneg > xpos) ? xneg : xpos;\n diagVec[1] += (yneg > ypos) ? yneg : ypos;\n diagVec[2] += (zneg > zpos) ? zneg : zpos;\n\n return Math.abs(math.lenVec3(diagVec));\n };\n }))(),\n\n /**\n * Gets the area of an AABB.\n *\n * @private\n */\n getAABB3Area(aabb) {\n const width = (aabb[3] - aabb[0]);\n const height = (aabb[4] - aabb[1]);\n const depth = (aabb[5] - aabb[2]);\n return (width * height * depth);\n },\n\n /**\n * Gets the center of an AABB.\n *\n * @private\n */\n getAABB3Center(aabb, dest) {\n const r = dest || math.vec3();\n\n r[0] = (aabb[0] + aabb[3]) / 2;\n r[1] = (aabb[1] + aabb[4]) / 2;\n r[2] = (aabb[2] + aabb[5]) / 2;\n\n return r;\n },\n\n /**\n * Gets the center of a 2D AABB.\n *\n * @private\n */\n getAABB2Center(aabb, dest) {\n const r = dest || math.vec2();\n\n r[0] = (aabb[2] + aabb[0]) / 2;\n r[1] = (aabb[3] + aabb[1]) / 2;\n\n return r;\n },\n\n /**\n * Collapses a 3D axis-aligned boundary, ready to expand to fit 3D points.\n * Creates new AABB if none supplied.\n *\n * @private\n */\n collapseAABB3(aabb = math.AABB3()) {\n aabb[0] = math.MAX_DOUBLE;\n aabb[1] = math.MAX_DOUBLE;\n aabb[2] = math.MAX_DOUBLE;\n aabb[3] = math.MIN_DOUBLE;\n aabb[4] = math.MIN_DOUBLE;\n aabb[5] = math.MIN_DOUBLE;\n\n return aabb;\n },\n\n /**\n * Converts an axis-aligned 3D boundary into an oriented boundary consisting of\n * an array of eight 3D positions, one for each corner of the boundary.\n *\n * @private\n */\n AABB3ToOBB3(aabb, obb = math.OBB3()) {\n obb[0] = aabb[0];\n obb[1] = aabb[1];\n obb[2] = aabb[2];\n obb[3] = 1;\n\n obb[4] = aabb[3];\n obb[5] = aabb[1];\n obb[6] = aabb[2];\n obb[7] = 1;\n\n obb[8] = aabb[3];\n obb[9] = aabb[4];\n obb[10] = aabb[2];\n obb[11] = 1;\n\n obb[12] = aabb[0];\n obb[13] = aabb[4];\n obb[14] = aabb[2];\n obb[15] = 1;\n\n obb[16] = aabb[0];\n obb[17] = aabb[1];\n obb[18] = aabb[5];\n obb[19] = 1;\n\n obb[20] = aabb[3];\n obb[21] = aabb[1];\n obb[22] = aabb[5];\n obb[23] = 1;\n\n obb[24] = aabb[3];\n obb[25] = aabb[4];\n obb[26] = aabb[5];\n obb[27] = 1;\n\n obb[28] = aabb[0];\n obb[29] = aabb[4];\n obb[30] = aabb[5];\n obb[31] = 1;\n\n return obb;\n },\n\n /**\n * Finds the minimum axis-aligned 3D boundary enclosing the homogeneous 3D points (x,y,z,w) given in a flattened array.\n *\n * @private\n */\n positions3ToAABB3: ((() => {\n\n const p = new FloatArrayType(3);\n\n return (positions, aabb, positionsDecodeMatrix) => {\n aabb = aabb || math.AABB3();\n\n let xmin = math.MAX_DOUBLE;\n let ymin = math.MAX_DOUBLE;\n let zmin = math.MAX_DOUBLE;\n let xmax = math.MIN_DOUBLE;\n let ymax = math.MIN_DOUBLE;\n let zmax = math.MIN_DOUBLE;\n\n let x;\n let y;\n let z;\n\n for (let i = 0, len = positions.length; i < len; i += 3) {\n\n if (positionsDecodeMatrix) {\n\n p[0] = positions[i + 0];\n p[1] = positions[i + 1];\n p[2] = positions[i + 2];\n\n math.decompressPosition(p, positionsDecodeMatrix, p);\n\n x = p[0];\n y = p[1];\n z = p[2];\n\n } else {\n x = positions[i + 0];\n y = positions[i + 1];\n z = positions[i + 2];\n }\n\n if (x < xmin) {\n xmin = x;\n }\n\n if (y < ymin) {\n ymin = y;\n }\n\n if (z < zmin) {\n zmin = z;\n }\n\n if (x > xmax) {\n xmax = x;\n }\n\n if (y > ymax) {\n ymax = y;\n }\n\n if (z > zmax) {\n zmax = z;\n }\n }\n\n aabb[0] = xmin;\n aabb[1] = ymin;\n aabb[2] = zmin;\n aabb[3] = xmax;\n aabb[4] = ymax;\n aabb[5] = zmax;\n\n return aabb;\n };\n }))(),\n\n /**\n * Finds the minimum axis-aligned 3D boundary enclosing the homogeneous 3D points (x,y,z,w) given in a flattened array.\n *\n * @private\n */\n OBB3ToAABB3(obb, aabb = math.AABB3()) {\n let xmin = math.MAX_DOUBLE;\n let ymin = math.MAX_DOUBLE;\n let zmin = math.MAX_DOUBLE;\n let xmax = math.MIN_DOUBLE;\n let ymax = math.MIN_DOUBLE;\n let zmax = math.MIN_DOUBLE;\n\n let x;\n let y;\n let z;\n\n for (let i = 0, len = obb.length; i < len; i += 4) {\n\n x = obb[i + 0];\n y = obb[i + 1];\n z = obb[i + 2];\n\n if (x < xmin) {\n xmin = x;\n }\n\n if (y < ymin) {\n ymin = y;\n }\n\n if (z < zmin) {\n zmin = z;\n }\n\n if (x > xmax) {\n xmax = x;\n }\n\n if (y > ymax) {\n ymax = y;\n }\n\n if (z > zmax) {\n zmax = z;\n }\n }\n\n aabb[0] = xmin;\n aabb[1] = ymin;\n aabb[2] = zmin;\n aabb[3] = xmax;\n aabb[4] = ymax;\n aabb[5] = zmax;\n\n return aabb;\n },\n\n /**\n * Finds the minimum axis-aligned 3D boundary enclosing the given 3D points.\n *\n * @private\n */\n points3ToAABB3(points, aabb = math.AABB3()) {\n let xmin = math.MAX_DOUBLE;\n let ymin = math.MAX_DOUBLE;\n let zmin = math.MAX_DOUBLE;\n let xmax = math.MIN_DOUBLE;\n let ymax = math.MIN_DOUBLE;\n let zmax = math.MIN_DOUBLE;\n\n let x;\n let y;\n let z;\n\n for (let i = 0, len = points.length; i < len; i++) {\n\n x = points[i][0];\n y = points[i][1];\n z = points[i][2];\n\n if (x < xmin) {\n xmin = x;\n }\n\n if (y < ymin) {\n ymin = y;\n }\n\n if (z < zmin) {\n zmin = z;\n }\n\n if (x > xmax) {\n xmax = x;\n }\n\n if (y > ymax) {\n ymax = y;\n }\n\n if (z > zmax) {\n zmax = z;\n }\n }\n\n aabb[0] = xmin;\n aabb[1] = ymin;\n aabb[2] = zmin;\n aabb[3] = xmax;\n aabb[4] = ymax;\n aabb[5] = zmax;\n\n return aabb;\n },\n\n /**\n * Finds the minimum boundary sphere enclosing the given 3D points.\n *\n * @private\n */\n points3ToSphere3: ((() => {\n\n const tempVec3 = new FloatArrayType(3);\n\n return (points, sphere) => {\n\n sphere = sphere || math.vec4();\n\n let x = 0;\n let y = 0;\n let z = 0;\n\n let i;\n const numPoints = points.length;\n\n for (i = 0; i < numPoints; i++) {\n x += points[i][0];\n y += points[i][1];\n z += points[i][2];\n }\n\n sphere[0] = x / numPoints;\n sphere[1] = y / numPoints;\n sphere[2] = z / numPoints;\n\n let radius = 0;\n let dist;\n\n for (i = 0; i < numPoints; i++) {\n\n dist = Math.abs(math.lenVec3(math.subVec3(points[i], sphere, tempVec3)));\n\n if (dist > radius) {\n radius = dist;\n }\n }\n\n sphere[3] = radius;\n\n return sphere;\n };\n }))(),\n\n /**\n * Finds the minimum boundary sphere enclosing the given 3D positions.\n *\n * @private\n */\n positions3ToSphere3: ((() => {\n\n const tempVec3a = new FloatArrayType(3);\n const tempVec3b = new FloatArrayType(3);\n\n return (positions, sphere) => {\n\n sphere = sphere || math.vec4();\n\n let x = 0;\n let y = 0;\n let z = 0;\n\n let i;\n const lenPositions = positions.length;\n let radius = 0;\n\n for (i = 0; i < lenPositions; i += 3) {\n x += positions[i];\n y += positions[i + 1];\n z += positions[i + 2];\n }\n\n const numPositions = lenPositions / 3;\n\n sphere[0] = x / numPositions;\n sphere[1] = y / numPositions;\n sphere[2] = z / numPositions;\n\n let dist;\n\n for (i = 0; i < lenPositions; i += 3) {\n\n tempVec3a[0] = positions[i];\n tempVec3a[1] = positions[i + 1];\n tempVec3a[2] = positions[i + 2];\n\n dist = Math.abs(math.lenVec3(math.subVec3(tempVec3a, sphere, tempVec3b)));\n\n if (dist > radius) {\n radius = dist;\n }\n }\n\n sphere[3] = radius;\n\n return sphere;\n };\n }))(),\n\n /**\n * Finds the minimum boundary sphere enclosing the given 3D points.\n *\n * @private\n */\n OBB3ToSphere3: ((() => {\n\n const point = new FloatArrayType(3);\n const tempVec3 = new FloatArrayType(3);\n\n return (points, sphere) => {\n\n sphere = sphere || math.vec4();\n\n let x = 0;\n let y = 0;\n let z = 0;\n\n let i;\n const lenPoints = points.length;\n const numPoints = lenPoints / 4;\n\n for (i = 0; i < lenPoints; i += 4) {\n x += points[i + 0];\n y += points[i + 1];\n z += points[i + 2];\n }\n\n sphere[0] = x / numPoints;\n sphere[1] = y / numPoints;\n sphere[2] = z / numPoints;\n\n let radius = 0;\n let dist;\n\n for (i = 0; i < lenPoints; i += 4) {\n\n point[0] = points[i + 0];\n point[1] = points[i + 1];\n point[2] = points[i + 2];\n\n dist = Math.abs(math.lenVec3(math.subVec3(point, sphere, tempVec3)));\n\n if (dist > radius) {\n radius = dist;\n }\n }\n\n sphere[3] = radius;\n\n return sphere;\n };\n }))(),\n\n /**\n * Gets the center of a bounding sphere.\n *\n * @private\n */\n getSphere3Center(sphere, dest = math.vec3()) {\n dest[0] = sphere[0];\n dest[1] = sphere[1];\n dest[2] = sphere[2];\n\n return dest;\n },\n\n /**\n * Gets the 3D center of the given flat array of 3D positions.\n *\n * @private\n */\n getPositionsCenter(positions, center = math.vec3()) {\n let xCenter = 0;\n let yCenter = 0;\n let zCenter = 0;\n for (var i = 0, len = positions.length; i < len; i += 3) {\n xCenter += positions[i + 0];\n yCenter += positions[i + 1];\n zCenter += positions[i + 2];\n }\n const numPositions = positions.length / 3;\n center[0] = xCenter / numPositions;\n center[1] = yCenter / numPositions;\n center[2] = zCenter / numPositions;\n return center;\n },\n\n /**\n * Expands the first axis-aligned 3D boundary to enclose the second, if required.\n *\n * @private\n */\n expandAABB3(aabb1, aabb2) {\n\n if (aabb1[0] > aabb2[0]) {\n aabb1[0] = aabb2[0];\n }\n\n if (aabb1[1] > aabb2[1]) {\n aabb1[1] = aabb2[1];\n }\n\n if (aabb1[2] > aabb2[2]) {\n aabb1[2] = aabb2[2];\n }\n\n if (aabb1[3] < aabb2[3]) {\n aabb1[3] = aabb2[3];\n }\n\n if (aabb1[4] < aabb2[4]) {\n aabb1[4] = aabb2[4];\n }\n\n if (aabb1[5] < aabb2[5]) {\n aabb1[5] = aabb2[5];\n }\n\n return aabb1;\n },\n\n /**\n * Expands an axis-aligned 3D boundary to enclose the given point, if needed.\n *\n * @private\n */\n expandAABB3Point3(aabb, p) {\n\n if (aabb[0] > p[0]) {\n aabb[0] = p[0];\n }\n\n if (aabb[1] > p[1]) {\n aabb[1] = p[1];\n }\n\n if (aabb[2] > p[2]) {\n aabb[2] = p[2];\n }\n\n if (aabb[3] < p[0]) {\n aabb[3] = p[0];\n }\n\n if (aabb[4] < p[1]) {\n aabb[4] = p[1];\n }\n\n if (aabb[5] < p[2]) {\n aabb[5] = p[2];\n }\n\n return aabb;\n },\n\n /**\n * Expands an axis-aligned 3D boundary to enclose the given points, if needed.\n *\n * @private\n */\n expandAABB3Points3(aabb, positions) {\n var x;\n var y;\n var z;\n for (var i = 0, len = positions.length; i < len; i += 3) {\n x = positions[i];\n y = positions[i + 1];\n z = positions[i + 2];\n if (aabb[0] > x) {\n aabb[0] = x;\n }\n if (aabb[1] > y) {\n aabb[1] = y;\n }\n if (aabb[2] > z) {\n aabb[2] = z;\n }\n if (aabb[3] < x) {\n aabb[3] = x;\n }\n if (aabb[4] < y) {\n aabb[4] = y;\n }\n if (aabb[5] < z) {\n aabb[5] = z;\n }\n }\n return aabb;\n },\n\n /**\n * Collapses a 2D axis-aligned boundary, ready to expand to fit 2D points.\n * Creates new AABB if none supplied.\n *\n * @private\n */\n collapseAABB2(aabb = math.AABB2()) {\n aabb[0] = math.MAX_DOUBLE;\n aabb[1] = math.MAX_DOUBLE;\n aabb[2] = math.MIN_DOUBLE;\n aabb[3] = math.MIN_DOUBLE;\n\n return aabb;\n },\n\n point3AABB3Intersect(aabb, p) {\n return aabb[0] > p[0] || aabb[3] < p[0] || aabb[1] > p[1] || aabb[4] < p[1] || aabb[2] > p[2] || aabb[5] < p[2];\n },\n\n /**\n *\n * @param dir\n * @param constant\n * @param aabb\n * @returns {number}\n */\n planeAABB3Intersect(dir, constant, aabb) {\n let min, max;\n if (dir[0] > 0) {\n min = dir[0] * aabb[0];\n max = dir[0] * aabb[3];\n } else {\n min = dir[0] * aabb[3];\n max = dir[0] * aabb[0];\n }\n if (dir[1] > 0) {\n min += dir[1] * aabb[1];\n max += dir[1] * aabb[4];\n } else {\n min += dir[1] * aabb[4];\n max += dir[1] * aabb[1];\n }\n if (dir[2] > 0) {\n min += dir[2] * aabb[2];\n max += dir[2] * aabb[5];\n } else {\n min += dir[2] * aabb[5];\n max += dir[2] * aabb[2];\n }\n const outside = (min <= -constant) && (max <= -constant);\n if (outside) {\n return -1;\n }\n\n const inside = (min >= -constant) && (max >= -constant);\n if (inside) {\n return 1;\n }\n\n return 0;\n },\n\n /**\n * Finds the minimum 2D projected axis-aligned boundary enclosing the given 3D points.\n *\n * @private\n */\n OBB3ToAABB2(points, aabb = math.AABB2()) {\n let xmin = math.MAX_DOUBLE;\n let ymin = math.MAX_DOUBLE;\n let xmax = math.MIN_DOUBLE;\n let ymax = math.MIN_DOUBLE;\n\n let x;\n let y;\n let w;\n let f;\n\n for (let i = 0, len = points.length; i < len; i += 4) {\n\n x = points[i + 0];\n y = points[i + 1];\n w = points[i + 3] || 1.0;\n\n f = 1.0 / w;\n\n x *= f;\n y *= f;\n\n if (x < xmin) {\n xmin = x;\n }\n\n if (y < ymin) {\n ymin = y;\n }\n\n if (x > xmax) {\n xmax = x;\n }\n\n if (y > ymax) {\n ymax = y;\n }\n }\n\n aabb[0] = xmin;\n aabb[1] = ymin;\n aabb[2] = xmax;\n aabb[3] = ymax;\n\n return aabb;\n },\n\n /**\n * Expands the first axis-aligned 2D boundary to enclose the second, if required.\n *\n * @private\n */\n expandAABB2(aabb1, aabb2) {\n\n if (aabb1[0] > aabb2[0]) {\n aabb1[0] = aabb2[0];\n }\n\n if (aabb1[1] > aabb2[1]) {\n aabb1[1] = aabb2[1];\n }\n\n if (aabb1[2] < aabb2[2]) {\n aabb1[2] = aabb2[2];\n }\n\n if (aabb1[3] < aabb2[3]) {\n aabb1[3] = aabb2[3];\n }\n\n return aabb1;\n },\n\n /**\n * Expands an axis-aligned 2D boundary to enclose the given point, if required.\n *\n * @private\n */\n expandAABB2Point2(aabb, p) {\n\n if (aabb[0] > p[0]) {\n aabb[0] = p[0];\n }\n\n if (aabb[1] > p[1]) {\n aabb[1] = p[1];\n }\n\n if (aabb[2] < p[0]) {\n aabb[2] = p[0];\n }\n\n if (aabb[3] < p[1]) {\n aabb[3] = p[1];\n }\n\n return aabb;\n },\n\n AABB2ToCanvas(aabb, canvasWidth, canvasHeight, aabb2 = aabb) {\n const xmin = (aabb[0] + 1.0) * 0.5;\n const ymin = (aabb[1] + 1.0) * 0.5;\n const xmax = (aabb[2] + 1.0) * 0.5;\n const ymax = (aabb[3] + 1.0) * 0.5;\n\n aabb2[0] = Math.floor(xmin * canvasWidth);\n aabb2[1] = canvasHeight - Math.floor(ymax * canvasHeight);\n aabb2[2] = Math.floor(xmax * canvasWidth);\n aabb2[3] = canvasHeight - Math.floor(ymin * canvasHeight);\n\n return aabb2;\n },\n\n //------------------------------------------------------------------------------------------------------------------\n // Curves\n //------------------------------------------------------------------------------------------------------------------\n\n tangentQuadraticBezier(t, p0, p1, p2) {\n return 2 * (1 - t) * (p1 - p0) + 2 * t * (p2 - p1);\n },\n\n tangentQuadraticBezier3(t, p0, p1, p2, p3) {\n return -3 * p0 * (1 - t) * (1 - t) +\n 3 * p1 * (1 - t) * (1 - t) - 6 * t * p1 * (1 - t) +\n 6 * t * p2 * (1 - t) - 3 * t * t * p2 +\n 3 * t * t * p3;\n },\n\n tangentSpline(t) {\n const h00 = 6 * t * t - 6 * t;\n const h10 = 3 * t * t - 4 * t + 1;\n const h01 = -6 * t * t + 6 * t;\n const h11 = 3 * t * t - 2 * t;\n return h00 + h10 + h01 + h11;\n },\n\n catmullRomInterpolate(p0, p1, p2, p3, t) {\n const v0 = (p2 - p0) * 0.5;\n const v1 = (p3 - p1) * 0.5;\n const t2 = t * t;\n const t3 = t * t2;\n return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;\n },\n\n// Bezier Curve formulii from http://en.wikipedia.org/wiki/B%C3%A9zier_curve\n\n// Quad Bezier Functions\n\n b2p0(t, p) {\n const k = 1 - t;\n return k * k * p;\n\n },\n\n b2p1(t, p) {\n return 2 * (1 - t) * t * p;\n },\n\n b2p2(t, p) {\n return t * t * p;\n },\n\n b2(t, p0, p1, p2) {\n return this.b2p0(t, p0) + this.b2p1(t, p1) + this.b2p2(t, p2);\n },\n\n// Cubic Bezier Functions\n\n b3p0(t, p) {\n const k = 1 - t;\n return k * k * k * p;\n },\n\n b3p1(t, p) {\n const k = 1 - t;\n return 3 * k * k * t * p;\n },\n\n b3p2(t, p) {\n const k = 1 - t;\n return 3 * k * t * t * p;\n },\n\n b3p3(t, p) {\n return t * t * t * p;\n },\n\n b3(t, p0, p1, p2, p3) {\n return this.b3p0(t, p0) + this.b3p1(t, p1) + this.b3p2(t, p2) + this.b3p3(t, p3);\n },\n\n //------------------------------------------------------------------------------------------------------------------\n // Geometry\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Calculates the normal vector of a triangle.\n *\n * @private\n */\n triangleNormal(a, b, c, normal = math.vec3()) {\n const p1x = b[0] - a[0];\n const p1y = b[1] - a[1];\n const p1z = b[2] - a[2];\n\n const p2x = c[0] - a[0];\n const p2y = c[1] - a[1];\n const p2z = c[2] - a[2];\n\n const p3x = p1y * p2z - p1z * p2y;\n const p3y = p1z * p2x - p1x * p2z;\n const p3z = p1x * p2y - p1y * p2x;\n\n const mag = Math.sqrt(p3x * p3x + p3y * p3y + p3z * p3z);\n if (mag === 0) {\n normal[0] = 0;\n normal[1] = 0;\n normal[2] = 0;\n } else {\n normal[0] = p3x / mag;\n normal[1] = p3y / mag;\n normal[2] = p3z / mag;\n }\n\n return normal\n },\n\n /**\n * Finds the intersection of a 3D ray with a 3D triangle.\n *\n * @private\n */\n rayTriangleIntersect: ((() => {\n\n const tempVec3 = new FloatArrayType(3);\n const tempVec3b = new FloatArrayType(3);\n const tempVec3c = new FloatArrayType(3);\n const tempVec3d = new FloatArrayType(3);\n const tempVec3e = new FloatArrayType(3);\n\n return (origin, dir, a, b, c, isect) => {\n\n isect = isect || math.vec3();\n\n const EPSILON = 0.000001;\n\n const edge1 = math.subVec3(b, a, tempVec3);\n const edge2 = math.subVec3(c, a, tempVec3b);\n\n const pvec = math.cross3Vec3(dir, edge2, tempVec3c);\n const det = math.dotVec3(edge1, pvec);\n if (det < EPSILON) {\n return null;\n }\n\n const tvec = math.subVec3(origin, a, tempVec3d);\n const u = math.dotVec3(tvec, pvec);\n if (u < 0 || u > det) {\n return null;\n }\n\n const qvec = math.cross3Vec3(tvec, edge1, tempVec3e);\n const v = math.dotVec3(dir, qvec);\n if (v < 0 || u + v > det) {\n return null;\n }\n\n const t = math.dotVec3(edge2, qvec) / det;\n isect[0] = origin[0] + t * dir[0];\n isect[1] = origin[1] + t * dir[1];\n isect[2] = origin[2] + t * dir[2];\n\n return isect;\n };\n }))(),\n\n /**\n * Finds the intersection of a 3D ray with a plane defined by 3 points.\n *\n * @private\n */\n rayPlaneIntersect: ((() => {\n\n const tempVec3 = new FloatArrayType(3);\n const tempVec3b = new FloatArrayType(3);\n const tempVec3c = new FloatArrayType(3);\n const tempVec3d = new FloatArrayType(3);\n\n return (origin, dir, a, b, c, isect) => {\n\n isect = isect || math.vec3();\n\n dir = math.normalizeVec3(dir, tempVec3);\n\n const edge1 = math.subVec3(b, a, tempVec3b);\n const edge2 = math.subVec3(c, a, tempVec3c);\n\n const n = math.cross3Vec3(edge1, edge2, tempVec3d);\n math.normalizeVec3(n, n);\n\n const d = -math.dotVec3(a, n);\n\n const t = -(math.dotVec3(origin, n) + d) / math.dotVec3(dir, n);\n\n isect[0] = origin[0] + t * dir[0];\n isect[1] = origin[1] + t * dir[1];\n isect[2] = origin[2] + t * dir[2];\n\n return isect;\n };\n }))(),\n\n /**\n * Gets barycentric coordinates from cartesian coordinates within a triangle.\n * Gets barycentric coordinates from cartesian coordinates within a triangle.\n *\n * @private\n */\n cartesianToBarycentric: ((() => {\n\n const tempVec3 = new FloatArrayType(3);\n const tempVec3b = new FloatArrayType(3);\n const tempVec3c = new FloatArrayType(3);\n\n return (cartesian, a, b, c, dest) => {\n\n const v0 = math.subVec3(c, a, tempVec3);\n const v1 = math.subVec3(b, a, tempVec3b);\n const v2 = math.subVec3(cartesian, a, tempVec3c);\n\n const dot00 = math.dotVec3(v0, v0);\n const dot01 = math.dotVec3(v0, v1);\n const dot02 = math.dotVec3(v0, v2);\n const dot11 = math.dotVec3(v1, v1);\n const dot12 = math.dotVec3(v1, v2);\n\n const denom = (dot00 * dot11 - dot01 * dot01);\n\n // Colinear or singular triangle\n\n if (denom === 0) {\n\n // Arbitrary location outside of triangle\n\n return null;\n }\n\n const invDenom = 1 / denom;\n\n const u = (dot11 * dot02 - dot01 * dot12) * invDenom;\n const v = (dot00 * dot12 - dot01 * dot02) * invDenom;\n\n dest[0] = 1 - u - v;\n dest[1] = v;\n dest[2] = u;\n\n return dest;\n };\n }))(),\n\n /**\n * Returns true if the given barycentric coordinates are within their triangle.\n *\n * @private\n */\n barycentricInsideTriangle(bary) {\n\n const v = bary[1];\n const u = bary[2];\n\n return (u >= 0) && (v >= 0) && (u + v < 1);\n },\n\n /**\n * Gets cartesian coordinates from barycentric coordinates within a triangle.\n *\n * @private\n */\n barycentricToCartesian(bary, a, b, c, cartesian = math.vec3()) {\n const u = bary[0];\n const v = bary[1];\n const w = bary[2];\n\n cartesian[0] = a[0] * u + b[0] * v + c[0] * w;\n cartesian[1] = a[1] * u + b[1] * v + c[1] * w;\n cartesian[2] = a[2] * u + b[2] * v + c[2] * w;\n\n return cartesian;\n },\n\n\n /**\n * Given geometry defined as an array of positions, optional normals, option uv and an array of indices, returns\n * modified arrays that have duplicate vertices removed.\n *\n * Note: does not work well when co-incident vertices have same positions but different normals and UVs.\n *\n * @param positions\n * @param normals\n * @param uv\n * @param indices\n * @returns {{positions: Array, indices: Array}}\n * @private\n */\n mergeVertices(positions, normals, uv, indices) {\n const positionsMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)\n const indicesLookup = [];\n const uniquePositions = [];\n const uniqueNormals = normals ? [] : null;\n const uniqueUV = uv ? [] : null;\n const indices2 = [];\n let vx;\n let vy;\n let vz;\n let key;\n const precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001\n const precision = 10 ** precisionPoints;\n let i;\n let len;\n let uvi = 0;\n for (i = 0, len = positions.length; i < len; i += 3) {\n vx = positions[i];\n vy = positions[i + 1];\n vz = positions[i + 2];\n key = `${Math.round(vx * precision)}_${Math.round(vy * precision)}_${Math.round(vz * precision)}`;\n if (positionsMap[key] === undefined) {\n positionsMap[key] = uniquePositions.length / 3;\n uniquePositions.push(vx);\n uniquePositions.push(vy);\n uniquePositions.push(vz);\n if (normals) {\n uniqueNormals.push(normals[i]);\n uniqueNormals.push(normals[i + 1]);\n uniqueNormals.push(normals[i + 2]);\n }\n if (uv) {\n uniqueUV.push(uv[uvi]);\n uniqueUV.push(uv[uvi + 1]);\n }\n }\n indicesLookup[i / 3] = positionsMap[key];\n uvi += 2;\n }\n for (i = 0, len = indices.length; i < len; i++) {\n indices2[i] = indicesLookup[indices[i]];\n }\n const result = {\n positions: uniquePositions,\n indices: indices2\n };\n if (uniqueNormals) {\n result.normals = uniqueNormals;\n }\n if (uniqueUV) {\n result.uv = uniqueUV;\n\n }\n return result;\n },\n\n /**\n * Builds normal vectors from positions and indices.\n *\n * @private\n */\n buildNormals: ((() => {\n\n const a = new FloatArrayType(3);\n const b = new FloatArrayType(3);\n const c = new FloatArrayType(3);\n const ab = new FloatArrayType(3);\n const ac = new FloatArrayType(3);\n const crossVec = new FloatArrayType(3);\n\n return (positions, indices, normals) => {\n\n let i;\n let len;\n const nvecs = new Array(positions.length / 3);\n let j0;\n let j1;\n let j2;\n\n for (i = 0, len = indices.length; i < len; i += 3) {\n\n j0 = indices[i];\n j1 = indices[i + 1];\n j2 = indices[i + 2];\n\n a[0] = positions[j0 * 3];\n a[1] = positions[j0 * 3 + 1];\n a[2] = positions[j0 * 3 + 2];\n\n b[0] = positions[j1 * 3];\n b[1] = positions[j1 * 3 + 1];\n b[2] = positions[j1 * 3 + 2];\n\n c[0] = positions[j2 * 3];\n c[1] = positions[j2 * 3 + 1];\n c[2] = positions[j2 * 3 + 2];\n\n math.subVec3(b, a, ab);\n math.subVec3(c, a, ac);\n\n const normVec = math.vec3();\n\n math.normalizeVec3(math.cross3Vec3(ab, ac, crossVec), normVec);\n\n if (!nvecs[j0]) {\n nvecs[j0] = [];\n }\n if (!nvecs[j1]) {\n nvecs[j1] = [];\n }\n if (!nvecs[j2]) {\n nvecs[j2] = [];\n }\n\n nvecs[j0].push(normVec);\n nvecs[j1].push(normVec);\n nvecs[j2].push(normVec);\n }\n\n normals = (normals && normals.length === positions.length) ? normals : new Float32Array(positions.length);\n\n let count;\n let x;\n let y;\n let z;\n\n for (i = 0, len = nvecs.length; i < len; i++) { // Now go through and average out everything\n\n count = nvecs[i].length;\n\n x = 0;\n y = 0;\n z = 0;\n\n for (let j = 0; j < count; j++) {\n x += nvecs[i][j][0];\n y += nvecs[i][j][1];\n z += nvecs[i][j][2];\n }\n\n normals[i * 3] = (x / count);\n normals[i * 3 + 1] = (y / count);\n normals[i * 3 + 2] = (z / count);\n }\n\n return normals;\n };\n }))(),\n\n /**\n * Builds vertex tangent vectors from positions, UVs and indices.\n *\n * @private\n */\n buildTangents: ((() => {\n\n const tempVec3 = new FloatArrayType(3);\n const tempVec3b = new FloatArrayType(3);\n const tempVec3c = new FloatArrayType(3);\n const tempVec3d = new FloatArrayType(3);\n const tempVec3e = new FloatArrayType(3);\n const tempVec3f = new FloatArrayType(3);\n const tempVec3g = new FloatArrayType(3);\n\n return (positions, indices, uv) => {\n\n const tangents = new Float32Array(positions.length);\n\n // The vertex arrays needs to be calculated\n // before the calculation of the tangents\n\n for (let location = 0; location < indices.length; location += 3) {\n\n // Recontructing each vertex and UV coordinate into the respective vectors\n\n let index = indices[location];\n\n const v0 = positions.subarray(index * 3, index * 3 + 3);\n const uv0 = uv.subarray(index * 2, index * 2 + 2);\n\n index = indices[location + 1];\n\n const v1 = positions.subarray(index * 3, index * 3 + 3);\n const uv1 = uv.subarray(index * 2, index * 2 + 2);\n\n index = indices[location + 2];\n\n const v2 = positions.subarray(index * 3, index * 3 + 3);\n const uv2 = uv.subarray(index * 2, index * 2 + 2);\n\n const deltaPos1 = math.subVec3(v1, v0, tempVec3);\n const deltaPos2 = math.subVec3(v2, v0, tempVec3b);\n\n const deltaUV1 = math.subVec2(uv1, uv0, tempVec3c);\n const deltaUV2 = math.subVec2(uv2, uv0, tempVec3d);\n\n const r = 1 / ((deltaUV1[0] * deltaUV2[1]) - (deltaUV1[1] * deltaUV2[0]));\n\n const tangent = math.mulVec3Scalar(\n math.subVec3(\n math.mulVec3Scalar(deltaPos1, deltaUV2[1], tempVec3e),\n math.mulVec3Scalar(deltaPos2, deltaUV1[1], tempVec3f),\n tempVec3g\n ),\n r,\n tempVec3f\n );\n\n // Average the value of the vectors\n\n let addTo;\n\n for (let v = 0; v < 3; v++) {\n addTo = indices[location + v] * 3;\n tangents[addTo] += tangent[0];\n tangents[addTo + 1] += tangent[1];\n tangents[addTo + 2] += tangent[2];\n }\n }\n\n return tangents;\n };\n }))(),\n\n /**\n * Builds vertex and index arrays needed by color-indexed triangle picking.\n *\n * @private\n */\n buildPickTriangles(positions, indices, compressGeometry) {\n\n const numIndices = indices.length;\n const pickPositions = compressGeometry ? new Uint16Array(numIndices * 9) : new Float32Array(numIndices * 9);\n const pickColors = new Uint8Array(numIndices * 12);\n let primIndex = 0;\n let vi;// Positions array index\n let pvi = 0;// Picking positions array index\n let pci = 0; // Picking color array index\n\n // Triangle indices\n let i;\n let r;\n let g;\n let b;\n let a;\n\n for (let location = 0; location < numIndices; location += 3) {\n\n // Primitive-indexed triangle pick color\n\n a = (primIndex >> 24 & 0xFF);\n b = (primIndex >> 16 & 0xFF);\n g = (primIndex >> 8 & 0xFF);\n r = (primIndex & 0xFF);\n\n // A\n\n i = indices[location];\n vi = i * 3;\n\n pickPositions[pvi++] = positions[vi];\n pickPositions[pvi++] = positions[vi + 1];\n pickPositions[pvi++] = positions[vi + 2];\n\n pickColors[pci++] = r;\n pickColors[pci++] = g;\n pickColors[pci++] = b;\n pickColors[pci++] = a;\n\n // B\n\n i = indices[location + 1];\n vi = i * 3;\n\n pickPositions[pvi++] = positions[vi];\n pickPositions[pvi++] = positions[vi + 1];\n pickPositions[pvi++] = positions[vi + 2];\n\n pickColors[pci++] = r;\n pickColors[pci++] = g;\n pickColors[pci++] = b;\n pickColors[pci++] = a;\n\n // C\n\n i = indices[location + 2];\n vi = i * 3;\n\n pickPositions[pvi++] = positions[vi];\n pickPositions[pvi++] = positions[vi + 1];\n pickPositions[pvi++] = positions[vi + 2];\n\n pickColors[pci++] = r;\n pickColors[pci++] = g;\n pickColors[pci++] = b;\n pickColors[pci++] = a;\n\n primIndex++;\n }\n\n return {\n positions: pickPositions,\n colors: pickColors\n };\n },\n\n /**\n * Converts surface-perpendicular face normals to vertex normals. Assumes that the mesh contains disjoint triangles\n * that don't share vertex array elements. Works by finding groups of vertices that have the same location and\n * averaging their normal vectors.\n *\n * @returns {{positions: Array, normals: *}}\n */\n faceToVertexNormals(positions, normals, options = {}) {\n const smoothNormalsAngleThreshold = options.smoothNormalsAngleThreshold || 20;\n const vertexMap = {};\n const vertexNormals = [];\n const vertexNormalAccum = {};\n let acc;\n let vx;\n let vy;\n let vz;\n let key;\n const precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001\n const precision = 10 ** precisionPoints;\n let posi;\n let i;\n let j;\n let len;\n let a;\n let b;\n\n for (i = 0, len = positions.length; i < len; i += 3) {\n\n posi = i / 3;\n\n vx = positions[i];\n vy = positions[i + 1];\n vz = positions[i + 2];\n\n key = `${Math.round(vx * precision)}_${Math.round(vy * precision)}_${Math.round(vz * precision)}`;\n\n if (vertexMap[key] === undefined) {\n vertexMap[key] = [posi];\n } else {\n vertexMap[key].push(posi);\n }\n\n const normal = math.normalizeVec3([normals[i], normals[i + 1], normals[i + 2]]);\n\n vertexNormals[posi] = normal;\n\n acc = math.vec4([normal[0], normal[1], normal[2], 1]);\n\n vertexNormalAccum[posi] = acc;\n }\n\n for (key in vertexMap) {\n\n if (vertexMap.hasOwnProperty(key)) {\n\n const vertices = vertexMap[key];\n const numVerts = vertices.length;\n\n for (i = 0; i < numVerts; i++) {\n\n const ii = vertices[i];\n\n acc = vertexNormalAccum[ii];\n\n for (j = 0; j < numVerts; j++) {\n\n if (i === j) {\n continue;\n }\n\n const jj = vertices[j];\n\n a = vertexNormals[ii];\n b = vertexNormals[jj];\n\n const angle = Math.abs(math.angleVec3(a, b) / math.DEGTORAD);\n\n if (angle < smoothNormalsAngleThreshold) {\n\n acc[0] += b[0];\n acc[1] += b[1];\n acc[2] += b[2];\n acc[3] += 1.0;\n }\n }\n }\n }\n }\n\n for (i = 0, len = normals.length; i < len; i += 3) {\n\n acc = vertexNormalAccum[i / 3];\n\n normals[i + 0] = acc[0] / acc[3];\n normals[i + 1] = acc[1] / acc[3];\n normals[i + 2] = acc[2] / acc[3];\n\n }\n },\n\n //------------------------------------------------------------------------------------------------------------------\n // Ray casting\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n Transforms a ray by a matrix.\n @method transformRay\n @static\n @param {Number[]} matrix 4x4 matrix\n @param {Number[]} rayOrigin The ray origin\n @param {Number[]} rayDir The ray direction\n @param {Number[]} rayOriginDest The transformed ray origin\n @param {Number[]} rayDirDest The transformed ray direction\n */\n transformRay: ((() => {\n\n const tempVec4a = new FloatArrayType(4);\n const tempVec4b = new FloatArrayType(4);\n\n return (matrix, rayOrigin, rayDir, rayOriginDest, rayDirDest) => {\n\n tempVec4a[0] = rayOrigin[0];\n tempVec4a[1] = rayOrigin[1];\n tempVec4a[2] = rayOrigin[2];\n tempVec4a[3] = 1;\n\n math.transformVec4(matrix, tempVec4a, tempVec4b);\n\n rayOriginDest[0] = tempVec4b[0];\n rayOriginDest[1] = tempVec4b[1];\n rayOriginDest[2] = tempVec4b[2];\n\n tempVec4a[0] = rayDir[0];\n tempVec4a[1] = rayDir[1];\n tempVec4a[2] = rayDir[2];\n\n math.transformVec3(matrix, tempVec4a, tempVec4b);\n\n math.normalizeVec3(tempVec4b);\n\n rayDirDest[0] = tempVec4b[0];\n rayDirDest[1] = tempVec4b[1];\n rayDirDest[2] = tempVec4b[2];\n };\n }))(),\n\n /**\n Transforms a Canvas-space position into a World-space ray, in the context of a Camera.\n @method canvasPosToWorldRay\n @static\n @param {Number[]} viewMatrix View matrix\n @param {Number[]} projMatrix Projection matrix\n @param {Number[]} canvasPos The Canvas-space position.\n @param {Number[]} worldRayOrigin The World-space ray origin.\n @param {Number[]} worldRayDir The World-space ray direction.\n */\n canvasPosToWorldRay: ((() => {\n\n const tempMat4b = new FloatArrayType(16);\n const tempMat4c = new FloatArrayType(16);\n const tempVec4a = new FloatArrayType(4);\n const tempVec4b = new FloatArrayType(4);\n const tempVec4c = new FloatArrayType(4);\n const tempVec4d = new FloatArrayType(4);\n\n return (canvas, viewMatrix, projMatrix, canvasPos, worldRayOrigin, worldRayDir) => {\n\n const pvMat = math.mulMat4(projMatrix, viewMatrix, tempMat4b);\n const pvMatInverse = math.inverseMat4(pvMat, tempMat4c);\n\n // Calculate clip space coordinates, which will be in range\n // of x=[-1..1] and y=[-1..1], with y=(+1) at top\n\n const canvasWidth = canvas.width;\n const canvasHeight = canvas.height;\n\n const clipX = (canvasPos[0] - canvasWidth / 2) / (canvasWidth / 2); // Calculate clip space coordinates\n const clipY = -(canvasPos[1] - canvasHeight / 2) / (canvasHeight / 2);\n\n tempVec4a[0] = clipX;\n tempVec4a[1] = clipY;\n tempVec4a[2] = -1;\n tempVec4a[3] = 1;\n\n math.transformVec4(pvMatInverse, tempVec4a, tempVec4b);\n math.mulVec4Scalar(tempVec4b, 1 / tempVec4b[3]);\n\n tempVec4c[0] = clipX;\n tempVec4c[1] = clipY;\n tempVec4c[2] = 1;\n tempVec4c[3] = 1;\n\n math.transformVec4(pvMatInverse, tempVec4c, tempVec4d);\n math.mulVec4Scalar(tempVec4d, 1 / tempVec4d[3]);\n\n worldRayOrigin[0] = tempVec4d[0];\n worldRayOrigin[1] = tempVec4d[1];\n worldRayOrigin[2] = tempVec4d[2];\n\n math.subVec3(tempVec4d, tempVec4b, worldRayDir);\n\n math.normalizeVec3(worldRayDir);\n };\n }))(),\n\n /**\n Transforms a Canvas-space position to a Mesh's Local-space coordinate system, in the context of a Camera.\n @method canvasPosToLocalRay\n @static\n @param {Camera} camera The Camera.\n @param {Mesh} mesh The Mesh.\n @param {Number[]} viewMatrix View matrix\n @param {Number[]} projMatrix Projection matrix\n @param {Number[]} worldMatrix Modeling matrix\n @param {Number[]} canvasPos The Canvas-space position.\n @param {Number[]} localRayOrigin The Local-space ray origin.\n @param {Number[]} localRayDir The Local-space ray direction.\n */\n canvasPosToLocalRay: ((() => {\n\n const worldRayOrigin = new FloatArrayType(3);\n const worldRayDir = new FloatArrayType(3);\n\n return (canvas, viewMatrix, projMatrix, worldMatrix, canvasPos, localRayOrigin, localRayDir) => {\n math.canvasPosToWorldRay(canvas, viewMatrix, projMatrix, canvasPos, worldRayOrigin, worldRayDir);\n math.worldRayToLocalRay(worldMatrix, worldRayOrigin, worldRayDir, localRayOrigin, localRayDir);\n };\n }))(),\n\n /**\n Transforms a ray from World-space to a Mesh's Local-space coordinate system.\n @method worldRayToLocalRay\n @static\n @param {Number[]} worldMatrix The World transform matrix\n @param {Number[]} worldRayOrigin The World-space ray origin.\n @param {Number[]} worldRayDir The World-space ray direction.\n @param {Number[]} localRayOrigin The Local-space ray origin.\n @param {Number[]} localRayDir The Local-space ray direction.\n */\n worldRayToLocalRay: ((() => {\n\n const tempMat4 = new FloatArrayType(16);\n const tempVec4a = new FloatArrayType(4);\n const tempVec4b = new FloatArrayType(4);\n\n return (worldMatrix, worldRayOrigin, worldRayDir, localRayOrigin, localRayDir) => {\n\n const modelMatInverse = math.inverseMat4(worldMatrix, tempMat4);\n\n tempVec4a[0] = worldRayOrigin[0];\n tempVec4a[1] = worldRayOrigin[1];\n tempVec4a[2] = worldRayOrigin[2];\n tempVec4a[3] = 1;\n\n math.transformVec4(modelMatInverse, tempVec4a, tempVec4b);\n\n localRayOrigin[0] = tempVec4b[0];\n localRayOrigin[1] = tempVec4b[1];\n localRayOrigin[2] = tempVec4b[2];\n\n math.transformVec3(modelMatInverse, worldRayDir, localRayDir);\n };\n }))(),\n\n buildKDTree: ((() => {\n\n const KD_TREE_MAX_DEPTH = 10;\n const KD_TREE_MIN_TRIANGLES = 20;\n\n const dimLength = new Float32Array();\n\n function buildNode(triangles, indices, positions, depth) {\n const aabb = new FloatArrayType(6);\n\n const node = {\n triangles: null,\n left: null,\n right: null,\n leaf: false,\n splitDim: 0,\n aabb\n };\n\n aabb[0] = aabb[1] = aabb[2] = Number.POSITIVE_INFINITY;\n aabb[3] = aabb[4] = aabb[5] = Number.NEGATIVE_INFINITY;\n\n let t;\n let len;\n\n for (t = 0, len = triangles.length; t < len; ++t) {\n var ii = triangles[t] * 3;\n for (let j = 0; j < 3; ++j) {\n const pi = indices[ii + j] * 3;\n if (positions[pi] < aabb[0]) {\n aabb[0] = positions[pi];\n }\n if (positions[pi] > aabb[3]) {\n aabb[3] = positions[pi];\n }\n if (positions[pi + 1] < aabb[1]) {\n aabb[1] = positions[pi + 1];\n }\n if (positions[pi + 1] > aabb[4]) {\n aabb[4] = positions[pi + 1];\n }\n if (positions[pi + 2] < aabb[2]) {\n aabb[2] = positions[pi + 2];\n }\n if (positions[pi + 2] > aabb[5]) {\n aabb[5] = positions[pi + 2];\n }\n }\n }\n\n if (triangles.length < KD_TREE_MIN_TRIANGLES || depth > KD_TREE_MAX_DEPTH) {\n node.triangles = triangles;\n node.leaf = true;\n return node;\n }\n\n dimLength[0] = aabb[3] - aabb[0];\n dimLength[1] = aabb[4] - aabb[1];\n dimLength[2] = aabb[5] - aabb[2];\n\n let dim = 0;\n\n if (dimLength[1] > dimLength[dim]) {\n dim = 1;\n }\n\n if (dimLength[2] > dimLength[dim]) {\n dim = 2;\n }\n\n node.splitDim = dim;\n\n const mid = (aabb[dim] + aabb[dim + 3]) / 2;\n const left = new Array(triangles.length);\n let numLeft = 0;\n const right = new Array(triangles.length);\n let numRight = 0;\n\n for (t = 0, len = triangles.length; t < len; ++t) {\n\n var ii = triangles[t] * 3;\n const i0 = indices[ii];\n const i1 = indices[ii + 1];\n const i2 = indices[ii + 2];\n\n const pi0 = i0 * 3;\n const pi1 = i1 * 3;\n const pi2 = i2 * 3;\n\n if (positions[pi0 + dim] <= mid || positions[pi1 + dim] <= mid || positions[pi2 + dim] <= mid) {\n left[numLeft++] = triangles[t];\n } else {\n right[numRight++] = triangles[t];\n }\n }\n\n left.length = numLeft;\n right.length = numRight;\n\n node.left = buildNode(left, indices, positions, depth + 1);\n node.right = buildNode(right, indices, positions, depth + 1);\n\n return node;\n }\n\n return (indices, positions) => {\n const numTris = indices.length / 3;\n const triangles = new Array(numTris);\n for (let i = 0; i < numTris; ++i) {\n triangles[i] = i;\n }\n return buildNode(triangles, indices, positions, 0);\n };\n }))(),\n\n\n decompressPosition(position, decodeMatrix, dest) {\n dest = dest || position;\n dest[0] = position[0] * decodeMatrix[0] + decodeMatrix[12];\n dest[1] = position[1] * decodeMatrix[5] + decodeMatrix[13];\n dest[2] = position[2] * decodeMatrix[10] + decodeMatrix[14];\n },\n\n decompressPositions(positions, decodeMatrix, dest = new Float32Array(positions.length)) {\n for (let i = 0, len = positions.length; i < len; i += 3) {\n dest[i + 0] = positions[i + 0] * decodeMatrix[0] + decodeMatrix[12];\n dest[i + 1] = positions[i + 1] * decodeMatrix[5] + decodeMatrix[13];\n dest[i + 2] = positions[i + 2] * decodeMatrix[10] + decodeMatrix[14];\n }\n return dest;\n },\n\n decompressUV(uv, decodeMatrix, dest) {\n dest[0] = uv[0] * decodeMatrix[0] + decodeMatrix[6];\n dest[1] = uv[1] * decodeMatrix[4] + decodeMatrix[7];\n },\n\n decompressUVs(uvs, decodeMatrix, dest = new Float32Array(uvs.length)) {\n for (let i = 0, len = uvs.length; i < len; i += 3) {\n dest[i + 0] = uvs[i + 0] * decodeMatrix[0] + decodeMatrix[6];\n dest[i + 1] = uvs[i + 1] * decodeMatrix[4] + decodeMatrix[7];\n }\n return dest;\n },\n\n octDecodeVec2(oct, result) {\n let x = oct[0];\n let y = oct[1];\n x = (2 * x + 1) / 255;\n y = (2 * y + 1) / 255;\n const z = 1 - Math.abs(x) - Math.abs(y);\n if (z < 0) {\n x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n }\n const length = Math.sqrt(x * x + y * y + z * z);\n result[0] = x / length;\n result[1] = y / length;\n result[2] = z / length;\n return result;\n },\n\n octDecodeVec2s(octs, result) {\n for (let i = 0, j = 0, len = octs.length; i < len; i += 2) {\n let x = octs[i + 0];\n let y = octs[i + 1];\n x = (2 * x + 1) / 255;\n y = (2 * y + 1) / 255;\n const z = 1 - Math.abs(x) - Math.abs(y);\n if (z < 0) {\n x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n }\n const length = Math.sqrt(x * x + y * y + z * z);\n result[j + 0] = x / length;\n result[j + 1] = y / length;\n result[j + 2] = z / length;\n j += 3;\n }\n return result;\n }\n};\n\nmath.buildEdgeIndices = (function () {\n\n const uniquePositions = [];\n const indicesLookup = [];\n const indicesReverseLookup = [];\n const weldedIndices = [];\n\n // TODO: Optimize with caching, but need to cater to both compressed and uncompressed positions\n\n const faces = [];\n let numFaces = 0;\n const compa = new Uint16Array(3);\n const compb = new Uint16Array(3);\n const compc = new Uint16Array(3);\n const a = math.vec3();\n const b = math.vec3();\n const c = math.vec3();\n const cb = math.vec3();\n const ab = math.vec3();\n const cross = math.vec3();\n const normal = math.vec3();\n\n function weldVertices(positions, indices) {\n const positionsMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)\n let vx;\n let vy;\n let vz;\n let key;\n const precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001\n const precision = Math.pow(10, precisionPoints);\n let i;\n let len;\n let lenUniquePositions = 0;\n for (i = 0, len = positions.length; i < len; i += 3) {\n vx = positions[i];\n vy = positions[i + 1];\n vz = positions[i + 2];\n key = Math.round(vx * precision) + '_' + Math.round(vy * precision) + '_' + Math.round(vz * precision);\n if (positionsMap[key] === undefined) {\n positionsMap[key] = lenUniquePositions / 3;\n uniquePositions[lenUniquePositions++] = vx;\n uniquePositions[lenUniquePositions++] = vy;\n uniquePositions[lenUniquePositions++] = vz;\n }\n indicesLookup[i / 3] = positionsMap[key];\n }\n for (i = 0, len = indices.length; i < len; i++) {\n weldedIndices[i] = indicesLookup[indices[i]];\n indicesReverseLookup[weldedIndices[i]] = indices[i];\n }\n }\n\n function buildFaces(numIndices, positionsDecodeMatrix) {\n numFaces = 0;\n for (let i = 0, len = numIndices; i < len; i += 3) {\n const ia = ((weldedIndices[i]) * 3);\n const ib = ((weldedIndices[i + 1]) * 3);\n const ic = ((weldedIndices[i + 2]) * 3);\n if (positionsDecodeMatrix) {\n compa[0] = uniquePositions[ia];\n compa[1] = uniquePositions[ia + 1];\n compa[2] = uniquePositions[ia + 2];\n compb[0] = uniquePositions[ib];\n compb[1] = uniquePositions[ib + 1];\n compb[2] = uniquePositions[ib + 2];\n compc[0] = uniquePositions[ic];\n compc[1] = uniquePositions[ic + 1];\n compc[2] = uniquePositions[ic + 2];\n // Decode\n math.decompressPosition(compa, positionsDecodeMatrix, a);\n math.decompressPosition(compb, positionsDecodeMatrix, b);\n math.decompressPosition(compc, positionsDecodeMatrix, c);\n } else {\n a[0] = uniquePositions[ia];\n a[1] = uniquePositions[ia + 1];\n a[2] = uniquePositions[ia + 2];\n b[0] = uniquePositions[ib];\n b[1] = uniquePositions[ib + 1];\n b[2] = uniquePositions[ib + 2];\n c[0] = uniquePositions[ic];\n c[1] = uniquePositions[ic + 1];\n c[2] = uniquePositions[ic + 2];\n }\n math.subVec3(c, b, cb);\n math.subVec3(a, b, ab);\n math.cross3Vec3(cb, ab, cross);\n math.normalizeVec3(cross, normal);\n const face = faces[numFaces] || (faces[numFaces] = {normal: math.vec3()});\n face.normal[0] = normal[0];\n face.normal[1] = normal[1];\n face.normal[2] = normal[2];\n numFaces++;\n }\n }\n\n return function (positions, indices, positionsDecodeMatrix, edgeThreshold) {\n weldVertices(positions, indices);\n buildFaces(indices.length, positionsDecodeMatrix);\n const edgeIndices = [];\n const thresholdDot = Math.cos(math.DEGTORAD * edgeThreshold);\n const edges = {};\n let edge1;\n let edge2;\n let index1;\n let index2;\n let key;\n let largeIndex = false;\n let edge;\n let normal1;\n let normal2;\n let dot;\n let ia;\n let ib;\n for (let i = 0, len = indices.length; i < len; i += 3) {\n const faceIndex = i / 3;\n for (let j = 0; j < 3; j++) {\n edge1 = weldedIndices[i + j];\n edge2 = weldedIndices[i + ((j + 1) % 3)];\n index1 = Math.min(edge1, edge2);\n index2 = Math.max(edge1, edge2);\n key = index1 + \",\" + index2;\n if (edges[key] === undefined) {\n edges[key] = {\n index1: index1,\n index2: index2,\n face1: faceIndex,\n face2: undefined\n };\n } else {\n edges[key].face2 = faceIndex;\n }\n }\n }\n for (key in edges) {\n edge = edges[key];\n // an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.\n if (edge.face2 !== undefined) {\n normal1 = faces[edge.face1].normal;\n normal2 = faces[edge.face2].normal;\n dot = math.dotVec3(normal1, normal2);\n if (dot > thresholdDot) {\n continue;\n }\n }\n ia = indicesReverseLookup[edge.index1];\n ib = indicesReverseLookup[edge.index2];\n if (!largeIndex && ia > 65535 || ib > 65535) {\n largeIndex = true;\n }\n edgeIndices.push(ia);\n edgeIndices.push(ib);\n }\n return (largeIndex) ? new Uint32Array(edgeIndices) : new Uint16Array(edgeIndices);\n };\n})();\n\n\n/**\n * Returns `true` if a plane clips the given 3D positions.\n * @param {Number[]} pos Position in plane\n * @param {Number[]} dir Direction of plane\n * @param {number} positions Flat array of 3D positions.\n * @param {number} numElementsPerPosition Number of elements perposition - usually either 3 or 4.\n * @returns {boolean}\n */\nmath.planeClipsPositions3 = function (pos, dir, positions, numElementsPerPosition = 3) {\n for (let i = 0, len = positions.length; i < len; i += numElementsPerPosition) {\n tempVec3a$N[0] = positions[i + 0] - pos[0];\n tempVec3a$N[1] = positions[i + 1] - pos[1];\n tempVec3a$N[2] = positions[i + 2] - pos[2];\n let dotProduct = tempVec3a$N[0] * dir[0] + tempVec3a$N[1] * dir[1] + tempVec3a$N[2] * dir[2];\n if (dotProduct < 0) {\n return true;\n }\n }\n return false;\n};\n\nconst MAX_KD_TREE_DEPTH$1 = 15; // Increase if greater precision needed\nconst kdTreeDimLength$1 = new Float32Array(3);\n\n/**\n * Automatically indexes a {@link Viewer}'s {@link Entity}s in a 3D k-d tree\n * to support fast collision detection with 3D World-space axis-aligned boundaries (AABBs) and frustums.\n *\n * See {@link MarqueePicker} for usage example.\n *\n * An ObjectsKdTree3 is configured with a Viewer, and will then automatically\n * keep itself populated with k-d nodes that contain the Viewer's Entitys.\n *\n * We can then traverse the k-d nodes, starting at {@link ObjectsKdTree3#root}, to find\n * the contained Entities.\n */\nclass ObjectsKdTree3 {\n\n /**\n * Creates an ObjectsKdTree3.\n *\n * @param {*} cfg Configuration\n * @param {Viewer} cfg.viewer The Viewer that provides the {@link Entity}s in this ObjectsKdTree3.\n * @param {number} [cfg.maxTreeDepth=15] Optional maximum depth for the k-d tree.\n */\n constructor(cfg) {\n\n if (!cfg) {\n throw \"Parameter expected: cfg\";\n }\n\n if (!cfg.viewer) {\n throw \"Parameter expected: cfg.viewer\";\n }\n\n this.viewer = cfg.viewer;\n\n this._maxTreeDepth = cfg.maxTreeDepth || MAX_KD_TREE_DEPTH$1;\n this._root = null;\n this._needsRebuild = true;\n\n this._onModelLoaded = this.viewer.scene.on(\"modelLoaded\", (modelId) => {\n this._needsRebuild = true;\n });\n\n this._onModelUnloaded = this.viewer.scene.on(\"modelUnloaded\", (modelId) => {\n this._needsRebuild = true;\n });\n }\n\n /**\n * Gets the root ObjectsKdTree3 node.\n *\n * Each time this accessor is accessed, it will lazy-rebuild the ObjectsKdTree3\n * if {@link Entity}s have been created or removed in the {@link Viewer} since the last time it was accessed.\n */\n get root() {\n if (this._needsRebuild) {\n this._rebuild();\n }\n return this._root;\n }\n\n _rebuild() {\n const viewer = this.viewer;\n const scene = viewer.scene;\n const depth = 0;\n this._root = {\n aabb: scene.getAABB()\n };\n for (let objectId in scene.objects) {\n const entity = scene.objects[objectId];\n this._insertEntity(this._root, entity, depth + 1);\n }\n this._needsRebuild = false;\n }\n\n _insertEntity(node, entity, depth) {\n\n const entityAABB = entity.aabb;\n\n if (depth >= this._maxTreeDepth) {\n node.entities = node.entities || [];\n node.entities.push(entity);\n return;\n }\n if (node.left) {\n if (math.containsAABB3(node.left.aabb, entityAABB)) {\n this._insertEntity(node.left, entity, depth + 1);\n return;\n }\n }\n if (node.right) {\n if (math.containsAABB3(node.right.aabb, entityAABB)) {\n this._insertEntity(node.right, entity, depth + 1);\n return;\n }\n }\n const nodeAABB = node.aabb;\n kdTreeDimLength$1[0] = nodeAABB[3] - nodeAABB[0];\n kdTreeDimLength$1[1] = nodeAABB[4] - nodeAABB[1];\n kdTreeDimLength$1[2] = nodeAABB[5] - nodeAABB[2];\n let dim = 0;\n if (kdTreeDimLength$1[1] > kdTreeDimLength$1[dim]) {\n dim = 1;\n }\n if (kdTreeDimLength$1[2] > kdTreeDimLength$1[dim]) {\n dim = 2;\n }\n if (!node.left) {\n const aabbLeft = nodeAABB.slice();\n aabbLeft[dim + 3] = ((nodeAABB[dim] + nodeAABB[dim + 3]) / 2.0);\n node.left = {\n aabb: aabbLeft\n };\n if (math.containsAABB3(aabbLeft, entityAABB)) {\n this._insertEntity(node.left, entity, depth + 1);\n return;\n }\n }\n if (!node.right) {\n const aabbRight = nodeAABB.slice();\n aabbRight[dim] = ((nodeAABB[dim] + nodeAABB[dim + 3]) / 2.0);\n node.right = {\n aabb: aabbRight\n };\n if (math.containsAABB3(aabbRight, entityAABB)) {\n this._insertEntity(node.right, entity, depth + 1);\n return;\n }\n }\n node.entities = node.entities || [];\n node.entities.push(entity);\n }\n\n /**\n * Destroys this ObjectsKdTree3.\n *\n * Does not destroy the {@link Viewer} given to the constructor of the ObjectsKdTree3.\n */\n destroy() {\n const scene = this.viewer.scene;\n scene.off(this._onModelLoaded);\n scene.off(this._onModelUnloaded);\n this._root = null;\n this._needsRebuild = true;\n }\n}\n\n// Fast queue that avoids using potentially inefficient array .shift() calls\n// Based on https://github.com/creationix/fastqueue\n\n/** @private */\nclass Queue {\n\n constructor() {\n\n this._head = [];\n this._headLength = 0;\n this._tail = [];\n this._index = 0;\n this._length = 0;\n }\n\n get length() {\n return this._length;\n }\n\n shift() {\n if (this._index >= this._headLength) {\n const t = this._head;\n t.length = 0;\n this._head = this._tail;\n this._tail = t;\n this._index = 0;\n this._headLength = this._head.length;\n if (!this._headLength) {\n return;\n }\n }\n const value = this._head[this._index];\n if (this._index < 0) {\n delete this._head[this._index++];\n }\n else {\n this._head[this._index++] = undefined;\n }\n this._length--;\n return value;\n }\n\n push(item) {\n this._length++;\n this._tail.push(item);\n return this;\n };\n\n unshift(item) {\n this._head[--this._index] = item;\n this._length++;\n return this;\n }\n}\n\n/**\n * xeokit runtime statistics.\n * @type {{components: {models: number, objects: number, scenes: number, meshes: number}, memory: {indices: number, uvs: number, textures: number, materials: number, transforms: number, positions: number, programs: number, normals: number, meshes: number, colors: number}, build: {version: string}, client: {browser: string}, frame: {frameCount: number, useProgram: number, bindTexture: number, drawElements: number, bindArray: number, tasksRun: number, fps: number, drawArrays: number, tasksScheduled: number}}}\n */\nconst stats = {\n build: {\n version: \"0.8\"\n },\n client: {\n browser: (navigator && navigator.userAgent) ? navigator.userAgent : \"n/a\"\n },\n\n components: {\n scenes: 0,\n models: 0,\n meshes: 0,\n objects: 0\n },\n memory: {\n meshes: 0,\n positions: 0,\n colors: 0,\n normals: 0,\n uvs: 0,\n indices: 0,\n textures: 0,\n transforms: 0,\n materials: 0,\n programs: 0\n },\n frame: {\n frameCount: 0,\n fps: 0,\n useProgram: 0,\n bindTexture: 0,\n bindArray: 0,\n drawElements: 0,\n drawArrays: 0,\n tasksRun: 0,\n tasksScheduled: 0\n }\n};\n\n/**\n * @private\n */\n\nfunction xmlToJson(node, attributeRenamer) {\n if (node.nodeType === node.TEXT_NODE) {\n var v = node.nodeValue;\n if (v.match(/^\\s+$/) === null) {\n return v;\n }\n } else if (node.nodeType === node.ELEMENT_NODE ||\n node.nodeType === node.DOCUMENT_NODE) {\n var json = {type: node.nodeName, children: []};\n\n if (node.nodeType === node.ELEMENT_NODE) {\n for (var j = 0; j < node.attributes.length; j++) {\n var attribute = node.attributes[j];\n var nm = attributeRenamer[attribute.nodeName] || attribute.nodeName;\n json[nm] = attribute.nodeValue;\n }\n }\n\n for (var i = 0; i < node.childNodes.length; i++) {\n var item = node.childNodes[i];\n var j = xmlToJson(item, attributeRenamer);\n if (j) json.children.push(j);\n }\n\n return json;\n }\n}\n\n/**\n * @private\n */\nfunction clone(ob) {\n return JSON.parse(JSON.stringify(ob));\n}\n\n/**\n * @private\n */\nvar guidChars = [[\"0\", 10], [\"A\", 26], [\"a\", 26], [\"_\", 1], [\"$\", 1]].map(function (a) {\n var li = [];\n var st = a[0].charCodeAt(0);\n var en = st + a[1];\n for (var i = st; i < en; ++i) {\n li.push(i);\n }\n return String.fromCharCode.apply(null, li);\n}).join(\"\");\n\n/**\n * @private\n */\nfunction b64(v, len) {\n var r = (!len || len === 4) ? [0, 6, 12, 18] : [0, 6];\n return r.map(function (i) {\n return guidChars.substr(parseInt(v / (1 << i)) % 64, 1)\n }).reverse().join(\"\");\n}\n\n/**\n * @private\n */\nfunction compressGuid(g) {\n var bs = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30].map(function (i) {\n return parseInt(g.substr(i, 2), 16);\n });\n return b64(bs[0], 2) + [1, 4, 7, 10, 13].map(function (i) {\n return b64((bs[i] << 16) + (bs[i + 1] << 8) + bs[i + 2]);\n }).join(\"\");\n}\n\n/**\n * @private\n */\nfunction findNodeOfType(m, t) {\n var li = [];\n var _ = function (n) {\n if (n.type === t) li.push(n);\n (n.children || []).forEach(function (c) {\n _(c);\n });\n };\n _(m);\n return li;\n}\n\n/**\n * @private\n */\nfunction timeout(dt) {\n return new Promise(function (resolve, reject) {\n setTimeout(resolve, dt);\n });\n}\n\n/**\n * @private\n */\nfunction httpRequest(args) {\n return new Promise(function (resolve, reject) {\n var xhr = new XMLHttpRequest();\n xhr.open(args.method || \"GET\", args.url, true);\n xhr.onload = function (e) {\n if (xhr.readyState === 4) {\n if (xhr.status === 200) {\n resolve(xhr.responseXML);\n } else {\n reject(xhr.statusText);\n }\n }\n };\n xhr.send(null);\n });\n}\n\n/**\n * @private\n */\nconst queryString = function () {\n // This function is anonymous, is executed immediately and\n // the return value is assigned to QueryString!\n var query_string = {};\n var query = window.location.search.substring(1);\n var vars = query.split(\"&\");\n for (var i = 0; i < vars.length; i++) {\n var pair = vars[i].split(\"=\");\n // If first entry with this name\n if (typeof query_string[pair[0]] === \"undefined\") {\n query_string[pair[0]] = decodeURIComponent(pair[1]);\n // If second entry with this name\n } else if (typeof query_string[pair[0]] === \"string\") {\n var arr = [query_string[pair[0]], decodeURIComponent(pair[1])];\n query_string[pair[0]] = arr;\n // If third or later entry with this name\n } else {\n query_string[pair[0]].push(decodeURIComponent(pair[1]));\n }\n }\n return query_string;\n}();\n\n/**\n * @private\n */\nfunction loadJSON(url, ok, err) {\n // Avoid checking ok and err on each use.\n var defaultCallback = (_value) => undefined;\n ok = ok || defaultCallback;\n err = err || defaultCallback;\n\n var request = new XMLHttpRequest();\n request.overrideMimeType(\"application/json\");\n request.open('GET', url, true);\n request.addEventListener('load', function (event) {\n var response = event.target.response;\n if (this.status === 200) {\n var json;\n try {\n json = JSON.parse(response);\n } catch (e) {\n err(`utils.loadJSON(): Failed to parse JSON response - ${e}`);\n }\n ok(json);\n } else if (this.status === 0) {\n // Some browsers return HTTP Status 0 when using non-http protocol\n // e.g. 'file://' or 'data://'. Handle as success.\n console.warn('loadFile: HTTP Status 0 received.');\n try {\n ok(JSON.parse(response));\n } catch (e) {\n err(`utils.loadJSON(): Failed to parse JSON response - ${e}`);\n }\n } else {\n err(event);\n }\n }, false);\n\n request.addEventListener('error', function (event) {\n err(event);\n }, false);\n request.send(null);\n}\n\n/**\n * @private\n */\nfunction loadArraybuffer$1(url, ok, err) {\n // Check for data: URI\n var defaultCallback = (_value) => undefined;\n ok = ok || defaultCallback;\n err = err || defaultCallback;\n const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;\n const dataUriRegexResult = url.match(dataUriRegex);\n if (dataUriRegexResult) { // Safari can't handle data URIs through XMLHttpRequest\n const isBase64 = !!dataUriRegexResult[2];\n var data = dataUriRegexResult[3];\n data = window.decodeURIComponent(data);\n if (isBase64) {\n data = window.atob(data);\n }\n try {\n const buffer = new ArrayBuffer(data.length);\n const view = new Uint8Array(buffer);\n for (var i = 0; i < data.length; i++) {\n view[i] = data.charCodeAt(i);\n }\n core.scheduleTask(() => {\n ok(buffer);\n });\n } catch (error) {\n core.scheduleTask(() => {\n err(error);\n });\n }\n } else {\n const request = new XMLHttpRequest();\n request.open('GET', url, true);\n request.responseType = 'arraybuffer';\n request.onreadystatechange = function () {\n if (request.readyState === 4) {\n if (request.status === 200) {\n ok(request.response);\n } else {\n err('loadArrayBuffer error : ' + request.response);\n }\n }\n };\n request.send(null);\n }\n}\n\n/**\n Tests if the given object is an array\n @private\n */\nfunction isArray(value) {\n return value && !(value.propertyIsEnumerable('length')) && typeof value === 'object' && typeof value.length === 'number';\n}\n\n/**\n Tests if the given value is a string\n @param value\n @returns {Boolean}\n @private\n */\nfunction isString(value) {\n return (typeof value === 'string' || value instanceof String);\n}\n\n/**\n Tests if the given value is a number\n @param value\n @returns {Boolean}\n @private\n */\nfunction isNumeric(value) {\n return !isNaN(parseFloat(value)) && isFinite(value);\n}\n\n/**\n Tests if the given value is an ID\n @param value\n @returns {Boolean}\n @private\n */\nfunction isID(value) {\n return utils.isString(value) || utils.isNumeric(value);\n}\n\n/**\n Tests if the given components are the same, where the components can be either IDs or instances.\n @param c1\n @param c2\n @returns {Boolean}\n @private\n */\nfunction isSameComponent(c1, c2) {\n if (!c1 || !c2) {\n return false;\n }\n const id1 = (utils.isNumeric(c1) || utils.isString(c1)) ? `${c1}` : c1.id;\n const id2 = (utils.isNumeric(c2) || utils.isString(c2)) ? `${c2}` : c2.id;\n return id1 === id2;\n}\n\n/**\n Tests if the given value is a function\n @param value\n @returns {Boolean}\n @private\n */\nfunction isFunction$1(value) {\n return (typeof value === \"function\");\n}\n\n/**\n Tests if the given value is a JavaScript JSON object, eg, ````{ foo: \"bar\" }````.\n @param value\n @returns {Boolean}\n @private\n */\nfunction isObject$1(value) {\n const objectConstructor = {}.constructor;\n return (!!value && value.constructor === objectConstructor);\n}\n\n/** Returns a shallow copy\n */\nfunction copy(o) {\n return utils.apply(o, {});\n}\n\n/** Add properties of o to o2, overwriting them on o2 if already there\n */\nfunction apply(o, o2) {\n for (const name in o) {\n if (o.hasOwnProperty(name)) {\n o2[name] = o[name];\n }\n }\n return o2;\n}\n\n/**\n Add non-null/defined properties of o to o2\n @private\n */\nfunction apply2(o, o2) {\n for (const name in o) {\n if (o.hasOwnProperty(name)) {\n if (o[name] !== undefined && o[name] !== null) {\n o2[name] = o[name];\n }\n }\n }\n return o2;\n}\n\n/**\n Add properties of o to o2 where undefined or null on o2\n @private\n */\nfunction applyIf(o, o2) {\n for (const name in o) {\n if (o.hasOwnProperty(name)) {\n if (o2[name] === undefined || o2[name] === null) {\n o2[name] = o[name];\n }\n }\n }\n return o2;\n}\n\n/**\n Returns true if the given map is empty.\n @param obj\n @returns {Boolean}\n @private\n */\nfunction isEmptyObject$1(obj) {\n for (const name in obj) {\n if (obj.hasOwnProperty(name)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n Returns the given ID as a string, in quotes if the ID was a string to begin with.\n\n This is useful for logging IDs.\n\n @param {Number| String} id The ID\n @returns {String}\n @private\n */\nfunction inQuotes(id) {\n return utils.isNumeric(id) ? (`${id}`) : (`'${id}'`);\n}\n\n/**\n Returns the concatenation of two typed arrays.\n @param a\n @param b\n @returns {*|a}\n @private\n */\nfunction concat(a, b) {\n const c = new a.constructor(a.length + b.length);\n c.set(a);\n c.set(b, a.length);\n return c;\n}\n\nfunction flattenParentChildHierarchy(root) {\n var list = [];\n\n function visit(node) {\n node.id = node.uuid;\n delete node.oid;\n list.push(node);\n var children = node.children;\n\n if (children) {\n for (var i = 0, len = children.length; i < len; i++) {\n const child = children[i];\n child.parent = node.id;\n visit(children[i]);\n }\n }\n node.children = [];\n }\n\n visit(root);\n return list;\n}\n\n/**\n * @private\n */\nconst utils = {\n xmlToJson: xmlToJson,\n clone: clone,\n compressGuid: compressGuid,\n findNodeOfType: findNodeOfType,\n timeout: timeout,\n httpRequest: httpRequest,\n loadJSON: loadJSON,\n loadArraybuffer: loadArraybuffer$1,\n queryString: queryString,\n isArray: isArray,\n isString: isString,\n isNumeric: isNumeric,\n isID: isID,\n isSameComponent: isSameComponent,\n isFunction: isFunction$1,\n isObject: isObject$1,\n copy: copy,\n apply: apply,\n apply2: apply2,\n applyIf: applyIf,\n isEmptyObject: isEmptyObject$1,\n inQuotes: inQuotes,\n concat: concat,\n flattenParentChildHierarchy: flattenParentChildHierarchy\n};\n\nconst scenesRenderInfo = {}; // Used for throttling FPS for each Scene\nconst sceneIDMap = new Map$1(); // Ensures unique scene IDs\nconst taskQueue = new Queue(); // Task queue, which is pumped on each frame; tasks are pushed to it with calls to xeokit.schedule\nconst tickEvent = {sceneId: null, time: null, startTime: null, prevTime: null, deltaTime: null};\nconst taskBudget = 10; // Millisecs we're allowed to spend on tasks in each frame\nconst fpsSamples = [];\nconst numFPSSamples = 30;\n\nlet lastTime = 0;\nlet elapsedTime;\nlet totalFPS = 0;\n\n\n/**\n * @private\n */\nfunction Core() {\n\n /**\n Semantic version number. The value for this is set by an expression that's concatenated to\n the end of the built binary by the xeokit build script.\n @property version\n @namespace xeokit\n @type {String}\n */\n this.version = \"1.0.0\";\n\n /**\n Existing {@link Scene}s , mapped to their IDs\n @property scenes\n @namespace xeokit\n @type {Scene}\n */\n this.scenes = {};\n\n this._superTypes = {}; // For each component type, a list of its supertypes, ordered upwards in the hierarchy.\n\n /**\n * Registers a scene on xeokit.\n * This is called within the xeokit.Scene constructor.\n * @private\n */\n this._addScene = function (scene) {\n if (scene.id) { // User-supplied ID\n if (core.scenes[scene.id]) {\n console.error(`[ERROR] Scene ${utils.inQuotes(scene.id)} already exists`);\n return;\n }\n } else { // Auto-generated ID\n scene.id = sceneIDMap.addItem({});\n }\n core.scenes[scene.id] = scene;\n const ticksPerOcclusionTest = scene.ticksPerOcclusionTest;\n const ticksPerRender = scene.ticksPerRender;\n scenesRenderInfo[scene.id] = {\n ticksPerOcclusionTest: ticksPerOcclusionTest,\n ticksPerRender: ticksPerRender,\n renderCountdown: ticksPerRender\n };\n stats.components.scenes++;\n scene.once(\"destroyed\", () => { // Unregister destroyed scenes\n sceneIDMap.removeItem(scene.id);\n delete core.scenes[scene.id];\n delete scenesRenderInfo[scene.id];\n stats.components.scenes--;\n });\n };\n\n /**\n * @private\n */\n this.clear = function () {\n let scene;\n for (const id in core.scenes) {\n if (core.scenes.hasOwnProperty(id)) {\n scene = core.scenes[id];\n // Only clear the default Scene\n // but destroy all the others\n if (id === \"default.scene\") {\n scene.clear();\n } else {\n scene.destroy();\n delete core.scenes[scene.id];\n }\n }\n }\n };\n\n /**\n * Schedule a task to run at the next frame.\n *\n * Internally, this pushes the task to a FIFO queue. Within each frame interval, xeokit processes the queue\n * for a certain period of time, popping tasks and running them. After each frame interval, tasks that did not\n * get a chance to run during the task are left in the queue to be run next time.\n *\n * @param {Function} callback Callback that runs the task.\n * @param {Object} [scope] Scope for the callback.\n */\n this.scheduleTask = function (callback, scope = null) {\n taskQueue.push(callback);\n taskQueue.push(scope);\n };\n\n this.runTasks = function (until = -1) { // Pops and processes tasks in the queue, until the given number of milliseconds has elapsed.\n let time = (new Date()).getTime();\n let callback;\n let scope;\n let tasksRun = 0;\n while (taskQueue.length > 0 && (until < 0 || time < until)) {\n callback = taskQueue.shift();\n scope = taskQueue.shift();\n if (scope) {\n callback.call(scope);\n } else {\n callback();\n }\n time = (new Date()).getTime();\n tasksRun++;\n }\n return tasksRun;\n };\n\n this.getNumTasks = function () {\n return taskQueue.length;\n };\n}\n\n/**\n * @private\n * @type {Core}\n */\nconst core = new Core();\n\nconst frame = function () {\n let time = Date.now();\n elapsedTime = time - lastTime;\n if (lastTime > 0 && elapsedTime > 0) { // Log FPS stats\n var newFPS = 1000 / elapsedTime; // Moving average of FPS\n totalFPS += newFPS;\n fpsSamples.push(newFPS);\n if (fpsSamples.length >= numFPSSamples) {\n totalFPS -= fpsSamples.shift();\n }\n stats.frame.fps = Math.round(totalFPS / fpsSamples.length);\n }\n for (let id in core.scenes) {\n core.scenes[id].compile();\n }\n runTasks(time);\n lastTime = time;\n};\n\nfunction customSetInterval(callback, interval) {\n let expected = Date.now() + interval;\n function loop() {\n const elapsed = Date.now() - expected;\n callback();\n expected += interval;\n setTimeout(loop, Math.max(0, interval - elapsed));\n }\n loop();\n return {\n cancel: function() {\n // No need to do anything, setTimeout cannot be directly cancelled\n }\n };\n}\n\ncustomSetInterval(() => {\n frame();\n}, 100);\n\nconst renderFrame = function () {\n let time = Date.now();\n elapsedTime = time - lastTime;\n if (lastTime > 0 && elapsedTime > 0) { // Log FPS stats\n var newFPS = 1000 / elapsedTime; // Moving average of FPS\n totalFPS += newFPS;\n fpsSamples.push(newFPS);\n if (fpsSamples.length >= numFPSSamples) {\n totalFPS -= fpsSamples.shift();\n }\n stats.frame.fps = Math.round(totalFPS / fpsSamples.length);\n }\n runTasks(time);\n fireTickEvents(time);\n renderScenes();\n (window.requestPostAnimationFrame !== undefined) ? window.requestPostAnimationFrame(frame) : requestAnimationFrame(renderFrame);\n};\n\nrenderFrame();\n\nfunction runTasks(time) { // Process as many enqueued tasks as we can within the per-frame task budget\n const tasksRun = core.runTasks(time + taskBudget);\n const tasksScheduled = core.getNumTasks();\n stats.frame.tasksRun = tasksRun;\n stats.frame.tasksScheduled = tasksScheduled;\n stats.frame.tasksBudget = taskBudget;\n}\n\nfunction fireTickEvents(time) { // Fire tick event on each Scene\n tickEvent.time = time;\n for (var id in core.scenes) {\n if (core.scenes.hasOwnProperty(id)) {\n var scene = core.scenes[id];\n tickEvent.sceneId = id;\n tickEvent.startTime = scene.startTime;\n tickEvent.deltaTime = tickEvent.prevTime != null ? tickEvent.time - tickEvent.prevTime : 0;\n /**\n * Fired on each game loop iteration.\n *\n * @event tick\n * @param {String} sceneID The ID of this Scene.\n * @param {Number} startTime The time in seconds since 1970 that this Scene was instantiated.\n * @param {Number} time The time in seconds since 1970 of this \"tick\" event.\n * @param {Number} prevTime The time of the previous \"tick\" event from this Scene.\n * @param {Number} deltaTime The time in seconds since the previous \"tick\" event from this Scene.\n */\n scene.fire(\"tick\", tickEvent, true);\n }\n }\n tickEvent.prevTime = time;\n}\n\nfunction renderScenes() {\n const scenes = core.scenes;\n const forceRender = false;\n let scene;\n let renderInfo;\n let ticksPerOcclusionTest;\n let ticksPerRender;\n let id;\n for (id in scenes) {\n if (scenes.hasOwnProperty(id)) {\n\n scene = scenes[id];\n renderInfo = scenesRenderInfo[id];\n\n if (!renderInfo) {\n renderInfo = scenesRenderInfo[id] = {}; // FIXME\n }\n\n ticksPerOcclusionTest = scene.ticksPerOcclusionTest;\n if (renderInfo.ticksPerOcclusionTest !== ticksPerOcclusionTest) {\n renderInfo.ticksPerOcclusionTest = ticksPerOcclusionTest;\n renderInfo.renderCountdown = ticksPerOcclusionTest;\n }\n if (--scene.occlusionTestCountdown <= 0) {\n scene.doOcclusionTest();\n scene.occlusionTestCountdown = ticksPerOcclusionTest;\n }\n\n ticksPerRender = scene.ticksPerRender;\n if (renderInfo.ticksPerRender !== ticksPerRender) {\n renderInfo.ticksPerRender = ticksPerRender;\n renderInfo.renderCountdown = ticksPerRender;\n }\n if (--renderInfo.renderCountdown === 0) {\n scene.render(forceRender);\n renderInfo.renderCountdown = ticksPerRender;\n }\n }\n }\n}\n\n/**\n * @desc Base class for all xeokit components.\n *\n * ## Component IDs\n *\n * Every Component has an ID that's unique within the parent {@link Scene}. xeokit generates\n * the IDs automatically by default, however you can also specify them yourself. In the example below, we're creating a\n * scene comprised of {@link Scene}, {@link Material}, {@link ReadableGeometry} and\n * {@link Mesh} components, while letting xeokit generate its own ID for\n * the {@link ReadableGeometry}:\n *\n *````JavaScript\n * import {Viewer, Mesh, buildTorusGeometry, ReadableGeometry, PhongMaterial, Texture, Fresnel} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({\n * center: [0, 0, 0],\n * radius: 1.5,\n * tube: 0.5,\n * radialSegments: 32,\n * tubeSegments: 24,\n * arc: Math.PI * 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * id: \"myMaterial\",\n * ambient: [0.9, 0.3, 0.9],\n * shininess: 30,\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * specularFresnel: new Fresnel(viewer.scene, {\n * leftColor: [1.0, 1.0, 1.0],\n * rightColor: [0.0, 0.0, 0.0],\n * power: 4\n * })\n * })\n * });\n *````\n *\n * We can then find those components like this:\n *\n * ````javascript\n * // Find the Material\n * var material = viewer.scene.components[\"myMaterial\"];\n *\n * // Find all PhongMaterials in the Scene\n * var phongMaterials = viewer.scene.types[\"PhongMaterial\"];\n *\n * // Find our Material within the PhongMaterials\n * var materialAgain = phongMaterials[\"myMaterial\"];\n * ````\n *\n * ## Restriction on IDs\n *\n * Auto-generated IDs are of the form ````\"__0\"````, ````\"__1\"````, ````\"__2\"```` ... and so on.\n *\n * Scene maintains a map of these IDs, along with a counter that it increments each time it generates a new ID.\n *\n * If Scene has created the IDs listed above, and we then destroy the ````Component```` with ID ````\"__1\"````,\n * Scene will mark that ID as available, and will reuse it for the next default ID.\n *\n * Therefore, two restrictions your on IDs:\n *\n * * don't use IDs that begin with two underscores, and\n * * don't reuse auto-generated IDs of destroyed Components.\n *\n * ## Logging\n *\n * Components have methods to log ID-prefixed messages to the JavaScript console:\n *\n * ````javascript\n * material.log(\"Everything is fine, situation normal.\");\n * material.warn(\"Wait, whats that red light?\");\n * material.error(\"Aw, snap!\");\n * ````\n *\n * The logged messages will look like this in the console:\n *\n * ````text\n * [LOG] myMaterial: Everything is fine, situation normal.\n * [WARN] myMaterial: Wait, whats that red light..\n * [ERROR] myMaterial: Aw, snap!\n * ````\n *\n * ## Destruction\n *\n * Get notification of destruction of Components:\n *\n * ````javascript\n * material.once(\"destroyed\", function() {\n * this.log(\"Component was destroyed: \" + this.id);\n * });\n * ````\n *\n * Or get notification of destruction of any Component within its {@link Scene}:\n *\n * ````javascript\n * scene.on(\"componentDestroyed\", function(component) {\n * this.log(\"Component was destroyed: \" + component.id);\n * });\n * ````\n *\n * Then destroy a component like this:\n *\n * ````javascript\n * material.destroy();\n * ````\n */\nclass Component {\n\n /**\n @private\n */\n get type() {\n return \"Component\";\n }\n\n /**\n * @private\n */\n get isComponent() {\n return true;\n }\n\n constructor(owner = null, cfg = {}) {\n\n /**\n * The parent {@link Scene} that contains this Component.\n *\n * @property scene\n * @type {Scene}\n * @final\n */\n this.scene = null;\n\n if (this.type === \"Scene\") {\n this.scene = this;\n /**\n * The viewer that contains this Scene.\n * @property viewer\n * @type {Viewer}\n */\n this.viewer = cfg.viewer;\n } else {\n if (owner.type === \"Scene\") {\n this.scene = owner;\n } else if (owner instanceof Component) {\n this.scene = owner.scene;\n } else {\n throw \"Invalid param: owner must be a Component\"\n }\n this._owner = owner;\n }\n\n this._dontClear = !!cfg.dontClear; // Prevent Scene#clear from destroying this component\n\n this._renderer = this.scene._renderer;\n\n /**\n Arbitrary, user-defined metadata on this component.\n\n @property metadata\n @type Object\n */\n this.meta = cfg.meta || {};\n\n\n /**\n * ID of this Component, unique within the {@link Scene}.\n *\n * Components are mapped by this ID in {@link Scene#components}.\n *\n * @property id\n * @type {String|Number}\n */\n this.id = cfg.id; // Auto-generated by Scene by default\n\n /**\n True as soon as this Component has been destroyed\n\n @property destroyed\n @type {Boolean}\n */\n this.destroyed = false;\n\n this._attached = {}; // Attached components with names.\n this._attachments = null; // Attached components keyed to IDs - lazy-instantiated\n this._subIdMap = null; // Subscription subId pool\n this._subIdEvents = null; // Subscription subIds mapped to event names\n this._eventSubs = null; // Event names mapped to subscribers\n this._eventSubsNum = null;\n this._events = null; // Maps names to events\n this._eventCallDepth = 0; // Helps us catch stack overflows from recursive events\n this._ownedComponents = null; // // Components created with #create - lazy-instantiated\n\n if (this !== this.scene) { // Don't add scene to itself\n this.scene._addComponent(this); // Assigns this component an automatic ID if not yet assigned\n }\n\n this._updateScheduled = false; // True when #_update will be called on next tick\n\n if (owner) {\n owner._own(this);\n }\n }\n\n // /**\n // * Unique ID for this Component within its {@link Scene}.\n // *\n // * @property\n // * @type {String}\n // */\n // get id() {\n // return this._id;\n // }\n\n /**\n Indicates that we need to redraw the scene.\n\n This is called by certain subclasses after they have made some sort of state update that requires the\n renderer to perform a redraw.\n\n For example: a {@link Mesh} calls this on itself whenever you update its\n {@link Mesh#layer} property, which manually controls its render order in\n relation to other Meshes.\n\n If this component has a ````castsShadow```` property that's set ````true````, then this will also indicate\n that the renderer needs to redraw shadow map associated with this component. Components like\n {@link DirLight} have that property set when they produce light that creates shadows, while\n components like {@link Mesh\"}}layer{{/crossLink}} have that property set when they cast shadows.\n\n @protected\n */\n glRedraw() {\n if (!this._renderer) { // Called from a constructor\n return;\n }\n this._renderer.imageDirty();\n if (this.castsShadow) { // Light source or object\n this._renderer.shadowsDirty();\n }\n }\n\n /**\n Indicates that we need to re-sort the renderer's state-ordered drawables list.\n\n For efficiency, the renderer keeps its list of drawables ordered so that runs of the same state updates can be\n combined. This method is called by certain subclasses after they have made some sort of state update that would\n require re-ordering of the drawables list.\n\n For example: a {@link DirLight} calls this on itself whenever you update {@link DirLight#dir}.\n\n @protected\n */\n glResort() {\n if (!this._renderer) { // Called from a constructor\n return;\n }\n this._renderer.needStateSort();\n }\n\n /**\n * The {@link Component} that owns the lifecycle of this Component, if any.\n *\n * When that component is destroyed, this component will be automatically destroyed also.\n *\n * Will be null if this Component has no owner.\n *\n * @property owner\n * @type {Component}\n */\n get owner() {\n return this._owner;\n }\n\n /**\n * Tests if this component is of the given type, or is a subclass of the given type.\n * @type {Boolean}\n */\n isType(type) {\n return this.type === type;\n }\n\n /**\n * Fires an event on this component.\n *\n * Notifies existing subscribers to the event, optionally retains the event to give to\n * any subsequent notifications on the event as they are made.\n *\n * @param {String} event The event type name\n * @param {Object} value The event parameters\n * @param {Boolean} [forget=false] When true, does not retain for subsequent subscribers\n */\n fire(event, value, forget) {\n if (!this._events) {\n this._events = {};\n }\n if (!this._eventSubs) {\n this._eventSubs = {};\n this._eventSubsNum = {};\n }\n if (forget !== true) {\n this._events[event] = value || true; // Save notification\n }\n const subs = this._eventSubs[event];\n let sub;\n if (subs) { // Notify subscriptions\n for (const subId in subs) {\n if (subs.hasOwnProperty(subId)) {\n sub = subs[subId];\n this._eventCallDepth++;\n if (this._eventCallDepth < 300) {\n sub.callback.call(sub.scope, value);\n } else {\n this.error(\"fire: potential stack overflow from recursive event '\" + event + \"' - dropping this event\");\n }\n this._eventCallDepth--;\n }\n }\n }\n }\n\n /**\n * Subscribes to an event on this component.\n *\n * The callback is be called with this component as scope.\n *\n * @param {String} event The event\n * @param {Function} callback Called fired on the event\n * @param {Object} [scope=this] Scope for the callback\n * @return {String} Handle to the subscription, which may be used to unsubscribe with {@link #off}.\n */\n on(event, callback, scope) {\n if (!this._events) {\n this._events = {};\n }\n if (!this._subIdMap) {\n this._subIdMap = new Map$1(); // Subscription subId pool\n }\n if (!this._subIdEvents) {\n this._subIdEvents = {};\n }\n if (!this._eventSubs) {\n this._eventSubs = {};\n }\n if (!this._eventSubsNum) {\n this._eventSubsNum = {};\n }\n let subs = this._eventSubs[event];\n if (!subs) {\n subs = {};\n this._eventSubs[event] = subs;\n this._eventSubsNum[event] = 1;\n } else {\n this._eventSubsNum[event]++;\n }\n const subId = this._subIdMap.addItem(); // Create unique subId\n subs[subId] = {\n callback: callback,\n scope: scope || this\n };\n this._subIdEvents[subId] = event;\n const value = this._events[event];\n if (value !== undefined) { // A publication exists, notify callback immediately\n callback.call(scope || this, value);\n }\n return subId;\n }\n\n /**\n * Cancels an event subscription that was previously made with {@link Component#on} or {@link Component#once}.\n *\n * @param {String} subId Subscription ID\n */\n off(subId) {\n if (subId === undefined || subId === null) {\n return;\n }\n if (!this._subIdEvents) {\n return;\n }\n const event = this._subIdEvents[subId];\n if (event) {\n delete this._subIdEvents[subId];\n const subs = this._eventSubs[event];\n if (subs) {\n delete subs[subId];\n this._eventSubsNum[event]--;\n }\n this._subIdMap.removeItem(subId); // Release subId\n }\n }\n\n /**\n * Subscribes to the next occurrence of the given event, then un-subscribes as soon as the event is subIdd.\n *\n * This is equivalent to calling {@link Component#on}, and then calling {@link Component#off} inside the callback function.\n *\n * @param {String} event Data event to listen to\n * @param {Function} callback Called when fresh data is available at the event\n * @param {Object} [scope=this] Scope for the callback\n */\n once(event, callback, scope) {\n const self = this;\n const subId = this.on(event,\n function (value) {\n self.off(subId);\n callback.call(scope || this, value);\n },\n scope);\n }\n\n /**\n * Returns true if there are any subscribers to the given event on this component.\n *\n * @param {String} event The event\n * @return {Boolean} True if there are any subscribers to the given event on this component.\n */\n hasSubs(event) {\n return (this._eventSubsNum && (this._eventSubsNum[event] > 0));\n }\n\n /**\n * Logs a console debugging message for this component.\n *\n * The console message will have this format: *````[LOG] [ : ````*\n *\n * Also fires the message as a \"log\" event on the parent {@link Scene}.\n *\n * @param {String} message The message to log\n */\n log(message) {\n message = \"[LOG]\" + this._message(message);\n window.console.log(message);\n this.scene.fire(\"log\", message);\n }\n\n _message(message) {\n return \" [\" + this.type + \" \" + utils.inQuotes(this.id) + \"]: \" + message;\n }\n\n /**\n * Logs a warning for this component to the JavaScript console.\n *\n * The console message will have this format: *````[WARN] [ =: ````*\n *\n * Also fires the message as a \"warn\" event on the parent {@link Scene}.\n *\n * @param {String} message The message to log\n */\n warn(message) {\n message = \"[WARN]\" + this._message(message);\n window.console.warn(message);\n this.scene.fire(\"warn\", message);\n }\n\n /**\n * Logs an error for this component to the JavaScript console.\n *\n * The console message will have this format: *````[ERROR] [ =: ````*\n *\n * Also fires the message as an \"error\" event on the parent {@link Scene}.\n *\n * @param {String} message The message to log\n */\n error(message) {\n message = \"[ERROR]\" + this._message(message);\n window.console.error(message);\n this.scene.fire(\"error\", message);\n }\n\n /**\n * Adds a child component to this.\n *\n * When component not given, attaches the scene's default instance for the given name (if any).\n * Publishes the new child component on this component, keyed to the given name.\n *\n * @param {*} params\n * @param {String} params.name component name\n * @param {Component} [params.component] The component\n * @param {String} [params.type] Optional expected type of base type of the child; when supplied, will\n * cause an exception if the given child is not the same type or a subtype of this.\n * @param {Boolean} [params.sceneDefault=false]\n * @param {Boolean} [params.sceneSingleton=false]\n * @param {Function} [params.onAttached] Optional callback called when component attached\n * @param {Function} [params.onAttached.callback] Callback function\n * @param {Function} [params.onAttached.scope] Optional scope for callback\n * @param {Function} [params.onDetached] Optional callback called when component is detached\n * @param {Function} [params.onDetached.callback] Callback function\n * @param {Function} [params.onDetached.scope] Optional scope for callback\n * @param {{String:Function}} [params.on] Callbacks to subscribe to properties on component\n * @param {Boolean} [params.recompiles=true] When true, fires \"dirty\" events on this component\n * @private\n */\n _attach(params) {\n\n const name = params.name;\n\n if (!name) {\n this.error(\"Component 'name' expected\");\n return;\n }\n\n let component = params.component;\n const sceneDefault = params.sceneDefault;\n const sceneSingleton = params.sceneSingleton;\n const type = params.type;\n const on = params.on;\n const recompiles = params.recompiles !== false;\n\n // True when child given as config object, where parent manages its instantiation and destruction\n let managingLifecycle = false;\n\n if (component) {\n\n if (utils.isNumeric(component) || utils.isString(component)) {\n\n // Component ID given\n // Both numeric and string IDs are supported\n\n const id = component;\n\n component = this.scene.components[id];\n\n if (!component) {\n\n // Quote string IDs in errors\n\n this.error(\"Component not found: \" + utils.inQuotes(id));\n return;\n }\n }\n }\n\n if (!component) {\n\n if (sceneSingleton === true) {\n\n // Using the first instance of the component type we find\n\n const instances = this.scene.types[type];\n for (const id2 in instances) {\n if (instances.hasOwnProperty) {\n component = instances[id2];\n break;\n }\n }\n\n if (!component) {\n this.error(\"Scene has no default component for '\" + name + \"'\");\n return null;\n }\n\n } else if (sceneDefault === true) {\n\n // Using a default scene component\n\n component = this.scene[name];\n\n if (!component) {\n this.error(\"Scene has no default component for '\" + name + \"'\");\n return null;\n }\n }\n }\n\n if (component) {\n\n if (component.scene.id !== this.scene.id) {\n this.error(\"Not in same scene: \" + component.type + \" \" + utils.inQuotes(component.id));\n return;\n }\n\n if (type) {\n\n if (!component.isType(type)) {\n this.error(\"Expected a \" + type + \" type or subtype: \" + component.type + \" \" + utils.inQuotes(component.id));\n return;\n }\n }\n }\n\n if (!this._attachments) {\n this._attachments = {};\n }\n\n const oldComponent = this._attached[name];\n let subs;\n let i;\n let len;\n\n if (oldComponent) {\n\n if (component && oldComponent.id === component.id) {\n\n // Reject attempt to reattach same component\n return;\n }\n\n const oldAttachment = this._attachments[oldComponent.id];\n\n // Unsubscribe from events on old component\n\n subs = oldAttachment.subs;\n\n for (i = 0, len = subs.length; i < len; i++) {\n oldComponent.off(subs[i]);\n }\n\n delete this._attached[name];\n delete this._attachments[oldComponent.id];\n\n const onDetached = oldAttachment.params.onDetached;\n if (onDetached) {\n if (utils.isFunction(onDetached)) {\n onDetached(oldComponent);\n } else {\n onDetached.scope ? onDetached.callback.call(onDetached.scope, oldComponent) : onDetached.callback(oldComponent);\n }\n }\n\n if (oldAttachment.managingLifecycle) {\n\n // Note that we just unsubscribed from all events fired by the child\n // component, so destroying it won't fire events back at us now.\n\n oldComponent.destroy();\n }\n }\n\n if (component) {\n\n // Set and publish the new component on this component\n\n const attachment = {\n params: params,\n component: component,\n subs: [],\n managingLifecycle: managingLifecycle\n };\n\n attachment.subs.push(\n component.once(\"destroyed\",\n function () {\n attachment.params.component = null;\n this._attach(attachment.params);\n },\n this));\n\n if (recompiles) {\n attachment.subs.push(\n component.on(\"dirty\",\n function () {\n this.fire(\"dirty\", this);\n },\n this));\n }\n\n this._attached[name] = component;\n this._attachments[component.id] = attachment;\n\n // Bind destruct listener to new component to remove it\n // from this component when destroyed\n\n const onAttached = params.onAttached;\n if (onAttached) {\n if (utils.isFunction(onAttached)) {\n onAttached(component);\n } else {\n onAttached.scope ? onAttached.callback.call(onAttached.scope, component) : onAttached.callback(component);\n }\n }\n\n if (on) {\n\n let event;\n let subIdr;\n let callback;\n let scope;\n\n for (event in on) {\n if (on.hasOwnProperty(event)) {\n\n subIdr = on[event];\n\n if (utils.isFunction(subIdr)) {\n callback = subIdr;\n scope = null;\n } else {\n callback = subIdr.callback;\n scope = subIdr.scope;\n }\n\n if (!callback) {\n continue;\n }\n\n attachment.subs.push(component.on(event, callback, scope));\n }\n }\n }\n }\n\n if (recompiles) {\n this.fire(\"dirty\", this); // FIXME: May trigger spurous mesh recompilations unless able to limit with param?\n }\n\n this.fire(name, component); // Component can be null\n\n return component;\n }\n\n _checkComponent(expectedType, component) {\n if (!component.isComponent) {\n if (utils.isID(component)) {\n const id = component;\n component = this.scene.components[id];\n if (!component) {\n this.error(\"Component not found: \" + id);\n return;\n }\n } else {\n this.error(\"Expected a Component or ID\");\n return;\n }\n }\n if (expectedType !== component.type) {\n this.error(\"Expected a \" + expectedType + \" Component\");\n return;\n }\n if (component.scene.id !== this.scene.id) {\n this.error(\"Not in same scene: \" + component.type);\n return;\n }\n return component;\n }\n\n _checkComponent2(expectedTypes, component) {\n if (!component.isComponent) {\n if (utils.isID(component)) {\n const id = component;\n component = this.scene.components[id];\n if (!component) {\n this.error(\"Component not found: \" + id);\n return;\n }\n } else {\n this.error(\"Expected a Component or ID\");\n return;\n }\n }\n if (component.scene.id !== this.scene.id) {\n this.error(\"Not in same scene: \" + component.type);\n return;\n }\n for (var i = 0, len = expectedTypes.length; i < len; i++) {\n if (expectedTypes[i] === component.type) {\n return component;\n }\n }\n this.error(\"Expected component types: \" + expectedTypes);\n return null;\n }\n\n _own(component) {\n if (!this._ownedComponents) {\n this._ownedComponents = {};\n }\n if (!this._ownedComponents[component.id]) {\n this._ownedComponents[component.id] = component;\n }\n component.once(\"destroyed\", () => {\n delete this._ownedComponents[component.id];\n }, this);\n }\n\n /**\n * Protected method, called by sub-classes to queue a call to _update().\n * @protected\n * @param {Number} [priority=1]\n */\n _needUpdate(priority) {\n if (!this._updateScheduled) {\n this._updateScheduled = true;\n if (priority === 0) {\n this._doUpdate();\n } else {\n core.scheduleTask(this._doUpdate, this);\n }\n }\n }\n\n /**\n * @private\n */\n _doUpdate() {\n if (this._updateScheduled) {\n this._updateScheduled = false;\n if (this._update) {\n this._update();\n }\n }\n }\n\n /**\n * Schedule a task to perform on the next browser interval\n * @param task\n */\n scheduleTask(task) {\n core.scheduleTask(task, null);\n }\n\n /**\n * Protected virtual template method, optionally implemented\n * by sub-classes to perform a scheduled task.\n *\n * @protected\n */\n _update() {\n }\n\n /**\n * Destroys all {@link Component}s that are owned by this. These are Components that were instantiated with\n * this Component as their first constructor argument.\n */\n clear() {\n if (this._ownedComponents) {\n for (var id in this._ownedComponents) {\n if (this._ownedComponents.hasOwnProperty(id)) {\n const component = this._ownedComponents[id];\n component.destroy();\n delete this._ownedComponents[id];\n }\n }\n }\n }\n\n /**\n * Destroys this component.\n */\n destroy() {\n\n if (this.destroyed) {\n return;\n }\n\n /**\n * Fired when this Component is destroyed.\n * @event destroyed\n */\n this.fire(\"destroyed\", this.destroyed = true); // Must fire before we blow away subscription maps, below\n\n // Unsubscribe from child components and destroy then\n\n let id;\n let attachment;\n let component;\n let subs;\n let i;\n let len;\n\n if (this._attachments) {\n for (id in this._attachments) {\n if (this._attachments.hasOwnProperty(id)) {\n attachment = this._attachments[id];\n component = attachment.component;\n subs = attachment.subs;\n for (i = 0, len = subs.length; i < len; i++) {\n component.off(subs[i]);\n }\n if (attachment.managingLifecycle) {\n component.destroy();\n }\n }\n }\n }\n\n if (this._ownedComponents) {\n for (id in this._ownedComponents) {\n if (this._ownedComponents.hasOwnProperty(id)) {\n component = this._ownedComponents[id];\n component.destroy();\n delete this._ownedComponents[id];\n }\n }\n }\n\n this.scene._removeComponent(this);\n\n // Memory leak avoidance\n this._attached = {};\n this._attachments = null;\n this._subIdMap = null;\n this._subIdEvents = null;\n this._eventSubs = null;\n this._events = null;\n this._eventCallDepth = 0;\n this._ownedComponents = null;\n this._updateScheduled = false;\n }\n}\n\nconst tempVec3a$M = math.vec3();\nconst tempVec3b$A = math.vec3();\nconst tempMat4a$s = math.mat4();\n\n/**\n * @private\n */\nclass FrustumPlane {\n\n constructor() {\n this.normal = math.vec3();\n this.offset = 0;\n this.testVertex = math.vec3();\n }\n\n set(nx, ny, nz, offset) {\n const s = 1.0 / Math.sqrt(nx * nx + ny * ny + nz * nz);\n this.normal[0] = nx * s;\n this.normal[1] = ny * s;\n this.normal[2] = nz * s;\n this.offset = offset * s;\n this.testVertex[0] = (this.normal[0] >= 0.0) ? 1 : 0;\n this.testVertex[1] = (this.normal[1] >= 0.0) ? 1 : 0;\n this.testVertex[2] = (this.normal[2] >= 0.0) ? 1 : 0;\n }\n}\n\n/**\n * @private\n */\nclass Frustum$1 {\n constructor() {\n this.planes = [\n new FrustumPlane(), new FrustumPlane(), new FrustumPlane(),\n new FrustumPlane(), new FrustumPlane(), new FrustumPlane()\n ];\n }\n}\n\nFrustum$1.INSIDE = 0;\nFrustum$1.INTERSECT = 1;\nFrustum$1.OUTSIDE = 2;\n\n/** @private */\nfunction setFrustum(frustum, viewMat, projMat) {\n\n const m = math.mulMat4(projMat, viewMat, tempMat4a$s);\n\n const m0 = m[0];\n const m1 = m[1];\n const m2 = m[2];\n const m3 = m[3];\n const m4 = m[4];\n const m5 = m[5];\n const m6 = m[6];\n const m7 = m[7];\n const m8 = m[8];\n const m9 = m[9];\n const m10 = m[10];\n const m11 = m[11];\n const m12 = m[12];\n const m13 = m[13];\n const m14 = m[14];\n const m15 = m[15];\n\n frustum.planes[0].set(m3 - m0, m7 - m4, m11 - m8, m15 - m12);\n frustum.planes[1].set(m3 + m0, m7 + m4, m11 + m8, m15 + m12);\n frustum.planes[2].set(m3 - m1, m7 - m5, m11 - m9, m15 - m13);\n frustum.planes[3].set(m3 + m1, m7 + m5, m11 + m9, m15 + m13);\n frustum.planes[4].set(m3 - m2, m7 - m6, m11 - m10, m15 - m14);\n frustum.planes[5].set(m3 + m2, m7 + m6, m11 + m10, m15 + m14);\n}\n\n/** @private */\nfunction frustumIntersectsAABB3(frustum, aabb) {\n\n let ret = Frustum$1.INSIDE;\n\n const min = tempVec3a$M;\n const max = tempVec3b$A;\n\n min[0] = aabb[0];\n min[1] = aabb[1];\n min[2] = aabb[2];\n max[0] = aabb[3];\n max[1] = aabb[4];\n max[2] = aabb[5];\n\n const bminmax = [min, max];\n\n for (let i = 0; i < 6; ++i) {\n const plane = frustum.planes[i];\n if (((plane.normal[0] * bminmax[plane.testVertex[0]][0]) +\n (plane.normal[1] * bminmax[plane.testVertex[1]][1]) +\n (plane.normal[2] * bminmax[plane.testVertex[2]][2]) +\n (plane.offset)) < 0.0) {\n return Frustum$1.OUTSIDE;\n }\n\n if (((plane.normal[0] * bminmax[1 - plane.testVertex[0]][0]) +\n (plane.normal[1] * bminmax[1 - plane.testVertex[1]][1]) +\n (plane.normal[2] * bminmax[1 - plane.testVertex[2]][2]) +\n (plane.offset)) < 0.0) {\n ret = Frustum$1.INTERSECT;\n }\n }\n\n return ret;\n}\n\n/**\n * Picks a {@link Viewer}'s {@link Entity}s with a canvas-space 2D marquee box.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)\n *\n * * [[Example 1: Select Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)]\n * * [[Example 2: View-Fit Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_viewFit)]\n *\n * # Usage\n *\n * In the example below, we\n *\n * 1. Create a {@link Viewer}, arrange the {@link Camera}\n * 2. Use an {@link XKTLoaderPlugin} to load a BIM model,\n * 3. Create a {@link ObjectsKdTree3} to automatically index the `Viewer's` {@link Entity}s for fast spatial lookup,\n * 4. Create a `MarqueePicker` to pick {@link Entity}s in the {@link Viewer}, using the {@link ObjectsKdTree3} to accelerate picking\n * 5. Create a {@link MarqueePickerMouseControl} to perform the marquee-picking with the `MarqueePicker`, using mouse input to draw the marquee box on the `Viewer's` canvas.\n *\n * When the {@link MarqueePickerMouseControl} is active:\n *\n * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.\n * * Drag left-to-right to pick {@link Entity}s that intersect the box.\n * * Drag right-to-left to pick {@link Entity}s that are fully inside the box.\n * * On release, the `MarqueePicker` will fire a \"picked\" event with IDs of the picked {@link Entity}s, if any.\n * * Handling that event, we mark the {@link Entity}s as selected.\n * * Hold down CTRL to multi-pick.\n *\n * ````javascript\n * import {\n * Viewer,\n * XKTLoaderPlugin,\n * ObjectsKdTree3,\n * MarqueePicker,\n * MarqueePickerMouseControl\n * } from \"xeokit-sdk.es.js\";\n *\n * // 1\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [14.9, 14.3, 5.4];\n * viewer.scene.camera.look = [6.5, 8.3, -4.1];\n * viewer.scene.camera.up = [-0.28, 0.9, -0.3];\n *\n * // 2\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const sceneModel = xktLoader.load({\n * id: \"myModel\",\n * src: \"../../assets/models/xkt/v8/ifc/HolterTower.ifc.xkt\"\n * });\n *\n * // 3\n *\n * const objectsKdTree3 = new ObjectsKdTree3({viewer});\n *\n * // 4\n *\n * const marqueePicker = new MarqueePicker({viewer, objectsKdTree3});\n *\n * // 5\n *\n * const marqueePickerMouseControl = new MarqueePickerMouseControl({marqueePicker});\n *\n * marqueePicker.on(\"clear\", () => {\n * viewer.scene.setObjectsSelected(viewer.scene.selectedObjectIds, false);\n * });\n *\n * marqueePicker.on(\"picked\", (objectIds) => {\n * viewer.scene.setObjectsSelected(objectIds, true);\n * });\n *\n * marqueePickerMouseControl.setActive(true);\n * ````\n *\n * # Design Notes\n *\n * * The {@link ObjectsKdTree3} can be shared with any other components that want to use it to spatially search for {@link Entity}s.\n * * The {@link MarqueePickerMouseControl} can be replaced with other types of controllers (i.e. touch), or used alongside them.\n * * The `MarqueePicker` has no input handlers of its own, and provides an API through which to programmatically control marquee picking. By firing the \"picked\" events, `MarqueePicker` implements the *Blackboard Pattern*.\n */\nclass MarqueePicker extends Component {\n\n /**\n * Creates a MarqueePicker.\n *\n * @param {*} cfg Configuration\n * @param {Viewer} cfg.viewer The Viewer to pick Entities from.\n * @param {ObjectsKdTree3} cfg.objectsKdTree3 A k-d tree that indexes the Entities in the Viewer for fast spatial lookup.\n */\n constructor(cfg = {}) {\n\n if (!cfg.viewer) {\n throw \"[MarqueePicker] Missing config: viewer\";\n }\n\n if (!cfg.objectsKdTree3) {\n throw \"[MarqueePicker] Missing config: objectsKdTree3\";\n }\n\n super(cfg.viewer.scene, cfg);\n\n this.viewer = cfg.viewer;\n this._objectsKdTree3 = cfg.objectsKdTree3;\n this._canvasMarqueeCorner1 = math.vec2();\n this._canvasMarqueeCorner2 = math.vec2();\n this._canvasMarquee = math.AABB2();\n this._marqueeFrustum = new Frustum$1();\n this._marqueeFrustumProjMat = math.mat4();\n this._pickMode = false;\n\n this._marqueeElement = document.createElement('div');\n document.body.appendChild(this._marqueeElement);\n\n this._marqueeElement.style.position = \"absolute\";\n this._marqueeElement.style[\"z-index\"] = \"40000005\";\n this._marqueeElement.style.width = 8 + \"px\";\n this._marqueeElement.style.height = 8 + \"px\";\n this._marqueeElement.style.visibility = \"hidden\";\n this._marqueeElement.style.top = 0 + \"px\";\n this._marqueeElement.style.left = 0 + \"px\";\n this._marqueeElement.style[\"box-shadow\"] = \"0 2px 5px 0 #182A3D;\";\n this._marqueeElement.style[\"opacity\"] = 1.0;\n this._marqueeElement.style[\"pointer-events\"] = \"none\";\n }\n\n /**\n * Sets the canvas-space position of the first marquee box corner.\n *\n * @param corner1\n */\n setMarqueeCorner1(corner1) {\n this._canvasMarqueeCorner1.set(corner1);\n this._canvasMarqueeCorner2.set(corner1);\n this._updateMarquee();\n }\n\n /**\n * Sets the canvas-space position of the second marquee box corner.\n *\n * @param corner2\n */\n setMarqueeCorner2(corner2) {\n this._canvasMarqueeCorner2.set(corner2);\n this._updateMarquee();\n }\n\n /**\n * Sets both canvas-space corner positions of the marquee box.\n *\n * @param corner1\n * @param corner2\n */\n setMarquee(corner1, corner2) {\n this._canvasMarqueeCorner1.set(corner1);\n this._canvasMarqueeCorner2.set(corner2);\n this._updateMarquee();\n }\n\n /**\n * Sets if the marquee box is visible.\n *\n * @param {boolean} visible True if the marquee box is to be visible, else false.\n */\n setMarqueeVisible(visible) {\n this._marqueVisible = visible;\n this._marqueeElement.style.visibility = visible ? \"visible\" : \"hidden\";\n }\n\n /**\n * Gets if the marquee box is visible.\n *\n * @returns {boolean} True if the marquee box is visible, else false.\n */\n getMarqueeVisible() {\n return this._marqueVisible;\n }\n\n /**\n * Sets the pick mode.\n *\n * Supported pick modes are:\n *\n * * MarqueePicker.PICK_MODE_INSIDE - picks {@link Entity}s that are completely inside the marquee box.\n * * MarqueePicker.PICK_MODE_INTERSECTS - picks {@link Entity}s that intersect the marquee box.\n *\n * @param {number} pickMode The pick mode.\n */\n setPickMode(pickMode) {\n if (pickMode !== MarqueePicker.PICK_MODE_INSIDE && pickMode !== MarqueePicker.PICK_MODE_INTERSECTS) {\n throw \"Illegal MarqueePicker pickMode: must be MarqueePicker.PICK_MODE_INSIDE or MarqueePicker.PICK_MODE_INTERSECTS\";\n }\n if (pickMode !== this._pickMode) {\n this._marqueeElement.style[\"background-image\"] =\n pickMode === MarqueePicker.PICK_MODE_INSIDE\n /* Solid */ ? \"url(\\\"data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='%23333' stroke-width='4'/%3e%3c/svg%3e\\\")\"\n /* Dashed */ : \"url(\\\"data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='%23333' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e\\\")\";\n this._pickMode = pickMode;\n }\n }\n\n /**\n * Gets the pick mode.\n *\n * Supported pick modes are:\n *\n * * MarqueePicker.PICK_MODE_INSIDE - picks {@link Entity}s that are completely inside the marquee box.\n * * MarqueePicker.PICK_MODE_INTERSECTS - picks {@link Entity}s that intersect the marquee box.\n *\n * @returns {number} The pick mode.\n */\n getPickMode() {\n return this._pickMode;\n }\n\n /**\n * Fires a \"clear\" event on this MarqueePicker.\n */\n clear() {\n this.fire(\"clear\", {});\n }\n\n /**\n * Attempts to pick {@link Entity}s, using the current MarquePicker settings.\n *\n * Fires a \"picked\" event with the IDs of the {@link Entity}s that were picked, if any.\n *\n * @returns {string[]} IDs of the {@link Entity}s that were picked, if any\n */\n pick() {\n this._updateMarquee();\n this._buildMarqueeFrustum();\n const entityIds = [];\n const visitNode = (node, intersects = Frustum$1.INTERSECT) => {\n if (intersects === Frustum$1.INTERSECT) {\n intersects = frustumIntersectsAABB3(this._marqueeFrustum, node.aabb);\n }\n if (intersects === Frustum$1.OUTSIDE) {\n return;\n }\n if (node.entities) {\n const entities = node.entities;\n for (let i = 0, len = entities.length; i < len; i++) {\n const entity = entities[i];\n if (!entity.visible) {\n continue;\n }\n const entityAABB = entity.aabb;\n if (this._pickMode === MarqueePicker.PICK_MODE_INSIDE) {\n // Select entities that are completely inside marquee\n const intersection = frustumIntersectsAABB3(this._marqueeFrustum, entityAABB);\n if (intersection === Frustum$1.INSIDE) {\n entityIds.push(entity.id);\n }\n } else {\n // Select entities that are partially inside marquee\n const intersection = frustumIntersectsAABB3(this._marqueeFrustum, entityAABB);\n if (intersection !== Frustum$1.OUTSIDE) {\n entityIds.push(entity.id);\n }\n }\n }\n }\n if (node.left) {\n visitNode(node.left, intersects);\n }\n if (node.right) {\n visitNode(node.right, intersects);\n }\n };\n if (this._canvasMarquee[2] - this._canvasMarquee[0] > 3 || this._canvasMarquee[3] - this._canvasMarquee[1] > 3) { // Marquee pick if rectangle big enough\n visitNode(this._objectsKdTree3.root);\n }\n this.fire(\"picked\", entityIds);\n return entityIds;\n }\n\n _updateMarquee() {\n this._canvasMarquee[0] = Math.min(this._canvasMarqueeCorner1[0], this._canvasMarqueeCorner2[0]);\n this._canvasMarquee[1] = Math.min(this._canvasMarqueeCorner1[1], this._canvasMarqueeCorner2[1]);\n this._canvasMarquee[2] = Math.max(this._canvasMarqueeCorner1[0], this._canvasMarqueeCorner2[0]);\n this._canvasMarquee[3] = Math.max(this._canvasMarqueeCorner1[1], this._canvasMarqueeCorner2[1]);\n this._marqueeElement.style.width = `${this._canvasMarquee[2] - this._canvasMarquee[0]}px`;\n this._marqueeElement.style.height = `${this._canvasMarquee[3] - this._canvasMarquee[1]}px`;\n this._marqueeElement.style.left = `${this._canvasMarquee[0]}px`;\n this._marqueeElement.style.top = `${this._canvasMarquee[1]}px`;\n }\n\n _buildMarqueeFrustum() { // https://github.com/xeokit/xeokit-sdk/issues/869#issuecomment-1165375770\n const canvas = this.viewer.scene.canvas.canvas;\n const canvasWidth = canvas.clientWidth;\n const canvasHeight = canvas.clientHeight;\n const canvasLeft = canvas.clientLeft;\n const canvasTop = canvas.clientTop;\n const xCanvasToClip = 2.0 / canvasWidth;\n const yCanvasToClip = 2.0 / canvasHeight;\n const NEAR_SCALING = 17;\n const ratio = canvas.clientHeight / canvas.clientWidth;\n const FAR_PLANE = 10000;\n const left = (this._canvasMarquee[0] - canvasLeft) * xCanvasToClip + -1;\n const right = (this._canvasMarquee[2] - canvasLeft) * xCanvasToClip + -1;\n const bottom = -(this._canvasMarquee[3] - canvasTop) * yCanvasToClip + 1;\n const top = -(this._canvasMarquee[1] - canvasTop) * yCanvasToClip + 1;\n const near = this.viewer.scene.camera.frustum.near * (NEAR_SCALING * ratio);\n const far = FAR_PLANE;\n math.frustumMat4(\n left,\n right,\n bottom * ratio,\n top * ratio,\n near,\n far,\n this._marqueeFrustumProjMat,\n );\n setFrustum(this._marqueeFrustum, this.viewer.scene.camera.viewMatrix, this._marqueeFrustumProjMat);\n }\n\n /**\n * Destroys this MarqueePicker.\n *\n * Does not destroy the {@link Viewer} or the {@link ObjectsKdTree3} provided to the constructor of this MarqueePicker.\n */\n destroy() {\n super.destroy();\n if (this._marqueeElement.parentElement) {\n this._marqueeElement.parentElement.removeChild(this._marqueeElement);\n this._marqueeElement = null;\n this._objectsKdTree3 = null;\n }\n }\n}\n\n/**\n * Pick mode that picks {@link Entity}s that intersect the marquee box.\n *\n * @type {number}\n */\nMarqueePicker.PICK_MODE_INTERSECTS = 0;\n\n/**\n * Pick mode that picks {@link Entity}s that are completely inside the marquee box.\n *\n * @type {number}\n */\nMarqueePicker.PICK_MODE_INSIDE = 1;\n\n/**\n * Controls a {@link MarqueePicker} with mouse input.\n *\n * See {@link MarqueePicker} for usage example.\n *\n * When the MarqueePickerMouseControl is active:\n *\n * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.\n * * Drag left-to-right to pick Entities that intersect the box.\n * * Drag right-to-left to pick Entities that are fully inside the box.\n * * On release, the MarqueePicker will fire a \"picked\" event with IDs of the picked Entities , if any.\n */\nclass MarqueePickerMouseControl extends Component {\n\n /**\n * Creates a new MarqueePickerMouseControl.\n *\n * @param {*} cfg Configuration\n * @param {MarqueePicker} cfg.marqueePicker The MarqueePicker to control.\n */\n constructor(cfg) {\n\n super(cfg.marqueePicker, cfg);\n\n const marqueePicker = cfg.marqueePicker;\n const scene = marqueePicker.viewer.scene;\n const canvas = scene.canvas.canvas;\n\n let pageStartX;\n let pageStartY;\n let pageEndX;\n let pageEndY;\n\n let canvasStartX;\n let canvasEndX;\n\n let isMouseDragging = false;\n let isMouseDown = false;\n let mouseWasUpOffCanvas = false;\n let mouseDownTimer;\n\n canvas.addEventListener(\"mousedown\", (e) => {\n if (!this.getActive()) {\n return;\n }\n if (e.button !== 0) { // Left button only\n return;\n }\n mouseDownTimer = setTimeout(function () {\n const input = marqueePicker.viewer.scene.input;\n if (!input.keyDown[input.KEY_CTRL]) { // Clear selection unless CTRL down\n marqueePicker.clear();\n }\n pageStartX = e.pageX;\n pageStartY = e.pageY;\n canvasStartX = e.offsetX;\n marqueePicker.setMarqueeCorner1([pageStartX, pageStartY]);\n isMouseDragging = true;\n marqueePicker.viewer.cameraControl.pointerEnabled = false; // Disable camera rotation\n marqueePicker.setMarqueeVisible(true);\n canvas.style.cursor = \"crosshair\";\n }, 400);\n\n isMouseDown = true;\n });\n\n canvas.addEventListener(\"mouseup\", (e) => {\n if (!this.getActive()) {\n return;\n }\n if (!isMouseDragging && !mouseWasUpOffCanvas) {\n return\n }\n if (e.button !== 0) {\n return;\n }\n clearTimeout(mouseDownTimer);\n pageEndX = e.pageX;\n pageEndY = e.pageY;\n const width = Math.abs(pageEndX - pageStartX);\n const height = Math.abs(pageEndY - pageStartY);\n isMouseDragging = false;\n marqueePicker.viewer.cameraControl.pointerEnabled = true; // Enable camera rotation\n if (mouseWasUpOffCanvas) {\n mouseWasUpOffCanvas = false;\n }\n if (width > 3 || height > 3) { // Marquee pick if rectangle big enough\n marqueePicker.pick();\n }\n }); // Bubbling\n\n document.addEventListener(\"mouseup\", (e) => {\n if (!this.getActive()) {\n return;\n }\n if (e.button !== 0) { // check if left button was clicked\n return;\n }\n clearTimeout(mouseDownTimer);\n if (!isMouseDragging) {\n return\n }\n marqueePicker.setMarqueeVisible(false);\n isMouseDragging = false;\n isMouseDown = false;\n mouseWasUpOffCanvas = true;\n marqueePicker.viewer.cameraControl.pointerEnabled = true;\n }, true); // Capturing\n\n canvas.addEventListener(\"mousemove\", (e) => {\n if (!this.getActive()) {\n return;\n }\n if (e.button !== 0) { // check if left button was clicked\n return;\n }\n\n if (!isMouseDown) {\n return;\n }\n\n clearTimeout(mouseDownTimer);\n\n if (!isMouseDragging) {\n return\n }\n\n pageEndX = e.pageX;\n pageEndY = e.pageY;\n canvasEndX = e.offsetX;\n\n marqueePicker.setMarqueeVisible(true);\n marqueePicker.setMarqueeCorner2([pageEndX, pageEndY]);\n marqueePicker.setPickMode((canvasStartX < canvasEndX) ? MarqueePicker.PICK_MODE_INSIDE : MarqueePicker.PICK_MODE_INTERSECTS);\n\n });\n }\n\n /**\n * Activates or deactivates this MarqueePickerMouseControl.\n *\n * @param {boolean} active Whether or not to activate.\n */\n setActive(active) {\n if (this._active === active) {\n return;\n }\n this._active = active;\n this.fire(\"active\", this._active);\n }\n\n /**\n * Gets if this MarqueePickerMouseControl is active.\n *\n * @returns {boolean}\n */\n getActive() {\n return this._active;\n }\n\n /**\n *\n */\n destroy() {\n\n }\n}\n\n/**\n * A PointerCircle shows a circle, centered at the position of the\n * mouse or touch pointer.\n */\nclass PointerCircle {\n\n /**\n * Constructs a new PointerCircle.\n * @param viewer The Viewer\n * @param [cfg] PointerCircle configuration.\n * @param [cfg.active=true] Whether PointerCircle is active. The PointerCircle can only be shown when this is `true` (default).\n */\n constructor(viewer, cfg = {}) {\n\n this.viewer = viewer;\n this.scene = this.viewer.scene;\n this._circleDiv = document.createElement('div');\n this.viewer.scene.canvas.canvas.parentNode.insertBefore(this._circleDiv, this.viewer.scene.canvas.canvas);\n this._circleDiv.style.backgroundColor = \"transparent\";\n this._circleDiv.style.border = \"2px solid green\";\n this._circleDiv.style.borderRadius = \"50px\";\n this._circleDiv.style.width = \"50px\";\n this._circleDiv.style.height = \"50px\";\n this._circleDiv.style.margin = \"-200px -200px\";\n this._circleDiv.style.zIndex = \"100000\";\n this._circleDiv.style.position = \"absolute\";\n this._circleDiv.style.pointerEvents = \"none\";\n\n this._circlePos = null;\n\n this._circleMaxRadius = 200;\n this._circleMinRadius = 2;\n\n this._active = (cfg.active !== false);\n this._visible = false;\n\n this._running = false;\n this._destroyed = false;\n }\n\n /**\n * Show the circle at the given canvas coordinates and begin shrinking it.\n */\n start(circlePos) {\n if (this._destroyed) {\n return;\n }\n this._circlePos = circlePos;\n this._running = false;\n this._circleRadius = this._circleMaxRadius;\n this._circleDiv.style.borderRadius = `${this._circleRadius}px`;\n this._circleDiv.style.marginLeft = `${this._circlePos[0] - this._circleRadius}px`;\n this._circleDiv.style.marginTop = `${this._circlePos[1] - this._circleRadius}px`;\n\n const startValue = this._circleMaxRadius;\n const endValue = 2;\n let startTime;\n const duration = 300;\n\n const animateCircle = (currentTime) => {\n if (!this._running) {\n return;\n }\n if (!startTime) {\n startTime = currentTime;\n }\n\n const elapsedTime = currentTime - startTime;\n const progress = Math.min(elapsedTime / duration, 1);\n const interpolatedValue = startValue + (endValue - startValue) * progress;\n\n this._circleRadius = interpolatedValue;\n this._circleDiv.style.width = `${this._circleRadius}px`;\n this._circleDiv.style.height = `${this._circleRadius}px`;\n this._circleDiv.style.marginLeft = `${this._circlePos[0] - this._circleRadius / 2}px`;\n this._circleDiv.style.marginTop = `${this._circlePos[1] - this._circleRadius / 2}px`;\n\n if (progress < 1) {\n requestAnimationFrame(animateCircle);\n }\n };\n this._running = true;\n requestAnimationFrame(animateCircle);\n this._circleDiv.style.visibility = \"visible\";\n }\n\n /**\n * Stop the shricking circle and hide it.\n */\n stop() {\n if (this._destroyed) {\n return;\n }\n this._running = false;\n this._circleRadius = this._circleMaxRadius;\n this._circleDiv.style.borderRadius = `${this._circleRadius}px`;\n this._circleDiv.style.visibility = \"hidden\";\n }\n\n /**\n * Sets the zoom factor for the lens.\n *\n * This is `2` by default.\n *\n * @param durationMs\n */\n set durationMs(durationMs) {\n this.stop();\n this._durationMs = durationMs;\n }\n\n /**\n * Gets the zoom factor for the lens.\n *\n * This is `2` by default.\n *\n * @returns Number\n */\n get durationMs() {\n return this._durationMs;\n }\n\n /**\n * Destroys this PointerCircle.\n */\n destroy() {\n if (!this._destroyed) {\n this.stop();\n document.body.removeChild(this._circleDiv);\n this._destroyed = true;\n }\n }\n}\n\n/**\n @desc Base class for {@link Viewer} plugin classes.\n */\nclass Plugin {\n\n /**\n * Creates this Plugin and installs it into the given {@link Viewer}.\n *\n * @param {string} id ID for this plugin, unique among all plugins in the viewer.\n * @param {Viewer} viewer The viewer.\n * @param {Object} [cfg] Options\n */\n constructor(id, viewer, cfg) {\n\n /**\n * ID for this Plugin, unique within its {@link Viewer}.\n *\n * @type {string}\n */\n this.id = (cfg && cfg.id) ? cfg.id : id;\n\n /**\n * The Viewer that contains this Plugin.\n *\n * @type {Viewer}\n */\n this.viewer = viewer;\n\n this._subIdMap = null; // Subscription subId pool\n this._subIdEvents = null; // Subscription subIds mapped to event names\n this._eventSubs = null; // Event names mapped to subscribers\n this._eventSubsNum = null;\n this._events = null; // Maps names to events\n this._eventCallDepth = 0; // Helps us catch stack overflows from recursive events\n\n viewer.addPlugin(this);\n }\n\n /**\n * Schedule a task to perform on the next browser interval\n * @param task\n */\n scheduleTask(task) {\n core.scheduleTask(task, null);\n }\n\n /**\n * Fires an event on this Plugin.\n *\n * Notifies existing subscribers to the event, optionally retains the event to give to\n * any subsequent notifications on the event as they are made.\n *\n * @param {String} event The event type name\n * @param {Object} value The event parameters\n * @param {Boolean} [forget=false] When true, does not retain for subsequent subscribers\n */\n fire(event, value, forget) {\n if (!this._events) {\n this._events = {};\n }\n if (!this._eventSubs) {\n this._eventSubs = {};\n this._eventSubsNum = {};\n }\n if (forget !== true) {\n this._events[event] = value || true; // Save notification\n }\n const subs = this._eventSubs[event];\n let sub;\n if (subs) { // Notify subscriptions\n for (const subId in subs) {\n if (subs.hasOwnProperty(subId)) {\n sub = subs[subId];\n this._eventCallDepth++;\n if (this._eventCallDepth < 300) {\n sub.callback.call(sub.scope, value);\n } else {\n this.error(\"fire: potential stack overflow from recursive event '\" + event + \"' - dropping this event\");\n }\n this._eventCallDepth--;\n }\n }\n }\n }\n\n /**\n * Subscribes to an event on this Plugin.\n *\n * The callback is be called with this Plugin as scope.\n *\n * @param {String} event The event\n * @param {Function} callback Called fired on the event\n * @param {Object} [scope=this] Scope for the callback\n * @return {String} Handle to the subscription, which may be used to unsubscribe with {@link #off}.\n */\n on(event, callback, scope) {\n if (!this._events) {\n this._events = {};\n }\n if (!this._subIdMap) {\n this._subIdMap = new Map$1(); // Subscription subId pool\n }\n if (!this._subIdEvents) {\n this._subIdEvents = {};\n }\n if (!this._eventSubs) {\n this._eventSubs = {};\n }\n if (!this._eventSubsNum) {\n this._eventSubsNum = {};\n }\n let subs = this._eventSubs[event];\n if (!subs) {\n subs = {};\n this._eventSubs[event] = subs;\n this._eventSubsNum[event] = 1;\n } else {\n this._eventSubsNum[event]++;\n }\n const subId = this._subIdMap.addItem(); // Create unique subId\n subs[subId] = {\n callback: callback,\n scope: scope || this\n };\n this._subIdEvents[subId] = event;\n const value = this._events[event];\n if (value !== undefined) { // A publication exists, notify callback immediately\n callback.call(scope || this, value);\n }\n return subId;\n }\n\n /**\n * Cancels an event subscription that was previously made with {@link Plugin#on} or {@link Plugin#once}.\n *\n * @param {String} subId Subscription ID\n */\n off(subId) {\n if (subId === undefined || subId === null) {\n return;\n }\n if (!this._subIdEvents) {\n return;\n }\n const event = this._subIdEvents[subId];\n if (event) {\n delete this._subIdEvents[subId];\n const subs = this._eventSubs[event];\n if (subs) {\n delete subs[subId];\n this._eventSubsNum[event]--;\n }\n this._subIdMap.removeItem(subId); // Release subId\n }\n }\n\n /**\n * Subscribes to the next occurrence of the given event, then un-subscribes as soon as the event is subIdd.\n *\n * This is equivalent to calling {@link Plugin#on}, and then calling {@link Plugin#off} inside the callback function.\n *\n * @param {String} event Data event to listen to\n * @param {Function} callback Called when fresh data is available at the event\n * @param {Object} [scope=this] Scope for the callback\n */\n once(event, callback, scope) {\n const self = this;\n const subId = this.on(event,\n function (value) {\n self.off(subId);\n callback.call(scope || this, value);\n },\n scope);\n }\n\n /**\n * Returns true if there are any subscribers to the given event on this Plugin.\n *\n * @param {String} event The event\n * @return {Boolean} True if there are any subscribers to the given event on this Plugin.\n */\n hasSubs(event) {\n return (this._eventSubsNum && (this._eventSubsNum[event] > 0));\n }\n\n /**\n * Logs a message to the JavaScript developer console, prefixed with the ID of this Plugin.\n *\n * @param {String} msg The error message\n */\n log(msg) {\n console.log(`[xeokit plugin ${this.id}]: ${msg}`);\n }\n\n /**\n * Logs a warning message to the JavaScript developer console, prefixed with the ID of this Plugin.\n *\n * @param {String} msg The error message\n */\n warn(msg) {\n console.warn(`[xeokit plugin ${this.id}]: ${msg}`);\n }\n\n /**\n * Logs an error message to the JavaScript developer console, prefixed with the ID of this Plugin.\n *\n * @param {String} msg The error message\n */\n error(msg) {\n console.error(`[xeokit plugin ${this.id}]: ${msg}`);\n }\n\n /**\n * Sends a message to this Plugin.\n *\n * @private\n */\n send(name, value) {\n //...\n }\n\n /**\n * Destroys this Plugin and removes it from its {@link Viewer}.\n */\n destroy() {\n this.viewer.removePlugin(this);\n }\n}\n\nconst tempVec3a$L = math.vec3();\n\n/**\n * Given a view matrix and a relative-to-center (RTC) coordinate origin, returns a view matrix\n * to transform RTC coordinates to View-space.\n *\n * The returned view matrix is\n *\n * @private\n */\nconst createRTCViewMat = (function () {\n\n const tempMat = new Float64Array(16);\n const rtcCenterWorld = new Float64Array(4);\n const rtcCenterView = new Float64Array(4);\n\n return function (viewMat, rtcCenter, rtcViewMat) {\n rtcViewMat = rtcViewMat || tempMat;\n rtcCenterWorld[0] = rtcCenter[0];\n rtcCenterWorld[1] = rtcCenter[1];\n rtcCenterWorld[2] = rtcCenter[2];\n rtcCenterWorld[3] = 1;\n math.transformVec4(viewMat, rtcCenterWorld, rtcCenterView);\n math.setMat4Translation(viewMat, rtcCenterView, rtcViewMat);\n return rtcViewMat.slice ();\n }\n}());\n\n/**\n * Converts a World-space 3D position to RTC.\n *\n * Given a double-precision World-space position, returns a double-precision relative-to-center (RTC) center pos\n * and a single-precision offset fom that center.\n * @private\n * @param {Float64Array} worldPos The World-space position.\n * @param {Float64Array} rtcCenter Double-precision relative-to-center (RTC) center pos.\n * @param {Float32Array} rtcPos Single-precision offset fom that center.\n */\nfunction worldToRTCPos(worldPos, rtcCenter, rtcPos) {\n\n const xHigh = Float32Array.from([worldPos[0]])[0];\n const xLow = worldPos[0] - xHigh;\n\n const yHigh = Float32Array.from([worldPos[1]])[0];\n const yLow = worldPos[1] - yHigh;\n\n const zHigh = Float32Array.from([worldPos[2]])[0];\n const zLow = worldPos[2] - zHigh;\n\n rtcCenter[0] = xHigh;\n rtcCenter[1] = yHigh;\n rtcCenter[2] = zHigh;\n\n rtcPos[0] = xLow;\n rtcPos[1] = yLow;\n rtcPos[2] = zLow;\n}\n\n\n/**\n * Converts a flat array of double-precision positions to RTC positions, if necessary.\n *\n * Conversion is necessary if the coordinates have values larger than can be expressed at single-precision. When\n * that's the case, then this function will compute the RTC coordinates and RTC center and return true. Otherwise\n * this function does nothing and returns false.\n *\n * When computing the RTC position, this function uses a modulus operation to ensure that, whenever possible,\n * identical RTC centers are reused for different positions arrays.\n *\n * @private\n * @param {Float64Array} worldPositions Flat array of World-space 3D positions.\n * @param {Float64Array} rtcPositions Outputs the computed flat array of 3D RTC positions.\n * @param {Float64Array} rtcCenter Outputs the computed double-precision relative-to-center (RTC) center pos.\n * @param {Number} [cellSize=10000000] The size of each coordinate cell within the RTC coordinate system.\n * @returns {Boolean} ````True```` if the positions actually needed conversion to RTC, else ````false````. When\n * ````false````, we can safely ignore the data returned in ````rtcPositions```` and ````rtcCenter````,\n * since ````rtcCenter```` will equal ````[0,0,0]````, and ````rtcPositions```` will contain identical values to ````positions````.\n */\nfunction worldToRTCPositions(worldPositions, rtcPositions, rtcCenter, cellSize = 1000) {\n\n const center = math.getPositionsCenter(worldPositions, tempVec3a$L);\n\n const rtcCenterX = Math.round(center[0] / cellSize) * cellSize;\n const rtcCenterY = Math.round(center[1] / cellSize) * cellSize;\n const rtcCenterZ = Math.round(center[2] / cellSize) * cellSize;\n\n rtcCenter[0] = rtcCenterX;\n rtcCenter[1] = rtcCenterY;\n rtcCenter[2] = rtcCenterZ;\n\n const rtcNeeded = (rtcCenter[0] !== 0 || rtcCenter[1] !== 0 || rtcCenter[2] !== 0);\n\n if (rtcNeeded) {\n for (let i = 0, len = worldPositions.length; i < len; i += 3) {\n rtcPositions[i + 0] = worldPositions[i + 0] - rtcCenterX;\n rtcPositions[i + 1] = worldPositions[i + 1] - rtcCenterY;\n rtcPositions[i + 2] = worldPositions[i + 2] - rtcCenterZ;\n }\n }\n\n return rtcNeeded;\n}\n\n/**\n * Converts an RTC 3D position to World-space.\n *\n * @private\n * @param {Float64Array} rtcCenter Double-precision relative-to-center (RTC) center pos.\n * @param {Float32Array} rtcPos Single-precision offset fom that center.\n * @param {Float64Array} worldPos The World-space position.\n */\nfunction rtcToWorldPos(rtcCenter, rtcPos, worldPos) {\n worldPos[0] = rtcCenter[0] + rtcPos[0];\n worldPos[1] = rtcCenter[1] + rtcPos[1];\n worldPos[2] = rtcCenter[2] + rtcPos[2];\n return worldPos;\n}\n\n/**\n * Given a 3D plane defined by distance from origin and direction, and an RTC center position,\n * return a plane position that is relative to the RTC center.\n *\n * @private\n * @param dist\n * @param dir\n * @param rtcCenter\n * @param rtcPlanePos\n * @returns {*}\n */\nfunction getPlaneRTCPos(dist, dir, rtcCenter, rtcPlanePos) {\n const rtcCenterToPlaneDist = math.dotVec3(dir, rtcCenter) + dist;\n const dirNormalized = math.normalizeVec3(dir, tempVec3a$L);\n math.mulVec3Scalar(dirNormalized, -rtcCenterToPlaneDist, rtcPlanePos);\n return rtcPlanePos;\n}\n\n/**\n * @private\n * @type {{PICKABLE: number, CLIPPABLE: number, BACKFACES: number, VISIBLE: number, SELECTED: number, OUTLINED: number, CULLED: number, RECEIVE_SHADOW: number, COLLIDABLE: number, XRAYED: number, CAST_SHADOW: number, EDGES: number, HIGHLIGHTED: number}}\n */\nconst ENTITY_FLAGS = {\n VISIBLE: 1,\n CULLED: 1 << 2,\n PICKABLE: 1 << 3,\n CLIPPABLE: 1 << 4,\n COLLIDABLE: 1 << 5,\n CAST_SHADOW: 1 << 6,\n RECEIVE_SHADOW: 1 << 7,\n XRAYED: 1 << 8,\n HIGHLIGHTED: 1 << 9,\n SELECTED: 1 << 10,\n EDGES: 1 << 11,\n BACKFACES: 1 << 12,\n TRANSPARENT: 1 << 13\n};\n\nconst tempFloatRGB = new Float32Array([0, 0, 0]);\nconst tempIntRGB = new Uint16Array([0, 0, 0]);\n\nmath.OBB3();\n\n/**\n * An entity within a {@link SceneModel}\n *\n * * Created with {@link SceneModel#createEntity}\n * * Stored by ID in {@link SceneModel#entities}\n * * Has one or more {@link SceneModelMesh}es\n *\n * @implements {Entity}\n */\nclass SceneModelEntity {\n\n /**\n * @private\n */\n constructor(model, isObject, id, meshes, flags, lodCullable) {\n\n this._isObject = isObject;\n\n /**\n * The {@link Scene} to which this SceneModelEntity belongs.\n */\n this.scene = model.scene;\n\n /**\n * The {@link SceneModel} to which this SceneModelEntity belongs.\n */\n this.model = model;\n\n /**\n * The {@link SceneModelMesh}es belonging to this SceneModelEntity.\n *\n * * These are created with {@link SceneModel#createMesh} and registered in {@ilnk SceneModel#meshes}\n * * Each SceneModelMesh belongs to one SceneModelEntity\n */\n this.meshes = meshes;\n\n this._numPrimitives = 0;\n\n for (let i = 0, len = this.meshes.length; i < len; i++) { // TODO: tidier way? Refactor?\n const mesh = this.meshes[i];\n mesh.parent = this;\n mesh.entity = this;\n this._numPrimitives += mesh.numPrimitives;\n }\n\n /**\n * The unique ID of this SceneModelEntity.\n */\n this.id = id;\n\n /**\n * The original system ID of this SceneModelEntity.\n */\n this.originalSystemId = math.unglobalizeObjectId(model.id, id);\n\n this._flags = flags;\n this._aabb = math.AABB3();\n this._aabbDirty = true;\n\n this._offset = math.vec3();\n this._colorizeUpdated = false;\n this._opacityUpdated = false;\n\n this._lodCullable = (!!lodCullable);\n this._culled = false;\n this._culledVFC = false;\n this._culledLOD = false;\n\n if (this._isObject) {\n model.scene._registerObject(this);\n }\n }\n\n _transformDirty() {\n this._aabbDirty = true;\n this.model._transformDirty();\n\n }\n\n _sceneModelDirty() { // Called by SceneModel when SceneModel's matrix is updated\n this._aabbDirty = true;\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._sceneModelDirty();\n }\n }\n\n /**\n * World-space 3D axis-aligned bounding box (AABB) of this SceneModelEntity.\n *\n * Represented by a six-element Float64Array containing the min/max extents of the\n * axis-aligned volume, ie. ````[xmin, ymin, zmin, xmax, ymax, zmax]````.\n *\n * @type {Float64Array}\n */\n get aabb() {\n if (this._aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this.meshes[i].aabb);\n }\n this._aabbDirty = false;\n }\n // if (this._aabbDirty) {\n // math.AABB3ToOBB3(this._aabb, tempOBB3a);\n // math.transformOBB3(this.model.matrix, tempOBB3a);\n // math.OBB3ToAABB3(tempOBB3a, this._worldAABB);\n // this._worldAABB[0] += this._offset[0];\n // this._worldAABB[1] += this._offset[1];\n // this._worldAABB[2] += this._offset[2];\n // this._worldAABB[3] += this._offset[0];\n // this._worldAABB[4] += this._offset[1];\n // this._worldAABB[5] += this._offset[2];\n // this._aabbDirty = false;\n // }\n return this._aabb;\n }\n\n get isEntity() {\n return true;\n }\n\n /**\n * Returns false to indicate that this Entity subtype is not a model.\n * @returns {boolean}\n */\n get isModel() {\n return false;\n }\n\n /**\n * Returns ````true```` if this SceneModelEntity represents an object.\n *\n * When this is ````true````, the SceneModelEntity will be registered by {@link SceneModelEntity#id}\n * in {@link Scene#objects} and may also have a corresponding {@link MetaObject}.\n *\n * @type {Boolean}\n */\n get isObject() {\n return this._isObject;\n }\n\n get numPrimitives() {\n return this._numPrimitives;\n }\n\n /**\n * The approximate number of triangles in this SceneModelEntity.\n *\n * @type {Number}\n */\n get numTriangles() {\n return this._numPrimitives;\n }\n\n /**\n * Gets if this SceneModelEntity is visible.\n *\n * Only rendered when {@link SceneModelEntity#visible} is ````true````\n * and {@link SceneModelEntity#culled} is ````false````.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#visible} are\n * both ````true```` the SceneModelEntity will be registered\n * by {@link SceneModelEntity#id} in {@link Scene#visibleObjects}.\n *\n * @type {Boolean}\n */\n get visible() {\n return this._getFlag(ENTITY_FLAGS.VISIBLE);\n }\n\n /**\n * Sets if this SceneModelEntity is visible.\n *\n * Only rendered when {@link SceneModelEntity#visible} is ````true```` and {@link SceneModelEntity#culled} is ````false````.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#visible} are\n * both ````true```` the SceneModelEntity will be\n * registered by {@link SceneModelEntity#id} in {@link Scene#visibleObjects}.\n *\n * @type {Boolean}\n */\n set visible(visible) {\n if (!!(this._flags & ENTITY_FLAGS.VISIBLE) === visible) {\n return; // Redundant update\n }\n if (visible) {\n this._flags = this._flags | ENTITY_FLAGS.VISIBLE;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.VISIBLE;\n }\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setVisible(this._flags);\n }\n if (this._isObject) {\n this.model.scene._objectVisibilityUpdated(this);\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets if this SceneModelEntity is highlighted.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#highlighted} are both ````true```` the SceneModelEntity will be\n * registered by {@link SceneModelEntity#id} in {@link Scene#highlightedObjects}.\n *\n * @type {Boolean}\n */\n get highlighted() {\n return this._getFlag(ENTITY_FLAGS.HIGHLIGHTED);\n }\n\n /**\n * Sets if this SceneModelEntity is highlighted.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#highlighted} are both ````true```` the SceneModelEntity will be\n * registered by {@link SceneModelEntity#id} in {@link Scene#highlightedObjects}.\n *\n * @type {Boolean}\n */\n set highlighted(highlighted) {\n if (!!(this._flags & ENTITY_FLAGS.HIGHLIGHTED) === highlighted) {\n return; // Redundant update\n }\n\n if (highlighted) {\n this._flags = this._flags | ENTITY_FLAGS.HIGHLIGHTED;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.HIGHLIGHTED;\n }\n for (var i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setHighlighted(this._flags);\n }\n if (this._isObject) {\n this.model.scene._objectHighlightedUpdated(this);\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets if this SceneModelEntity is xrayed.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#xrayed} are both ````true``` the SceneModelEntity will be\n * registered by {@link SceneModelEntity#id} in {@link Scene#xrayedObjects}.\n *\n * @type {Boolean}\n */\n get xrayed() {\n return this._getFlag(ENTITY_FLAGS.XRAYED);\n }\n\n /**\n * Sets if this SceneModelEntity is xrayed.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#xrayed} are both ````true``` the SceneModelEntity will be\n * registered by {@link SceneModelEntity#id} in {@link Scene#xrayedObjects}.\n *\n * @type {Boolean}\n */\n set xrayed(xrayed) {\n if (!!(this._flags & ENTITY_FLAGS.XRAYED) === xrayed) {\n return; // Redundant update\n }\n if (xrayed) {\n this._flags = this._flags | ENTITY_FLAGS.XRAYED;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.XRAYED;\n }\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setXRayed(this._flags);\n }\n if (this._isObject) {\n this.model.scene._objectXRayedUpdated(this);\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets if this SceneModelEntity is selected.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#selected} are both ````true``` the SceneModelEntity will be\n * registered by {@link SceneModelEntity#id} in {@link Scene#selectedObjects}.\n *\n * @type {Boolean}\n */\n get selected() {\n return this._getFlag(ENTITY_FLAGS.SELECTED);\n }\n\n /**\n * Gets if this SceneModelEntity is selected.\n *\n * When {@link SceneModelEntity#isObject} and {@link SceneModelEntity#selected} are both ````true``` the SceneModelEntity will be\n * registered by {@link SceneModelEntity#id} in {@link Scene#selectedObjects}.\n *\n * @type {Boolean}\n */\n set selected(selected) {\n if (!!(this._flags & ENTITY_FLAGS.SELECTED) === selected) {\n return; // Redundant update\n }\n if (selected) {\n this._flags = this._flags | ENTITY_FLAGS.SELECTED;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.SELECTED;\n }\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setSelected(this._flags);\n }\n if (this._isObject) {\n this.model.scene._objectSelectedUpdated(this);\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets if this SceneModelEntity's edges are enhanced.\n *\n * @type {Boolean}\n */\n get edges() {\n return this._getFlag(ENTITY_FLAGS.EDGES);\n }\n\n /**\n * Sets if this SceneModelEntity's edges are enhanced.\n *\n * @type {Boolean}\n */\n set edges(edges) {\n if (!!(this._flags & ENTITY_FLAGS.EDGES) === edges) {\n return; // Redundant update\n }\n if (edges) {\n this._flags = this._flags | ENTITY_FLAGS.EDGES;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.EDGES;\n }\n for (var i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setEdges(this._flags);\n }\n this.model.glRedraw();\n }\n\n\n get culledVFC() {\n return !!(this._culledVFC);\n }\n\n set culledVFC(culled) {\n this._culledVFC = culled;\n this._setCulled();\n }\n\n get culledLOD() {\n return !!(this._culledLOD);\n }\n\n set culledLOD(culled) {\n this._culledLOD = culled;\n this._setCulled();\n }\n\n /**\n * Gets if this SceneModelEntity is culled.\n *\n * Only rendered when {@link SceneModelEntity#visible} is ````true```` and {@link SceneModelEntity#culled} is ````false````.\n *\n * @type {Boolean}\n */\n get culled() {\n return !!(this._culled);\n // return this._getFlag(ENTITY_FLAGS.CULLED);\n }\n\n /**\n * Sets if this SceneModelEntity is culled.\n *\n * Only rendered when {@link SceneModelEntity#visible} is ````true```` and {@link SceneModelEntity#culled} is ````false````.\n *\n * @type {Boolean}\n */\n set culled(culled) {\n this._culled = culled;\n this._setCulled();\n }\n\n _setCulled() {\n let culled = !!(this._culled) || !!(this._culledLOD && this._lodCullable) || !!(this._culledVFC);\n if (!!(this._flags & ENTITY_FLAGS.CULLED) === culled) {\n return; // Redundant update\n }\n if (culled) {\n this._flags = this._flags | ENTITY_FLAGS.CULLED;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.CULLED;\n }\n for (var i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setCulled(this._flags);\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets if this SceneModelEntity is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * @type {Boolean}\n */\n get clippable() {\n return this._getFlag(ENTITY_FLAGS.CLIPPABLE);\n }\n\n /**\n * Sets if this SceneModelEntity is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * @type {Boolean}\n */\n set clippable(clippable) {\n if ((!!(this._flags & ENTITY_FLAGS.CLIPPABLE)) === clippable) {\n return; // Redundant update\n }\n if (clippable) {\n this._flags = this._flags | ENTITY_FLAGS.CLIPPABLE;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.CLIPPABLE;\n }\n for (var i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setClippable(this._flags);\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets if this SceneModelEntity is included in boundary calculations.\n *\n * @type {Boolean}\n */\n get collidable() {\n return this._getFlag(ENTITY_FLAGS.COLLIDABLE);\n }\n\n /**\n * Sets if this SceneModelEntity is included in boundary calculations.\n *\n * @type {Boolean}\n */\n set collidable(collidable) {\n if (!!(this._flags & ENTITY_FLAGS.COLLIDABLE) === collidable) {\n return; // Redundant update\n }\n if (collidable) {\n this._flags = this._flags | ENTITY_FLAGS.COLLIDABLE;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.COLLIDABLE;\n }\n for (var i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setCollidable(this._flags);\n }\n }\n\n /**\n * Gets if this SceneModelEntity is pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * @type {Boolean}\n */\n get pickable() {\n return this._getFlag(ENTITY_FLAGS.PICKABLE);\n }\n\n /**\n * Sets if this SceneModelEntity is pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * @type {Boolean}\n */\n set pickable(pickable) {\n if (!!(this._flags & ENTITY_FLAGS.PICKABLE) === pickable) {\n return; // Redundant update\n }\n if (pickable) {\n this._flags = this._flags | ENTITY_FLAGS.PICKABLE;\n } else {\n this._flags = this._flags & ~ENTITY_FLAGS.PICKABLE;\n }\n for (var i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setPickable(this._flags);\n }\n }\n\n /**\n * Gets the SceneModelEntity's RGB colorize color, multiplies by the SceneModelEntity's rendered fragment colors.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * @type {Number[]}\n */\n get colorize() { // [0..1, 0..1, 0..1]\n if (this.meshes.length === 0) {\n return null;\n }\n const colorize = this.meshes[0]._colorize;\n tempFloatRGB[0] = colorize[0] / 255.0; // Unquantize\n tempFloatRGB[1] = colorize[1] / 255.0;\n tempFloatRGB[2] = colorize[2] / 255.0;\n return tempFloatRGB;\n }\n\n /**\n * Sets the SceneModelEntity's RGB colorize color, multiplies by the SceneModelEntity's rendered fragment colors.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * @type {Number[]}\n */\n set colorize(color) { // [0..1, 0..1, 0..1]\n if (color) {\n tempIntRGB[0] = Math.floor(color[0] * 255.0); // Quantize\n tempIntRGB[1] = Math.floor(color[1] * 255.0);\n tempIntRGB[2] = Math.floor(color[2] * 255.0);\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setColorize(tempIntRGB);\n }\n } else {\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setColorize(null);\n }\n }\n if (this._isObject) {\n const colorized = (!!color);\n this.scene._objectColorizeUpdated(this, colorized);\n this._colorizeUpdated = colorized;\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets the SceneModelEntity's opacity factor.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n get opacity() {\n if (this.meshes.length > 0) {\n return (this.meshes[0]._colorize[3] / 255.0);\n } else {\n return 1.0;\n }\n }\n\n /**\n * Sets the SceneModelEntity's opacity factor.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n set opacity(opacity) {\n if (this.meshes.length === 0) {\n return;\n }\n const opacityUpdated = (opacity !== null && opacity !== undefined);\n const lastOpacityQuantized = this.meshes[0]._colorize[3];\n let opacityQuantized = 255;\n if (opacityUpdated) {\n if (opacity < 0) {\n opacity = 0;\n } else if (opacity > 1) {\n opacity = 1;\n }\n opacityQuantized = Math.floor(opacity * 255.0); // Quantize\n if (lastOpacityQuantized === opacityQuantized) {\n return;\n }\n } else {\n opacityQuantized = 255.0;\n if (lastOpacityQuantized === opacityQuantized) {\n return;\n }\n }\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setOpacity(opacityQuantized, this._flags);\n }\n if (this._isObject) {\n this.scene._objectOpacityUpdated(this, opacityUpdated);\n this._opacityUpdated = opacityUpdated;\n }\n this.model.glRedraw();\n }\n\n /**\n * Gets the SceneModelEntity's 3D World-space offset.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get offset() {\n return this._offset;\n }\n\n /**\n * Sets the SceneModelEntity's 3D World-space offset.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set offset(offset) {\n if (offset) {\n this._offset[0] = offset[0];\n this._offset[1] = offset[1];\n this._offset[2] = offset[2];\n } else {\n this._offset[0] = 0;\n this._offset[1] = 0;\n this._offset[2] = 0;\n }\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._setOffset(this._offset);\n }\n this._aabbDirty = true;\n this.model._aabbDirty = true;\n this.scene._aabbDirty = true;\n this.scene._objectOffsetUpdated(this, offset);\n this.model.glRedraw();\n }\n\n get saoEnabled() {\n return this.model.saoEnabled;\n }\n\n getEachVertex(callback) {\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i].getEachVertex(callback);\n }\n }\n\n _getFlag(flag) {\n return !!(this._flags & flag);\n }\n\n _finalize() {\n const scene = this.model.scene;\n if (this._isObject) {\n if (this.visible) {\n scene._objectVisibilityUpdated(this);\n }\n if (this.highlighted) {\n scene._objectHighlightedUpdated(this);\n }\n if (this.xrayed) {\n scene._objectXRayedUpdated(this);\n }\n if (this.selected) {\n scene._objectSelectedUpdated(this);\n }\n }\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._finalize(this._flags);\n }\n }\n\n _finalize2() {\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._finalize2();\n }\n }\n\n _destroy() {\n const scene = this.model.scene;\n if (this._isObject) {\n scene._deregisterObject(this);\n if (this.visible) {\n scene._deRegisterVisibleObject(this);\n }\n if (this.xrayed) {\n scene._deRegisterXRayedObject(this);\n }\n if (this.selected) {\n scene._deRegisterSelectedObject(this);\n }\n if (this.highlighted) {\n scene._deRegisterHighlightedObject(this);\n }\n if (this._colorizeUpdated) {\n this.scene._deRegisterColorizedObject(this);\n }\n if (this._opacityUpdated) {\n this.scene._deRegisterOpacityObject(this);\n }\n if (this._offset && (this._offset[0] !== 0 || this._offset[1] !== 0 || this._offset[2] !== 0)) {\n this.scene._deRegisterOffsetObject(this);\n }\n }\n for (let i = 0, len = this.meshes.length; i < len; i++) {\n this.meshes[i]._destroy();\n }\n scene._aabbDirty = true;\n }\n}\n\nconst tempVec4a$9 = math.vec4();\nconst tempVec4b$6 = math.vec4();\n\n\n/**\n * @desc Tracks the World, View and Canvas coordinates, and visibility, of a position within a {@link Scene}.\n *\n * ## Position\n *\n * A Marker holds its position in the World, View and Canvas coordinate systems in three properties:\n *\n * * {@link Marker#worldPos} holds the Marker's 3D World-space coordinates. This property can be dynamically updated. The Marker will fire a \"worldPos\" event whenever this property changes.\n * * {@link Marker#viewPos} holds the Marker's 3D View-space coordinates. This property is read-only, and is automatically updated from {@link Marker#worldPos} and the current {@link Camera} position. The Marker will fire a \"viewPos\" event whenever this property changes.\n * * {@link Marker#canvasPos} holds the Marker's 2D Canvas-space coordinates. This property is read-only, and is automatically updated from {@link Marker#canvasPos} and the current {@link Camera} position and projection. The Marker will fire a \"canvasPos\" event whenever this property changes.\n *\n * ## Visibility\n *\n * {@link Marker#visible} indicates if the Marker is currently visible. The Marker will fire a \"visible\" event whenever {@link Marker#visible} changes.\n *\n * This property will be ````false```` when:\n *\n * * {@link Marker#entity} is set to an {@link Entity}, and {@link Entity#visible} is ````false````,\n * * {@link Marker#occludable} is ````true```` and the Marker is occluded by some {@link Entity} in the 3D view, or\n * * {@link Marker#canvasPos} is outside the boundary of the {@link Canvas}.\n *\n * ## Usage\n *\n * In the example below, we'll create a Marker that's associated with a {@link Mesh} (which a type of {@link Entity}).\n *\n * We'll configure our Marker to\n * become invisible whenever it's occluded by any Entities in the canvas.\n *\n * We'll also demonstrate how to query the Marker's visibility status and position (in the World, View and\n * Canvas coordinate systems), and how to subscribe to change events on those properties.\n *\n * ````javascript\n * import {Viewer, GLTFLoaderPlugin, Marker} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * // Create the torus Mesh\n * // Recall that a Mesh is an Entity\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({\n * center: [0,0,0],\n * radius: 1.0,\n * tube: 0.5,\n * radialSegments: 32,\n * tubeSegments: 24,\n * arc: Math.PI * 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * backfaces: true\n * })\n * });\n *\n * // Create the Marker, associated with our Mesh Entity\n * const myMarker = new Marker(viewer, {\n * entity: entity,\n * worldPos: [10,0,0],\n * occludable: true\n * });\n *\n * // Get the Marker's current World, View and Canvas coordinates\n * const worldPos = myMarker.worldPos; // 3D World-space position\n * const viewPos = myMarker.viewPos; // 3D View-space position\n * const canvasPos = myMarker.canvasPos; // 2D Canvas-space position\n *\n * const visible = myMarker.visible;\n *\n * // Listen for change of the Marker's 3D World-space position\n * myMarker.on(\"worldPos\", function(worldPos) {\n * //...\n * });\n *\n * // Listen for change of the Marker's 3D View-space position, which happens\n * // when either worldPos was updated or the Camera was moved\n * myMarker.on(\"viewPos\", function(viewPos) {\n * //...\n * });\n *\n * // Listen for change of the Marker's 2D Canvas-space position, which happens\n * // when worldPos or viewPos was updated, or Camera's projection was updated\n * myMarker.on(\"canvasPos\", function(canvasPos) {\n * //...\n * });\n *\n * // Listen for change of Marker visibility. The Marker becomes invisible when it falls outside the canvas,\n * // has an Entity that is also invisible, or when an Entity occludes the Marker's position in the 3D view.\n * myMarker.on(\"visible\", function(visible) { // Marker visibility has changed\n * if (visible) {\n * this.log(\"Marker is visible\");\n * } else {\n * this.log(\"Marker is invisible\");\n * }\n * });\n *\n * // Listen for destruction of Marker\n * myMarker.on(\"destroyed\", () => {\n * //...\n * });\n * ````\n */\nclass Marker extends Component {\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this Marker as well.\n * @param {*} [cfg] Marker configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Entity} [cfg.entity] Entity to associate this Marker with. When the Marker has an Entity, then {@link Marker#visible} will always be ````false```` if {@link Entity#visible} is false.\n * @param {Boolean} [cfg.occludable=false] Indicates whether or not this Marker is hidden (ie. {@link Marker#visible} is ````false```` whenever occluded by {@link Entity}s in the {@link Scene}.\n * @param {Number[]} [cfg.worldPos=[0,0,0]] World-space 3D Marker position.\n */\n constructor(owner, cfg) {\n\n super(owner, cfg);\n\n this._entity = null;\n this._visible = null;\n this._worldPos = math.vec3();\n this._origin = math.vec3();\n this._rtcPos = math.vec3();\n this._viewPos = math.vec3();\n this._canvasPos = math.vec2();\n this._occludable = false;\n\n this._onCameraViewMatrix = this.scene.camera.on(\"matrix\", () => {\n this._viewPosDirty = true;\n this._needUpdate();\n });\n\n this._onCameraProjMatrix = this.scene.camera.on(\"projMatrix\", () => {\n this._canvasPosDirty = true;\n this._needUpdate();\n });\n\n this._onEntityDestroyed = null;\n this._onEntityModelDestroyed = null;\n\n this._renderer.addMarker(this);\n\n this.entity = cfg.entity;\n this.worldPos = cfg.worldPos;\n this.occludable = cfg.occludable;\n }\n\n _update() { // this._needUpdate() schedules this for next tick\n if (this._viewPosDirty) {\n math.transformPoint3(this.scene.camera.viewMatrix, this._worldPos, this._viewPos);\n this._viewPosDirty = false;\n this._canvasPosDirty = true;\n this.fire(\"viewPos\", this._viewPos);\n }\n if (this._canvasPosDirty) {\n tempVec4a$9.set(this._viewPos);\n tempVec4a$9[3] = 1.0;\n math.transformPoint4(this.scene.camera.projMatrix, tempVec4a$9, tempVec4b$6);\n const aabb = this.scene.canvas.boundary;\n this._canvasPos[0] = Math.floor((1 + tempVec4b$6[0] / tempVec4b$6[3]) * aabb[2] / 2);\n this._canvasPos[1] = Math.floor((1 - tempVec4b$6[1] / tempVec4b$6[3]) * aabb[3] / 2);\n this._canvasPosDirty = false;\n this.fire(\"canvasPos\", this._canvasPos);\n }\n }\n\n _setVisible(visible) { // Called by VisibilityTester and this._entity.on(\"destroyed\"..)\n if (this._visible === visible) ;\n this._visible = visible;\n this.fire(\"visible\", this._visible);\n }\n\n /**\n * Sets the {@link Entity} this Marker is associated with.\n *\n * An Entity is optional. When the Marker has an Entity, then {@link Marker#visible} will always be ````false````\n * if {@link Entity#visible} is false.\n *\n * @type {Entity}\n */\n set entity(entity) {\n if (this._entity) {\n if (this._entity === entity) {\n return;\n }\n if (this._onEntityDestroyed !== null) {\n this._entity.model.off(this._onEntityDestroyed);\n this._onEntityDestroyed = null;\n }\n if (this._onEntityModelDestroyed !== null) {\n this._entity.model.off(this._onEntityModelDestroyed);\n this._onEntityModelDestroyed = null;\n }\n }\n this._entity = entity;\n if (this._entity) {\n if (this._entity instanceof SceneModelEntity) {\n this._onEntityModelDestroyed = this._entity.model.on(\"destroyed\", () => { // SceneModelEntity does not fire events, and cannot exist beyond its VBOSceneModel\n this._entity = null; // Marker now may become visible, if it was synched to invisible Entity\n this._onEntityModelDestroyed = null;\n });\n } else {\n this._onEntityDestroyed = this._entity.model.on(\"destroyed\", () => {\n this._entity = null;\n this._onEntityDestroyed = null;\n });\n }\n }\n this.fire(\"entity\", this._entity, true /* forget */);\n }\n\n /**\n * Gets the {@link Entity} this Marker is associated with.\n *\n * @type {Entity}\n */\n get entity() {\n return this._entity;\n }\n\n /**\n * Sets whether occlusion testing is performed for this Marker.\n *\n * When this is ````true````, then {@link Marker#visible} will be ````false```` whenever the Marker is occluded by an {@link Entity} in the 3D view.\n *\n * The {@link Scene} periodically occlusion-tests all Markers on every 20th \"tick\" (which represents a rendered frame). We\n * can adjust that frequency via property {@link Scene#ticksPerOcclusionTest}.\n *\n * @type {Boolean}\n */\n set occludable(occludable) {\n occludable = !!occludable;\n if (occludable === this._occludable) {\n return;\n }\n this._occludable = occludable;\n if (this._occludable) {\n this._renderer.markerWorldPosUpdated(this);\n }\n }\n\n /**\n * Gets whether occlusion testing is performed for this Marker.\n *\n * When this is ````true````, then {@link Marker#visible} will be ````false```` whenever the Marker is occluded by an {@link Entity} in the 3D view.\n *\n * @type {Boolean}\n */\n get occludable() {\n return this._occludable;\n }\n\n /**\n * Sets the World-space 3D position of this Marker.\n *\n * Fires a \"worldPos\" event with new World position.\n *\n * @type {Number[]}\n */\n set worldPos(worldPos) {\n this._worldPos.set(worldPos || [0, 0, 0]);\n worldToRTCPos(this._worldPos, this._origin, this._rtcPos);\n if (this._occludable) {\n this._renderer.markerWorldPosUpdated(this);\n }\n this._viewPosDirty = true;\n this.fire(\"worldPos\", this._worldPos);\n this._needUpdate();\n }\n\n /**\n * Gets the World-space 3D position of this Marker.\n *\n * @type {Number[]}\n */\n get worldPos() {\n return this._worldPos;\n }\n\n /**\n * Gets the RTC center of this Marker.\n *\n * This is automatically calculated from {@link Marker#worldPos}.\n *\n * @type {Number[]}\n */\n get origin() {\n return this._origin;\n }\n\n /**\n * Gets the RTC position of this Marker.\n *\n * This is automatically calculated from {@link Marker#worldPos}.\n *\n * @type {Number[]}\n */\n get rtcPos() {\n return this._rtcPos;\n }\n\n /**\n * View-space 3D coordinates of this Marker.\n *\n * This property is read-only and is automatically calculated from {@link Marker#worldPos} and the current {@link Camera} position.\n *\n * The Marker fires a \"viewPos\" event whenever this property changes.\n *\n * @type {Number[]}\n * @final\n */\n get viewPos() {\n this._update();\n return this._viewPos;\n }\n\n /**\n * Canvas-space 2D coordinates of this Marker.\n *\n * This property is read-only and is automatically calculated from {@link Marker#worldPos} and the current {@link Camera} position and projection.\n *\n * The Marker fires a \"canvasPos\" event whenever this property changes.\n *\n * @type {Number[]}\n * @final\n */\n get canvasPos() {\n this._update();\n return this._canvasPos;\n }\n\n /**\n * Indicates if this Marker is currently visible.\n *\n * This is read-only and is automatically calculated.\n *\n * The Marker is **invisible** whenever:\n *\n * * {@link Marker#canvasPos} is currently outside the canvas,\n * * {@link Marker#entity} is set to an {@link Entity} that has {@link Entity#visible} ````false````, or\n * * or {@link Marker#occludable} is ````true```` and the Marker is currently occluded by an Entity in the 3D view.\n *\n * The Marker fires a \"visible\" event whenever this property changes.\n *\n * @type {Boolean}\n * @final\n */\n get visible() {\n return !!this._visible;\n }\n\n /**\n * Destroys this Marker.\n */\n destroy() {\n this.fire(\"destroyed\", true);\n this.scene.camera.off(this._onCameraViewMatrix);\n this.scene.camera.off(this._onCameraProjMatrix);\n if (this._entity) {\n if (this._onEntityDestroyed !== null) {\n this._entity.off(this._onEntityDestroyed);\n }\n if (this._onEntityModelDestroyed !== null) {\n this._entity.model.off(this._onEntityModelDestroyed);\n }\n }\n this._renderer.removeMarker(this);\n super.destroy();\n }\n}\n\nconst os = {\n isIphoneSafari() {\n const userAgent = window.navigator.userAgent;\n const isIphone = /iPhone/i.test(userAgent);\n const isSafari = /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent);\n\n return isIphone && isSafari;\n }\n};\n\n/** @private */\nclass Wire {\n\n constructor(parentElement, cfg = {}) {\n\n this._color = cfg.color || \"black\";\n this._highlightClass = \"viewer-ruler-wire-highlighted\";\n\n this._wire = document.createElement('div');\n this._wire.className += this._wire.className ? ' viewer-ruler-wire' : 'viewer-ruler-wire';\n\n this._wireClickable = document.createElement('div');\n this._wireClickable.className += this._wireClickable.className ? ' viewer-ruler-wire-clickable' : 'viewer-ruler-wire-clickable';\n\n this._thickness = cfg.thickness || 1.0;\n this._thicknessClickable = cfg.thicknessClickable || 6.0;\n\n this._visible = true;\n this._culled = false;\n\n var wire = this._wire;\n var wireStyle = wire.style;\n\n wireStyle.border = \"solid \" + this._thickness + \"px \" + this._color;\n wireStyle.position = \"absolute\";\n wireStyle[\"z-index\"] = cfg.zIndex === undefined ? \"2000001\" : cfg.zIndex;\n wireStyle.width = 0 + \"px\";\n wireStyle.height = 0 + \"px\";\n wireStyle.visibility = \"visible\";\n wireStyle.top = 0 + \"px\";\n wireStyle.left = 0 + \"px\";\n wireStyle['-webkit-transform-origin'] = \"0 0\";\n wireStyle['-moz-transform-origin'] = \"0 0\";\n wireStyle['-ms-transform-origin'] = \"0 0\";\n wireStyle['-o-transform-origin'] = \"0 0\";\n wireStyle['transform-origin'] = \"0 0\";\n wireStyle['-webkit-transform'] = 'rotate(0deg)';\n wireStyle['-moz-transform'] = 'rotate(0deg)';\n wireStyle['-ms-transform'] = 'rotate(0deg)';\n wireStyle['-o-transform'] = 'rotate(0deg)';\n wireStyle['transform'] = 'rotate(0deg)';\n wireStyle[\"opacity\"] = 1.0;\n wireStyle[\"pointer-events\"] = \"none\";\n if (cfg.onContextMenu) ;\n\n parentElement.appendChild(wire);\n\n var wireClickable = this._wireClickable;\n var wireClickableStyle = wireClickable.style;\n\n wireClickableStyle.border = \"solid \" + this._thicknessClickable + \"px \" + this._color;\n wireClickableStyle.position = \"absolute\";\n wireClickableStyle[\"z-index\"] = cfg.zIndex === undefined ? \"2000002\" : (cfg.zIndex + 1);\n wireClickableStyle.width = 0 + \"px\";\n wireClickableStyle.height = 0 + \"px\";\n wireClickableStyle.visibility = \"visible\";\n wireClickableStyle.top = 0 + \"px\";\n wireClickableStyle.left = 0 + \"px\";\n // wireClickableStyle[\"pointer-events\"] = \"none\";\n wireClickableStyle['-webkit-transform-origin'] = \"0 0\";\n wireClickableStyle['-moz-transform-origin'] = \"0 0\";\n wireClickableStyle['-ms-transform-origin'] = \"0 0\";\n wireClickableStyle['-o-transform-origin'] = \"0 0\";\n wireClickableStyle['transform-origin'] = \"0 0\";\n wireClickableStyle['-webkit-transform'] = 'rotate(0deg)';\n wireClickableStyle['-moz-transform'] = 'rotate(0deg)';\n wireClickableStyle['-ms-transform'] = 'rotate(0deg)';\n wireClickableStyle['-o-transform'] = 'rotate(0deg)';\n wireClickableStyle['transform'] = 'rotate(0deg)';\n wireClickableStyle[\"opacity\"] = 0.0;\n wireClickableStyle[\"pointer-events\"] = \"none\";\n if (cfg.onContextMenu) ;\n\n parentElement.appendChild(wireClickable);\n\n if (cfg.onMouseOver) {\n wireClickable.addEventListener('mouseover', (event) => {\n cfg.onMouseOver(event, this);\n });\n }\n\n if (cfg.onMouseLeave) {\n wireClickable.addEventListener('mouseleave', (event) => {\n cfg.onMouseLeave(event, this);\n });\n }\n\n if (cfg.onMouseWheel) {\n wireClickable.addEventListener('wheel', (event) => {\n cfg.onMouseWheel(event, this);\n });\n }\n\n if (cfg.onMouseDown) {\n wireClickable.addEventListener('mousedown', (event) => {\n cfg.onMouseDown(event, this);\n });\n }\n\n if (cfg.onMouseUp) {\n wireClickable.addEventListener('mouseup', (event) => {\n cfg.onMouseUp(event, this);\n });\n }\n\n if (cfg.onMouseMove) {\n wireClickable.addEventListener('mousemove', (event) => {\n cfg.onMouseMove(event, this);\n });\n }\n\n if (cfg.onContextMenu) {\n if(os.isIphoneSafari()){\n wireClickable.addEventListener('touchstart', (event) => {\n event.preventDefault();\n if(this._timeout){\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n this._timeout = setTimeout(() => {\n event.clientX = event.touches[0].clientX;\n event.clientY = event.touches[0].clientY;\n cfg.onContextMenu(event, this);\n clearTimeout(this._timeout);\n this._timeout = null;\n }, 500);\n });\n\n wireClickable.addEventListener('touchend', (event) => {\n event.preventDefault();\n //stops short touches from calling the timeout\n if(this._timeout) {\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n } );\n\n }\n else {\n wireClickable.addEventListener('contextmenu', (event) => {\n console.log(event);\n cfg.onContextMenu(event, this);\n event.preventDefault();\n event.stopPropagation();\n console.log(\"Label context menu\");\n });\n }\n \n }\n\n this._x1 = 0;\n this._y1 = 0;\n this._x2 = 0;\n this._y2 = 0;\n\n this._update();\n }\n\n get visible() {\n return this._wire.style.visibility === \"visible\";\n }\n\n _update() {\n\n var length = Math.abs(Math.sqrt((this._x1 - this._x2) * (this._x1 - this._x2) + (this._y1 - this._y2) * (this._y1 - this._y2)));\n var angle = Math.atan2(this._y2 - this._y1, this._x2 - this._x1) * 180.0 / Math.PI;\n\n var wireStyle = this._wire.style;\n wireStyle[\"width\"] = Math.round(length) + 'px';\n wireStyle[\"left\"] = Math.round(this._x1) + 'px';\n wireStyle[\"top\"] = Math.round(this._y1) + 'px';\n wireStyle['-webkit-transform'] = 'rotate(' + angle + 'deg)';\n wireStyle['-moz-transform'] = 'rotate(' + angle + 'deg)';\n wireStyle['-ms-transform'] = 'rotate(' + angle + 'deg)';\n wireStyle['-o-transform'] = 'rotate(' + angle + 'deg)';\n wireStyle['transform'] = 'rotate(' + angle + 'deg)';\n\n var wireClickableStyle = this._wireClickable.style;\n wireClickableStyle[\"width\"] = Math.round(length) + 'px';\n wireClickableStyle[\"left\"] = Math.round(this._x1) + 'px';\n wireClickableStyle[\"top\"] = Math.round(this._y1) + 'px';\n wireClickableStyle['-webkit-transform'] = 'rotate(' + angle + 'deg)';\n wireClickableStyle['-moz-transform'] = 'rotate(' + angle + 'deg)';\n wireClickableStyle['-ms-transform'] = 'rotate(' + angle + 'deg)';\n wireClickableStyle['-o-transform'] = 'rotate(' + angle + 'deg)';\n wireClickableStyle['transform'] = 'rotate(' + angle + 'deg)';\n }\n\n setStartAndEnd(x1, y1, x2, y2) {\n this._x1 = x1;\n this._y1 = y1;\n this._x2 = x2;\n this._y2 = y2;\n this._update();\n }\n\n setColor(color) {\n this._color = color || \"black\";\n this._wire.style.border = \"solid \" + this._thickness + \"px \" + this._color;\n }\n\n setOpacity(opacity) {\n this._wire.style.opacity = opacity;\n }\n\n setVisible(visible) {\n if (this._visible === visible) {\n return;\n }\n this._visible = !!visible;\n this._wire.style.visibility = this._visible && !this._culled ? \"visible\" : \"hidden\";\n }\n\n setCulled(culled) {\n if (this._culled === culled) {\n return;\n }\n this._culled = !!culled;\n this._wire.style.visibility = this._visible && !this._culled ? \"visible\" : \"hidden\";\n }\n\n setClickable(clickable) {\n this._wireClickable.style[\"pointer-events\"] = (clickable) ? \"all\" : \"none\";\n }\n\n setHighlighted(highlighted) {\n if (this._highlighted === highlighted) {\n return;\n }\n this._highlighted = !!highlighted;\n if (this._highlighted) {\n this._wire.classList.add(this._highlightClass);\n } else {\n this._wire.classList.remove(this._highlightClass);\n }\n }\n\n destroy(visible) {\n if (this._wire.parentElement) {\n this._wire.parentElement.removeChild(this._wire);\n }\n if (this._wireClickable.parentElement) {\n this._wireClickable.parentElement.removeChild(this._wireClickable);\n }\n }\n}\n\n/** @private */\nclass Dot {\n\n constructor(parentElement, cfg = {}) {\n\n this._highlightClass = \"viewer-ruler-dot-highlighted\";\n\n this._x = 0;\n this._y = 0;\n\n this._dot = document.createElement('div');\n this._dot.className += this._dot.className ? ' viewer-ruler-dot' : 'viewer-ruler-dot';\n\n this._dotClickable = document.createElement('div');\n this._dotClickable.className += this._dotClickable.className ? ' viewer-ruler-dot-clickable' : 'viewer-ruler-dot-clickable';\n\n this._visible = !!cfg.visible;\n this._culled = false;\n\n var dot = this._dot;\n var dotStyle = dot.style;\n dotStyle[\"border-radius\"] = 25 + \"px\";\n dotStyle.border = \"solid 2px white\";\n dotStyle.background = \"lightgreen\";\n dotStyle.position = \"absolute\";\n dotStyle[\"z-index\"] = cfg.zIndex === undefined ? \"40000005\" : cfg.zIndex ;\n dotStyle.width = 8 + \"px\";\n dotStyle.height = 8 + \"px\";\n dotStyle.visibility = cfg.visible !== false ? \"visible\" : \"hidden\";\n dotStyle.top = 0 + \"px\";\n dotStyle.left = 0 + \"px\";\n dotStyle[\"box-shadow\"] = \"0 2px 5px 0 #182A3D;\";\n dotStyle[\"opacity\"] = 1.0;\n dotStyle[\"pointer-events\"] = \"none\";\n if (cfg.onContextMenu) ;\n parentElement.appendChild(dot);\n\n var dotClickable = this._dotClickable;\n var dotClickableStyle = dotClickable.style;\n dotClickableStyle[\"border-radius\"] = 35 + \"px\";\n dotClickableStyle.border = \"solid 10px white\";\n dotClickableStyle.position = \"absolute\";\n dotClickableStyle[\"z-index\"] = cfg.zIndex === undefined ? \"40000007\" : (cfg.zIndex + 1);\n dotClickableStyle.width = 8 + \"px\";\n dotClickableStyle.height = 8 + \"px\";\n dotClickableStyle.visibility = \"visible\";\n dotClickableStyle.top = 0 + \"px\";\n dotClickableStyle.left = 0 + \"px\";\n dotClickableStyle[\"opacity\"] = 0.0;\n dotClickableStyle[\"pointer-events\"] = \"none\";\n if (cfg.onContextMenu) ;\n parentElement.appendChild(dotClickable);\n\n dotClickable.addEventListener('click', (event) => {\n parentElement.dispatchEvent(new MouseEvent('mouseover', event));\n });\n\n if (cfg.onMouseOver) {\n dotClickable.addEventListener('mouseover', (event) => {\n cfg.onMouseOver(event, this);\n parentElement.dispatchEvent(new MouseEvent('mouseover', event));\n });\n }\n\n if (cfg.onMouseLeave) {\n dotClickable.addEventListener('mouseleave', (event) => {\n cfg.onMouseLeave(event, this);\n });\n }\n\n if (cfg.onMouseWheel) {\n dotClickable.addEventListener('wheel', (event) => {\n cfg.onMouseWheel(event, this);\n });\n }\n\n if (cfg.onMouseDown) {\n dotClickable.addEventListener('mousedown', (event) => {\n cfg.onMouseDown(event, this);\n });\n }\n\n if (cfg.onMouseUp) {\n dotClickable.addEventListener('mouseup', (event) => {\n cfg.onMouseUp(event, this);\n });\n }\n\n if (cfg.onMouseMove) {\n dotClickable.addEventListener('mousemove', (event) => {\n cfg.onMouseMove(event, this);\n });\n }\n\n if (cfg.onContextMenu) {\n if(os.isIphoneSafari()){\n dotClickable.addEventListener('touchstart', (event) => {\n event.preventDefault();\n if(this._timeout){\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n this._timeout = setTimeout(() => {\n event.clientX = event.touches[0].clientX;\n event.clientY = event.touches[0].clientY;\n cfg.onContextMenu(event, this);\n clearTimeout(this._timeout);\n this._timeout = null;\n }, 500);\n });\n\n dotClickable.addEventListener('touchend', (event) => {\n event.preventDefault();\n //stops short touches from calling the timeout\n if(this._timeout) {\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n } );\n\n }\n else {\n dotClickable.addEventListener('contextmenu', (event) => {\n console.log(event);\n cfg.onContextMenu(event, this);\n event.preventDefault();\n event.stopPropagation();\n console.log(\"Label context menu\");\n });\n }\n \n }\n \n this.setPos(cfg.x || 0, cfg.y || 0);\n this.setFillColor(cfg.fillColor);\n this.setBorderColor(cfg.borderColor);\n }\n\n setPos(x, y) {\n this._x = x;\n this._y = y;\n var dotStyle = this._dot.style;\n dotStyle[\"left\"] = (Math.round(x) - 4) + 'px';\n dotStyle[\"top\"] = (Math.round(y) - 4) + 'px';\n\n var dotClickableStyle = this._dotClickable.style;\n dotClickableStyle[\"left\"] = (Math.round(x) - 9) + 'px';\n dotClickableStyle[\"top\"] = (Math.round(y) - 9) + 'px';\n }\n\n setFillColor(color) {\n this._dot.style.background = color || \"lightgreen\";\n }\n\n setBorderColor(color) {\n this._dot.style.border = \"solid 2px\" + (color || \"black\");\n }\n\n setOpacity(opacity) {\n this._dot.style.opacity = opacity;\n }\n\n setVisible(visible) {\n if (this._visible === visible) {\n return;\n }\n this._visible = !!visible;\n this._dot.style.visibility = this._visible && !this._culled ? \"visible\" : \"hidden\";\n }\n\n setCulled(culled) {\n if (this._culled === culled) {\n return;\n }\n this._culled = !!culled;\n this._dot.style.visibility = this._visible && !this._culled ? \"visible\" : \"hidden\";\n }\n\n setClickable(clickable) {\n this._dotClickable.style[\"pointer-events\"] = (clickable) ? \"all\" : \"none\";\n }\n\n setHighlighted(highlighted) {\n if (this._highlighted === highlighted) {\n return;\n }\n this._highlighted = !!highlighted;\n if (this._highlighted) {\n this._dot.classList.add(this._highlightClass);\n } else {\n this._dot.classList.remove(this._highlightClass);\n }\n }\n \n destroy() {\n this.setVisible(false);\n if (this._dot.parentElement) {\n this._dot.parentElement.removeChild(this._dot);\n }\n if (this._dotClickable.parentElement) {\n this._dotClickable.parentElement.removeChild(this._dotClickable);\n }\n }\n}\n\n/** @private */\nclass Label {\n\n constructor(parentElement, cfg = {}) {\n\n this._highlightClass = \"viewer-ruler-label-highlighted\";\n\n this._prefix = cfg.prefix || \"\";\n this._x = 0;\n this._y = 0;\n this._visible = true;\n this._culled = false;\n\n this._label = document.createElement('div');\n this._label.className += this._label.className ? ' viewer-ruler-label' : 'viewer-ruler-label';\n this._timeout = null;\n\n var label = this._label;\n var style = label.style;\n\n style[\"border-radius\"] = 5 + \"px\";\n style.color = \"white\";\n style.padding = \"4px\";\n style.border = \"solid 1px\";\n style.background = \"lightgreen\";\n style.position = \"absolute\";\n style[\"z-index\"] = cfg.zIndex === undefined ? \"5000005\" : cfg.zIndex;\n style.width = \"auto\";\n style.height = \"auto\";\n style.visibility = \"visible\";\n style.top = 0 + \"px\";\n style.left = 0 + \"px\";\n style[\"pointer-events\"] = \"all\";\n style[\"opacity\"] = 1.0;\n if (cfg.onContextMenu) ;\n label.innerText = \"\";\n\n parentElement.appendChild(label);\n\n this.setPos(cfg.x || 0, cfg.y || 0);\n this.setFillColor(cfg.fillColor);\n this.setBorderColor(cfg.fillColor);\n this.setText(cfg.text);\n\n if (cfg.onMouseOver) {\n label.addEventListener('mouseover', (event) => {\n cfg.onMouseOver(event, this);\n event.preventDefault();\n });\n }\n\n if (cfg.onMouseLeave) {\n label.addEventListener('mouseleave', (event) => {\n cfg.onMouseLeave(event, this);\n event.preventDefault();\n });\n }\n\n if (cfg.onMouseWheel) {\n label.addEventListener('wheel', (event) => {\n cfg.onMouseWheel(event, this);\n });\n }\n\n if (cfg.onMouseDown) {\n label.addEventListener('mousedown', (event) => {\n cfg.onMouseDown(event, this);\n event.stopPropagation();\n });\n }\n\n if (cfg.onMouseUp) {\n label.addEventListener('mouseup', (event) => {\n cfg.onMouseUp(event, this);\n event.stopPropagation();\n });\n }\n\n if (cfg.onMouseMove) {\n label.addEventListener('mousemove', (event) => {\n cfg.onMouseMove(event, this);\n });\n }\n\n if (cfg.onContextMenu) {\n if(os.isIphoneSafari()){\n label.addEventListener('touchstart', (event) => {\n event.preventDefault();\n if(this._timeout){\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n this._timeout = setTimeout(() => {\n event.clientX = event.touches[0].clientX;\n event.clientY = event.touches[0].clientY;\n cfg.onContextMenu(event, this);\n clearTimeout(this._timeout);\n this._timeout = null;\n }, 500);\n });\n\n label.addEventListener('touchend', (event) => {\n event.preventDefault();\n //stops short touches from calling the timeout\n if(this._timeout) {\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n } );\n\n }\n else {\n label.addEventListener('contextmenu', (event) => {\n console.log(event);\n cfg.onContextMenu(event, this);\n event.preventDefault();\n event.stopPropagation();\n console.log(\"Label context menu\");\n });\n }\n \n }\n }\n\n setPos(x, y) {\n this._x = x;\n this._y = y;\n var style = this._label.style;\n style[\"left\"] = (Math.round(x) - 20) + 'px';\n style[\"top\"] = (Math.round(y) - 12) + 'px';\n }\n\n setPosOnWire(x1, y1, x2, y2) {\n var x = x1 + ((x2 - x1) * 0.5);\n var y = y1 + ((y2 - y1) * 0.5);\n var style = this._label.style;\n style[\"left\"] = (Math.round(x) - 20) + 'px';\n style[\"top\"] = (Math.round(y) - 12) + 'px';\n }\n\n setPosBetweenWires(x1, y1, x2, y2, x3, y3) {\n var x = (x1 + x2 + x3) / 3;\n var y = (y1 + y2 + y3) / 3;\n var style = this._label.style;\n style[\"left\"] = (Math.round(x) - 20) + 'px';\n style[\"top\"] = (Math.round(y) - 12) + 'px';\n }\n\n setText(text) {\n this._label.innerHTML = this._prefix + (text || \"\");\n }\n\n setFillColor(color) {\n this._fillColor = color || \"lightgreen\";\n this._label.style.background =this._fillColor;\n }\n\n setBorderColor(color) {\n this._borderColor = color || \"black\";\n this._label.style.border = \"solid 1px \" + this._borderColor;\n }\n\n setOpacity(opacity) {\n this._label.style.opacity = opacity;\n }\n\n setVisible(visible) {\n if (this._visible === visible) {\n return;\n }\n this._visible = !!visible;\n this._label.style.visibility = this._visible && !this._culled ? \"visible\" : \"hidden\";\n }\n\n setCulled(culled) {\n if (this._culled === culled) {\n return;\n }\n this._culled = !!culled;\n this._label.style.visibility = this._visible && !this._culled ? \"visible\" : \"hidden\";\n }\n\n setHighlighted(highlighted) {\n if (this._highlighted === highlighted) {\n return;\n }\n this._highlighted = !!highlighted;\n if (this._highlighted) {\n this._label.classList.add(this._highlightClass);\n } else {\n this._label.classList.remove(this._highlightClass);\n }\n }\n\n setClickable(clickable) {\n this._label.style[\"pointer-events\"] = (clickable) ? \"all\" : \"none\";\n }\n\n setPrefix(prefix) {\n if(this._prefix === prefix){\n return;\n }\n this._prefix = prefix;\n }\n\n destroy() {\n if (this._label.parentElement) {\n this._label.parentElement.removeChild(this._label);\n }\n }\n\n \n}\n\nvar originVec = math.vec3();\nvar targetVec = math.vec3();\n\n/**\n * @desc Measures the angle indicated by three 3D points.\n *\n * See {@link AngleMeasurementsPlugin} for more info.\n */\nclass AngleMeasurement extends Component {\n\n /**\n * @private\n */\n constructor(plugin, cfg = {}) {\n\n super(plugin.viewer.scene, cfg);\n\n /**\n * The {@link AngleMeasurementsPlugin} that owns this AngleMeasurement.\n * @type {AngleMeasurementsPlugin}\n */\n this.plugin = plugin;\n\n this._container = cfg.container;\n if (!this._container) {\n throw \"config missing: container\";\n }\n\n this._color = cfg.color || plugin.defaultColor;\n\n var scene = this.plugin.viewer.scene;\n\n this._originMarker = new Marker(scene, cfg.origin);\n this._cornerMarker = new Marker(scene, cfg.corner);\n this._targetMarker = new Marker(scene, cfg.target);\n\n this._originWorld = math.vec3();\n this._cornerWorld = math.vec3();\n this._targetWorld = math.vec3();\n\n this._wp = new Float64Array(12);\n this._vp = new Float64Array(12);\n this._pp = new Float64Array(12);\n this._cp = new Int16Array(6);\n\n const onMouseOver = cfg.onMouseOver ? (event) => {\n cfg.onMouseOver(event, this);\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseover', event));\n } : null;\n\n const onMouseLeave = cfg.onMouseLeave ? (event) => {\n cfg.onMouseLeave(event, this);\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseleave', event));\n } : null;\n\n const onContextMenu = cfg.onContextMenu ? (event) => {\n cfg.onContextMenu(event, this);\n } : null;\n\n const onMouseWheel = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new WheelEvent('wheel', event));\n };\n\n const onMouseDown = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mousedown', event));\n } ;\n\n const onMouseUp = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseup', event));\n };\n\n const onMouseMove = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mousemove', event));\n };\n\n this._originDot = new Dot(this._container, {\n fillColor: this._color,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 2 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n this._cornerDot = new Dot(this._container, {\n fillColor: this._color,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 2 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n this._targetDot = new Dot(this._container, {\n fillColor: this._color,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 2 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._originWire = new Wire(this._container, {\n color: this._color || \"blue\",\n thickness: 1,\n zIndex: plugin.zIndex,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n this._targetWire = new Wire(this._container, {\n color: this._color || \"red\",\n thickness: 1,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 1 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._angleLabel = new Label(this._container, {\n fillColor: this._color || \"#00BBFF\",\n prefix: \"\",\n text: \"\",\n zIndex: plugin.zIndex + 2,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._wpDirty = false;\n this._vpDirty = false;\n this._cpDirty = false;\n\n this._visible = false;\n this._originVisible = false;\n this._cornerVisible = false;\n this._targetVisible = false;\n\n this._originWireVisible = false;\n this._targetWireVisible = false;\n\n this._angleVisible = false;\n this._labelsVisible = false;\n this._clickable = false;\n\n this._originMarker.on(\"worldPos\", (value) => {\n this._originWorld.set(value || [0, 0, 0]);\n this._wpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._cornerMarker.on(\"worldPos\", (value) => {\n this._cornerWorld.set(value || [0, 0, 0]);\n this._wpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._targetMarker.on(\"worldPos\", (value) => {\n this._targetWorld.set(value || [0, 0, 0]);\n this._wpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._onViewMatrix = scene.camera.on(\"viewMatrix\", () => {\n this._vpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._onProjMatrix = scene.camera.on(\"projMatrix\", () => {\n this._cpDirty = true;\n this._needUpdate();\n });\n\n this._onCanvasBoundary = scene.canvas.on(\"boundary\", () => {\n this._cpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._onSectionPlaneUpdated = scene.on(\"sectionPlaneUpdated\", () => {\n this._sectionPlanesDirty = true;\n this._needUpdate();\n });\n\n this.approximate = cfg.approximate;\n this.visible = cfg.visible;\n\n this.originVisible = cfg.originVisible;\n this.cornerVisible = cfg.cornerVisible;\n this.targetVisible = cfg.targetVisible;\n\n this.originWireVisible = cfg.originWireVisible;\n this.targetWireVisible = cfg.targetWireVisible;\n\n this.angleVisible = cfg.angleVisible;\n this.labelsVisible = cfg.labelsVisible;\n }\n\n _update() {\n\n if (!this._visible) {\n return;\n }\n\n const scene = this.plugin.viewer.scene;\n\n if (this._wpDirty) {\n\n this._wp[0] = this._originWorld[0];\n this._wp[1] = this._originWorld[1];\n this._wp[2] = this._originWorld[2];\n this._wp[3] = 1.0;\n\n this._wp[4] = this._cornerWorld[0];\n this._wp[5] = this._cornerWorld[1];\n this._wp[6] = this._cornerWorld[2];\n this._wp[7] = 1.0;\n\n this._wp[8] = this._targetWorld[0];\n this._wp[9] = this._targetWorld[1];\n this._wp[10] = this._targetWorld[2];\n this._wp[11] = 1.0;\n\n this._wpDirty = false;\n this._vpDirty = true;\n }\n\n if (this._vpDirty) {\n\n math.transformPositions4(scene.camera.viewMatrix, this._wp, this._vp);\n\n this._vp[3] = 1.0;\n this._vp[7] = 1.0;\n this._vp[11] = 1.0;\n\n this._vpDirty = false;\n this._cpDirty = true;\n }\n\n if (this._sectionPlanesDirty) {\n\n if (this._isSliced(this._wp)) {\n this._angleLabel.setCulled(true);\n this._originWire.setCulled(true);\n this._targetWire.setCulled(true);\n this._originDot.setCulled(true);\n this._cornerDot.setCulled(true);\n this._targetDot.setCulled(true);\n return;\n } else {\n this._angleLabel.setCulled(false);\n this._originWire.setCulled(false);\n this._targetWire.setCulled(false);\n this._originDot.setCulled(false);\n this._cornerDot.setCulled(false);\n this._targetDot.setCulled(false);\n }\n\n this._sectionPlanesDirty = true;\n }\n\n if (this._cpDirty) {\n\n const near = -0.3;\n const zOrigin = this._originMarker.viewPos[2];\n const zCorner = this._cornerMarker.viewPos[2];\n const zTarget = this._targetMarker.viewPos[2];\n\n if (zOrigin > near || zCorner > near || zTarget > near) {\n\n this._originDot.setVisible(false);\n this._cornerDot.setVisible(false);\n this._targetDot.setVisible(false);\n\n this._originWire.setVisible(false);\n this._targetWire.setVisible(false);\n\n this._angleLabel.setCulled(true);\n\n return;\n }\n\n math.transformPositions4(scene.camera.project.matrix, this._vp, this._pp);\n\n var pp = this._pp;\n var cp = this._cp;\n\n var canvas = scene.canvas.canvas;\n var offsets = canvas.getBoundingClientRect();\n const containerOffsets = this._container.getBoundingClientRect();\n var top = offsets.top - containerOffsets.top;\n var left = offsets.left - containerOffsets.left;\n var aabb = scene.canvas.boundary;\n var canvasWidth = aabb[2];\n var canvasHeight = aabb[3];\n var j = 0;\n\n for (var i = 0, len = pp.length; i < len; i += 4) {\n cp[j] = left + Math.floor((1 + pp[i + 0] / pp[i + 3]) * canvasWidth / 2);\n cp[j + 1] = top + Math.floor((1 - pp[i + 1] / pp[i + 3]) * canvasHeight / 2);\n j += 2;\n }\n\n this._originDot.setPos(cp[0], cp[1]);\n this._cornerDot.setPos(cp[2], cp[3]);\n this._targetDot.setPos(cp[4], cp[5]);\n\n this._originWire.setStartAndEnd(cp[0], cp[1], cp[2], cp[3]);\n this._targetWire.setStartAndEnd(cp[2], cp[3], cp[4], cp[5]);\n\n this._angleLabel.setPosBetweenWires(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]);\n\n math.subVec3(this._originWorld, this._cornerWorld, originVec);\n math.subVec3(this._targetWorld, this._cornerWorld, targetVec);\n\n var validVecs =\n (originVec[0] !== 0 || originVec[1] !== 0 || originVec[2] !== 0) &&\n (targetVec[0] !== 0 || targetVec[1] !== 0 || targetVec[2] !== 0);\n\n if (validVecs) {\n\n const tilde = this._approximate ? \" ~ \" : \" = \";\n\n math.normalizeVec3(originVec);\n math.normalizeVec3(targetVec);\n const angle = Math.abs(math.angleVec3(originVec, targetVec));\n this._angle = angle / math.DEGTORAD;\n this._angleLabel.setText(tilde + this._angle.toFixed(2) + \"°\");\n } else {\n this._angleLabel.setText(\"\");\n }\n\n // this._angleLabel.setText((Math.abs(math.lenVec3(math.subVec3(this._targetWorld, this._originWorld, distVec3)) * scale).toFixed(2)) + unitAbbrev);\n\n this._originDot.setVisible(this._visible && this._originVisible);\n this._cornerDot.setVisible(this._visible && this._cornerVisible);\n this._targetDot.setVisible(this._visible && this._targetVisible);\n\n this._originWire.setVisible(this._visible && this._originWireVisible);\n this._targetWire.setVisible(this._visible && this._targetWireVisible);\n\n this._angleLabel.setCulled(!(this._visible && this._angleVisible && this.labelsVisible));\n\n this._cpDirty = false;\n }\n }\n\n _isSliced(positions) {\n const sectionPlanes = this.scene._sectionPlanesState.sectionPlanes;\n for (let i = 0, len = sectionPlanes.length; i < len; i++) {\n const sectionPlane = sectionPlanes[i];\n if (math.planeClipsPositions3(sectionPlane.pos, sectionPlane.dir, positions, 4)) {\n return true\n }\n }\n return false;\n }\n\n /**\n * Sets whether this AngleMeasurement indicates that its measurement is approximate.\n *\n * This is ````true```` by default.\n *\n * @type {Boolean}\n */\n set approximate(approximate) {\n approximate = approximate !== false;\n if (this._approximate === approximate) {\n return;\n }\n this._approximate = approximate;\n this._cpDirty = true;\n this._needUpdate(0);\n }\n\n /**\n * Gets whether this AngleMeasurement indicates that its measurement is approximate.\n *\n * This is ````true```` by default.\n *\n * @type {Boolean}\n */\n get approximate() {\n return this._approximate;\n }\n\n /**\n * Gets the origin {@link Marker}.\n *\n * @type {Marker}\n */\n get origin() {\n return this._originMarker;\n }\n\n /**\n * Gets the corner {@link Marker}.\n *\n * @type {Marker}\n */\n get corner() {\n return this._cornerMarker;\n }\n\n /**\n * Gets the target {@link Marker}.\n *\n * @type {Marker}\n */\n get target() {\n return this._targetMarker;\n }\n\n /**\n * Gets the angle between two connected 3D line segments, given\n * as three positions on the surface(s) of one or more {@link Entity}s.\n *\n * @type {Number}\n */\n get angle() {\n this._update();\n return this._angle;\n }\n\n /**\n * Gets the color of the angle measurement.\n *\n * The color is an HTML string representation, eg. \"#00BBFF\" and \"blue\".\n *\n * @type {String}\n */\n get color() {\n return this._color;\n }\n\n /** Sets the color of the angle measurement.\n *\n * The color is given as an HTML string representation, eg. \"#00BBFF\" and \"blue\".\n *\n * @type {String}\n */\n set color(value) {\n this._originDot.setFillColor(value);\n this._cornerDot.setFillColor(value);\n this._targetDot.setFillColor(value);\n this._originWire.setColor(value || \"blue\");\n this._targetWire.setColor(value || \"red\");\n this._angleLabel.setFillColor(value || \"#00BBFF\");\n\n this._color = value;\n }\n\n /**\n * Sets whether this AngleMeasurement is visible or not.\n *\n * @type {Boolean}\n */\n set visible(value) {\n value = value !== false;\n this._visible = value;\n this._originDot.setVisible(this._visible && this._originVisible);\n this._cornerDot.setVisible(this._visible && this._cornerVisible);\n this._targetDot.setVisible(this._visible && this._targetVisible);\n this._originWire.setVisible(this._visible && this._originWireVisible);\n this._targetWire.setVisible(this._visible && this._targetWireVisible);\n this._angleLabel.setVisible(this._visible && this._angleVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets whether this AngleMeasurement is visible or not.\n *\n * @type {Boolean}\n */\n get visible() {\n return this._visible;\n }\n\n /**\n * Sets if the origin {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n set originVisible(value) {\n value = value !== false;\n this._originVisible = value;\n this._originDot.setVisible(this._visible && this._originVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the origin {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n get originVisible() {\n return this._originVisible;\n }\n\n /**\n * Sets if the corner {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n set cornerVisible(value) {\n value = value !== false;\n this._cornerVisible = value;\n this._cornerDot.setVisible(this._visible && this._cornerVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the corner {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n get cornerVisible() {\n return this._cornerVisible;\n }\n\n /**\n * Sets if the target {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n set targetVisible(value) {\n value = value !== false;\n this._targetVisible = value;\n this._targetDot.setVisible(this._visible && this._targetVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the target {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n get targetVisible() {\n return this._targetVisible;\n }\n\n /**\n * Sets if the wire between the origin and the corner is visible.\n *\n * @type {Boolean}\n */\n set originWireVisible(value) {\n value = value !== false;\n this._originWireVisible = value;\n this._originWire.setVisible(this._visible && this._originWireVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the wire between the origin and the corner is visible.\n *\n * @type {Boolean}\n */\n get originWireVisible() {\n return this._originWireVisible;\n }\n\n /**\n * Sets if the wire between the target and the corner is visible.\n *\n * @type {Boolean}\n */\n set targetWireVisible(value) {\n value = value !== false;\n this._targetWireVisible = value;\n this._targetWire.setVisible(this._visible && this._targetWireVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the wire between the target and the corner is visible.\n *\n * @type {Boolean}\n */\n get targetWireVisible() {\n return this._targetWireVisible;\n }\n\n /**\n * Sets if the angle label is visible.\n *\n * @type {Boolean}\n */\n set angleVisible(value) {\n value = value !== false;\n this._angleVisible = value;\n this._angleLabel.setVisible(this._visible && this._angleVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the angle label is visible.\n *\n * @type {Boolean}\n */\n get angleVisible() {\n return this._angleVisible;\n }\n\n /**\n * Sets if the labels are visible.\n *\n * @type {Boolean}\n */\n set labelsVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultLabelsVisible;\n this._labelsVisible = value;\n var labelsVisible = this._visible && this._labelsVisible;\n this._angleLabel.setVisible(labelsVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the labels are visible.\n *\n * @type {Boolean}\n */\n get labelsVisible() {\n return this._labelsVisible;\n }\n\n /**\n * Sets if this DistanceMeasurement appears highlighted.\n * @param highlighted\n */\n setHighlighted(highlighted) {\n this._originDot.setHighlighted(highlighted);\n this._cornerDot.setHighlighted(highlighted);\n this._targetDot.setHighlighted(highlighted);\n this._originWire.setHighlighted(highlighted);\n this._targetWire.setHighlighted(highlighted);\n this._angleLabel.setHighlighted(highlighted);\n }\n\n /**\n * Sets if the wires, dots ad labels will fire \"mouseOver\" \"mouseLeave\" and \"contextMenu\" events.\n *\n * @type {Boolean}\n */\n set clickable(value) {\n value = !!value;\n this._clickable = value;\n this._originDot.setClickable(this._clickable);\n this._cornerDot.setClickable(this._clickable);\n this._targetDot.setClickable(this._clickable);\n this._originWire.setClickable(this._clickable);\n this._targetWire.setClickable(this._clickable);\n this._angleLabel.setClickable(this._clickable);\n }\n\n /**\n * Gets if the wires, dots ad labels will fire \"mouseOver\" \"mouseLeave\" and \"contextMenu\" events.\n *\n * @type {Boolean}\n */\n get clickable() {\n return this._clickable;\n }\n\n /**\n * @private\n */\n destroy() {\n\n const scene = this.plugin.viewer.scene;\n\n if (this._onViewMatrix) {\n scene.camera.off(this._onViewMatrix);\n }\n if (this._onProjMatrix) {\n scene.camera.off(this._onProjMatrix);\n }\n if (this._onCanvasBoundary) {\n scene.canvas.off(this._onCanvasBoundary);\n }\n if (this._onSectionPlaneUpdated) {\n scene.off(this._onSectionPlaneUpdated);\n }\n\n this._originDot.destroy();\n this._cornerDot.destroy();\n this._targetDot.destroy();\n\n this._originWire.destroy();\n this._targetWire.destroy();\n\n this._angleLabel.destroy();\n\n super.destroy();\n }\n}\n\n/**\n * Creates {@link AngleMeasurement}s in an {@link AngleMeasurementsPlugin} from user input.\n *\n * @interface\n * @abstract\n */\nclass AngleMeasurementsControl extends Component {\n\n /**\n * Gets if this AngleMeasurementsControl is currently active, where it is responding to input.\n *\n * @returns {boolean} True if this AngleMeassurementsControl is active.\n * @abstract\n */\n get active() {\n }\n\n /**\n * Sets whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsControl.\n *\n * This is `true` by default.\n *\n * Internally, this deactivates then activates the AngleMeasurementsControl when changed, which means that\n * it will destroy any AngleMeasurements currently under construction, and incurs some overhead, since it unbinds\n * and rebinds various input handlers.\n *\n * @param {boolean} snapping Whether to enable snap-to-vertex and snap-edge for this AngleMeasurementsControl.\n */\n set snapping(snapping) {\n }\n\n /**\n * Gets whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsControl.\n *\n * This is `true` by default.\n *\n * @returns {boolean} Whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsControl.\n */\n get snapping() {\n return true;\n }\n\n /**\n * Activates this AngleMeasurementsMouseControl, ready to respond to input.\n *\n * @abstract\n */\n activate() {\n }\n\n /**\n * Deactivates this AngleMeasurementsControl, making it unresponsive to input.\n *\n * Destroys any {@link AngleMeasurement} under construction by this AngleMeasurementsControl.\n *\n * @abstract\n */\n deactivate() {\n }\n\n /**\n * Resets this AngleMeasurementsControl.\n *\n * Destroys any {@link AngleMeasurement} under construction by this AngleMeasurementsControl.\n *\n * Does nothing if the AngleMeasurementsControl is not active.\n *\n * @abstract\n */\n reset() {\n }\n\n /**\n * Gets the {@link AngleMeasurement} under construction by this AngleMeasurementsControl, if any.\n *\n * @returns {null|AngleMeasurement}\n * @abstract\n */\n get currentMeasurement() {\n return null;\n }\n\n /**\n * Destroys this AngleMeasurementsMouseControl.\n *\n * Destroys any {@link AngleMeasurement} under construction by this AngleMeasurementsControl.\n *\n * @abstract\n */\n destroy() {\n }\n}\n\nconst MOUSE_FINDING_ORIGIN = 0;\nconst MOUSE_FINDING_CORNER = 1;\nconst MOUSE_FINDING_TARGET = 2;\n\n/**\n * Creates {@link AngleMeasurement}s in an {@link AngleMeasurementsPlugin} from mouse input.\n *\n * ## Usage\n *\n * [[Run example](/examples/measurement/#angle_createWithMouse_snapping)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin, AngleMeasurementsMouseControl, PointerLens} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * });\n *\n * viewer.camera.eye = [-3.93, 2.85, 27.01];\n * viewer.camera.look = [4.40, 3.72, 8.89];\n * viewer.camera.up = [-0.01, 0.99, 0.039];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const sceneModel = xktLoader.load({\n * id: \"myModel\",\n * src: \"Duplex.xkt\"\n * });\n *\n * const angleMeasurements = new AngleMeasurementsPlugin(viewer);\n *\n * const angleMeasurementsMouseControl = new AngleMeasurementsMouseControl(angleMeasurements, {\n * pointerLens : new PointerLens(viewer)\n * })\n *\n * angleMeasurementsMouseControl.snapping = true;\n *\n * angleMeasurementsMouseControl.activate();\n * ````\n */\nclass AngleMeasurementsMouseControl extends AngleMeasurementsControl {\n\n /**\n * Creates a AngleMeasurementsMouseControl bound to the given AngleMeasurementsPlugin.\n *\n * @param {AngleMeasurementsPlugin} angleMeasurementsPlugin The AngleMeasurementsPlugin to control.\n * @param {*} [cfg] Configuration\n * @param {function} [cfg.canvasToPagePos] Optional function to map canvas-space coordinates to page coordinates.\n * @param {PointerLens} [cfg.pointerLens] A PointerLens to use to provide a magnified view of the cursor when snapping is enabled.\n * @param {boolean} [cfg.snapping=true] Whether to initially enable snap-to-vertex and snap-to-edge for this AngleMeasurementsMouseControl.\n */\n constructor(angleMeasurementsPlugin, cfg = {}) {\n\n super(angleMeasurementsPlugin.viewer.scene);\n\n this._canvasToPagePos = cfg.canvasToPagePos;\n\n this.pointerLens = cfg.pointerLens;\n\n this._active = false;\n this._mouseState = MOUSE_FINDING_ORIGIN;\n\n this._currentAngleMeasurement = null;\n\n // init markerDiv element (think about making its style configurable)\n this._initMarkerDiv();\n\n this._onMouseHoverSurface = null;\n this._onHoverNothing = null;\n this._onPickedNothing = null;\n this._onPickedSurface = null;\n\n this._onInputMouseDown = null;\n this._onInputMouseUp = null;\n\n this._snapping = cfg.snapping !== false;\n\n this._attachPlugin(angleMeasurementsPlugin, cfg);\n }\n\n _initMarkerDiv() {\n const markerDiv = document.createElement('div');\n markerDiv.setAttribute('id', 'myMarkerDiv');\n const canvas = this.scene.canvas.canvas;\n canvas.parentNode.insertBefore(markerDiv, canvas);\n\n markerDiv.style.background = \"black\";\n markerDiv.style.border = \"2px solid blue\";\n markerDiv.style.borderRadius = \"10px\";\n markerDiv.style.width = \"5px\";\n markerDiv.style.height = \"5px\";\n markerDiv.style.top = \"-200px\";\n markerDiv.style.left = \"-200px\";\n markerDiv.style.margin = \"0 0\";\n markerDiv.style.zIndex = \"100\";\n markerDiv.style.position = \"absolute\";\n markerDiv.style.pointerEvents = \"none\";\n\n this.markerDiv = markerDiv;\n }\n\n _destroyMarkerDiv() {\n if (this._markerDiv) {\n const element = document.getElementById('myMarkerDiv');\n element.parentNode.removeChild(element);\n this._markerDiv = null;\n }\n }\n\n _attachPlugin(angleMeasurementsPlugin, cfg = {}) {\n\n /**\n * The {@link AngleMeasurementsPlugin} that owns this AngleMeasurementsMouseControl.\n *\n * @type {AngleMeasurementsPlugin}\n */\n this.angleMeasurementsPlugin = angleMeasurementsPlugin;\n\n /**\n * The {@link AngleMeasurementsPlugin} that owns this AngleMeasurementsMouseControl.\n *\n * @type {AngleMeasurementsPlugin}\n */\n this.plugin = angleMeasurementsPlugin;\n }\n\n /**\n * Gets if this AngleMeasurementsMouseControl is currently active, where it is responding to input.\n *\n * @returns {boolean}\n */\n get active() {\n return this._active;\n }\n\n /**\n * Sets whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsMouseControl.\n *\n * This is `true` by default.\n *\n * Internally, this deactivates then activates the AngleMeasurementsMouseControl when changed, which means that\n * it will destroy any AngleMeasurements currently under construction, and incurs some overhead, since it unbinds\n * and rebinds various input handlers.\n *\n * @param {boolean} snapping Whether to enable snap-to-vertex and snap-edge for this AngleMeasurementsMouseControl.\n */\n set snapping(snapping) {\n if (snapping !== this._snapping) {\n this._snapping = snapping;\n this.deactivate();\n this.activate();\n } else {\n this._snapping = snapping;\n }\n }\n\n /**\n * Gets whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsMouseControl.\n *\n * This is `true` by default.\n *\n * @returns {boolean} Whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsMouseControl.\n */\n get snapping() {\n return this._snapping;\n }\n\n /**\n * Activates this AngleMeasurementsMouseControl, ready to respond to input.\n */\n activate() {\n if (this._active) {\n return;\n }\n if (!this.markerDiv) {\n this._initMarkerDiv(); // if the marker is destroyed after deactivation, we recreate it\n }\n this.angleMeasurementsPlugin;\n const scene = this.scene;\n scene.input;\n const canvas = scene.canvas.canvas;\n const clickTolerance = 20;\n const cameraControl = this.angleMeasurementsPlugin.viewer.cameraControl;\n const pointerLens = this.pointerLens;\n let mouseHovering = false;\n let hoveredEntity = null;\n let lastMouseCanvasX = 0;\n let lastMouseCanvasY = 0;\n const mouseWorldPos = math.vec3();\n const mouseHoverCanvasPos = math.vec2();\n this._currentAngleMeasurement = null;\n\n const getTop = el => el.offsetTop + (el.offsetParent && (el.offsetParent !== canvas.parentNode) && getTop(el.offsetParent));\n const getLeft = el => el.offsetLeft + (el.offsetParent && (el.offsetParent !== canvas.parentNode) && getLeft(el.offsetParent));\n\n const pagePos = math.vec2();\n\n this._onMouseHoverSurface = cameraControl.on(\n this._snapping\n ? \"hoverSnapOrSurface\"\n : \"hoverSurface\",\n event => {\n if (event.snappedToVertex || event.snappedToEdge) {\n if (pointerLens) {\n pointerLens.visible = true;\n pointerLens.canvasPos = event.canvasPos;\n pointerLens.snappedCanvasPos = event.snappedCanvasPos || event.canvasPos;\n pointerLens.snapped = true;\n }\n this.markerDiv.style.background = \"greenyellow\";\n this.markerDiv.style.border = \"2px solid green\";\n } else {\n if (pointerLens) {\n pointerLens.visible = true;\n pointerLens.canvasPos = event.canvasPos;\n pointerLens.snappedCanvasPos = event.canvasPos;\n pointerLens.snapped = false;\n }\n this.markerDiv.style.background = \"pink\";\n this.markerDiv.style.border = \"2px solid red\";\n }\n const canvasPos = event.snappedCanvasPos || event.canvasPos;\n mouseHovering = true;\n hoveredEntity = event.entity;\n mouseWorldPos.set(event.worldPos);\n mouseHoverCanvasPos.set(canvasPos);\n switch (this._mouseState) {\n case MOUSE_FINDING_ORIGIN:\n if (this._canvasToPagePos) {\n this._canvasToPagePos(canvas, canvasPos, pagePos);\n this.markerDiv.style.left = `${pagePos[0] - 5}px`;\n this.markerDiv.style.top = `${pagePos[1] - 5}px`;\n } else {\n this.markerDiv.style.left = `${getLeft(canvas) + canvasPos[0] - 5}px`;\n this.markerDiv.style.top = `${getTop(canvas) + canvasPos[1] - 5}px`;\n }\n break;\n case MOUSE_FINDING_CORNER:\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.angleVisible = false;\n this._currentAngleMeasurement.corner.worldPos = event.worldPos;\n this._currentAngleMeasurement.corner.entity = event.entity;\n }\n this.markerDiv.style.left = `-10000px`;\n this.markerDiv.style.top = `-10000px`;\n canvas.style.cursor = \"pointer\";\n break;\n case MOUSE_FINDING_TARGET:\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.targetWireVisible = true;\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n this._currentAngleMeasurement.target.worldPos = event.worldPos;\n this._currentAngleMeasurement.target.entity = event.entity;\n }\n this.markerDiv.style.left = `-10000px`;\n this.markerDiv.style.top = `-10000px`;\n canvas.style.cursor = \"pointer\";\n break;\n }\n });\n canvas.addEventListener('mousedown', this._onMouseDown = (e) => {\n if (e.which !== 1) {\n return;\n }\n lastMouseCanvasX = e.clientX;\n lastMouseCanvasY = e.clientY;\n });\n canvas.addEventListener(\"mouseup\", this._onMouseUp =(e) => {\n if (e.which !== 1) {\n return;\n }\n if (e.clientX > lastMouseCanvasX + clickTolerance ||\n e.clientX < lastMouseCanvasX - clickTolerance ||\n e.clientY > lastMouseCanvasY + clickTolerance ||\n e.clientY < lastMouseCanvasY - clickTolerance) {\n return;\n }\n switch (this._mouseState) {\n case MOUSE_FINDING_ORIGIN:\n if (mouseHovering) {\n this._currentAngleMeasurement = this.angleMeasurementsPlugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: mouseWorldPos,\n entity: hoveredEntity\n },\n corner: {\n worldPos: mouseWorldPos,\n entity: hoveredEntity\n },\n target: {\n worldPos: mouseWorldPos,\n entity: hoveredEntity\n },\n approximate: true\n });\n this._currentAngleMeasurement.clickable = false;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n this._currentAngleMeasurement.origin.entity = hoveredEntity;\n this._mouseState = MOUSE_FINDING_CORNER;\n this.angleMeasurementsPlugin.fire(\"measurementStart\", this._currentAngleMeasurement);\n }\n break;\n case MOUSE_FINDING_CORNER:\n if (mouseHovering) {\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n this._currentAngleMeasurement.corner.entity = hoveredEntity;\n this._mouseState = MOUSE_FINDING_TARGET;\n } else {\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n hoveredEntity = null;\n this._mouseState = MOUSE_FINDING_ORIGIN;\n this.angleMeasurementsPlugin.fire(\"measurementCancel\", this._currentAngleMeasurement);\n }\n }\n break;\n case MOUSE_FINDING_TARGET:\n if (mouseHovering) {\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n this._currentAngleMeasurement.target.entity = hoveredEntity;\n this._currentAngleMeasurement.clickable = true;\n hoveredEntity = null;\n this.angleMeasurementsPlugin.fire(\"measurementEnd\", this._currentAngleMeasurement);\n this._currentAngleMeasurement = null;\n this._mouseState = MOUSE_FINDING_ORIGIN;\n } else {\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n hoveredEntity = null;\n this._mouseState = MOUSE_FINDING_ORIGIN;\n this.angleMeasurementsPlugin.fire(\"measurementCancel\", this._currentAngleMeasurement);\n }\n }\n break;\n }\n });\n this._onMouseHoverOff = cameraControl.on(\n this._snapping\n ? \"hoverSnapOrSurfaceOff\"\n : \"hoverOff\",\n event => {\n mouseHovering = false;\n if (pointerLens) {\n pointerLens.visible = true;\n pointerLens.pointerPos = event.canvasPos;\n pointerLens.snappedCanvasPos = event.snappedCanvasPos || event.canvasPos;\n pointerLens.snapped = false;\n }\n this.markerDiv.style.left = `-100px`;\n this.markerDiv.style.top = `-100px`;\n if (this._currentAngleMeasurement) {\n switch (this._mouseState) {\n case MOUSE_FINDING_ORIGIN:\n this._currentAngleMeasurement.originVisible = false;\n break;\n case MOUSE_FINDING_CORNER:\n this._currentAngleMeasurement.cornerVisible = false;\n this._currentAngleMeasurement.originWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n break;\n case MOUSE_FINDING_TARGET:\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n break;\n }\n canvas.style.cursor = \"default\";\n }\n });\n this._active = true;\n }\n\n /**\n * Deactivates this AngleMeasurementsMouseControl, making it unresponsive to input.\n *\n * Destroys any {@link AngleMeasurement} under construction by this AngleMeasurementsMouseControl.\n */\n deactivate() {\n if (!this._active) {\n return;\n }\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n if (this.markerDiv) {\n this._destroyMarkerDiv();\n }\n this.reset();\n const canvas = this.scene.canvas.canvas;\n canvas.removeEventListener(\"mousedown\", this._onMouseDown);\n canvas.removeEventListener(\"mouseup\", this._onMouseUp);\n const cameraControl = this.angleMeasurementsPlugin.viewer.cameraControl;\n cameraControl.off(this._onMouseHoverSurface);\n cameraControl.off(this._onPickedSurface);\n cameraControl.off(this._onHoverNothing);\n cameraControl.off(this._onPickedNothing);\n this._currentAngleMeasurement = null;\n this._active = false;\n }\n\n /**\n * Resets this AngleMeasurementsMouseControl.\n *\n * Destroys any {@link AngleMeasurement} under construction by this AngleMeasurementsMouseControl.\n *\n * Does nothing if the AngleMeasurementsMouseControl is not active.\n */\n reset() {\n if (!this._active) {\n return;\n }\n\n this._destroyMarkerDiv();\n this._initMarkerDiv();\n\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n this._mouseState = MOUSE_FINDING_ORIGIN;\n }\n\n /**\n * Gets the {@link AngleMeasurement} under construction by this AngleMeasurementsMouseControl, if any.\n *\n * @returns {null|AngleMeasurement}\n */\n get currentMeasurement() {\n return this._currentAngleMeasurement;\n }\n\n /**\n * Destroys this AngleMeasurementsMouseControl.\n */\n destroy() {\n this.deactivate();\n super.destroy();\n }\n}\n\n/**\n * {@link Viewer} plugin for measuring angles.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_angle_createWithMouse)\n *\n * * [[Example 1: Model with angle measurements](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_angle_modelWithMeasurements)]\n * * [[Example 2: Create angle measurements with mouse](https://xeokit.github.io/xeokit-sdk/examples/measurement/#angle_createWithMouse_snapping)]\n *\n * ## Overview\n *\n * * An {@link AngleMeasurement} shows the angle between two connected 3D line segments, given\n * as three positions on the surface(s) of one or more {@link Entity}s.\n * * As shown on the screen capture above, a AngleMeasurement has two wires that show the line segments, with a label that shows the angle between them.\n * * Create AngleMeasurements programmatically with {@link AngleMeasurementsPlugin#createMeasurement}.\n * * Create AngleMeasurements interactively using a {@link AngleMeasurementsControl}.\n * * Existing AngleMeasurements are registered by ID in {@link AngleMeasurementsPlugin#measurements}.\n * * Destroy AngleMeasurements using {@link AngleMeasurementsPlugin#destroyMeasurement}.\n * * Configure global measurement units and scale via {@link Metrics}, located at {@link Scene#metrics}\n *\n * ## Example 1: Creating AngleMeasurements Programmatically\n *\n * In our first example, we'll use an {@link XKTLoaderPlugin} to load a model, and then use a AngleMeasurementsPlugin to programmatically create two {@link AngleMeasurement}s.\n *\n * Note how each AngleMeasurement has ````origin````, ````corner```` and ````target````, which each indicate a 3D World-space\n * position on the surface of an {@link Entity}. These can be aon the same Entity, or on different Entitys.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_angle_modelWithMeasurements)]\n *\n * ````JavaScript\n * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const angleMeasurements = new AngleMeasurementsPlugin(viewer);\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/duplex.xkt\"\n * });\n *\n * model.on(\"loaded\", () => {\n *\n * const myMeasurement1 = angleMeasurements.createMeasurement({\n * id: \"myAngleMeasurement1\",\n * origin: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"],\n * worldPos: [0.044, 5.998, 17.767]\n * },\n * corner: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"],\n * worldPos: [0.044, 5.998, 17.767]\n * },\n * target: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"],\n * worldPos: [4.738, 3.172, 17.768]\n * },\n * visible: true\n * });\n *\n * const myMeasurement2 = angleMeasurements.createMeasurement({\n * id: \"myAngleMeasurement2\",\n * origin: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FNr2\"],\n * worldPos: [0.457, 2.532, 17.766]\n * },\n * corner: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FNr2\"],\n * worldPos: [0.457, 2.532, 17.766]\n * },\n * target: {\n * entity: viewer.scene.objects[\"1CZILmCaHETO8tf3SgGEXu\"],\n * worldPos: [0.436, 0.001, 22.135]\n * },\n * visible: true\n * });\n * });\n * ````\n *\n * ## Example 2: Creating AngleMeasurements with Mouse Input\n *\n * In our second example, we'll use an {@link XKTLoaderPlugin} to load a model, then we'll use the AngleMeasurementsPlugin's {@link AngleMeasurementsTouchControl} to interactively create {@link AngleMeasurement}s with mouse or touch input.\n *\n * After we've activated the AngleMeasurementsControl, the first click on any {@link Entity} begins constructing a AngleMeasurement, fixing its\n * origin to that Entity. The next click on any Entity will fix the AngleMeasurement's corner, and the next click after\n * that will fix its target and complete the AngleMeasurement.\n *\n * The AngleMeasurementControl will then wait for the next click on any Entity, to begin constructing\n * another AngleMeasurement, and so on, until deactivated again.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#angle_createWithMouse_snapping)]\n *\n * ````JavaScript\n * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin, AngleMeasurementsMouseControl, PointerLens} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * cconst angleMeasurementsMouseControl = new AngleMeasurementsMouseControl(angleMeasurements, {\n * pointerLens : new PointerLens(viewer)\n * })\n *\n * angleMeasurementsMouseControl.snapToVertex = true;\n * angleMeasurementsMouseControl.snapToEdge = true;\n *\n * angleMeasurementsMouseControl.activate();\n * ````\n *\n * ## Example 4: Attaching Mouse Handlers\n *\n * In our fourth example, we'll attach even handlers to our plugin, to catch when the user\n * hovers or right-clicks over our measurements.\n *\n * [[Run example](/examples/measurement/#angle_modelWithMeasurements)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const angleMeasurements = new AngleMeasurementsPlugin(viewer);\n *\n * angleMeasurements.on(\"mouseOver\", (e) => {\n * e.measurement.setHighlighted(true);\n * });\n *\n * angleMeasurements.on(\"mouseLeave\", (e) => {\n * e.measurement.setHighlighted(false);\n * });\n *\n * angleMeasurements.on(\"contextMenu\", (e) => {\n * // Show context menu\n * e.event.preventDefault();\n * });\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/duplex.xkt\"\n * });\n *\n * model.on(\"loaded\", () => {\n *\n * angleMeasurementsPlugin.createMeasurement({\n * id: \"angleMeasurement1\",\n * origin: {\n * entity: viewer.scene.objects[\"1CZILmCaHETO8tf3SgGEXu\"],\n * worldPos: [0.4158603637281142, 2.5193106917110457, 17.79972838299403]\n * },\n * corner: {\n * entity: viewer.scene.objects[\"1CZILmCaHETO8tf3SgGEXu\"],\n * worldPos: [0.41857741956197625,0.0987169929481646,17.799763071093395]\n * },\n * target: {\n * entity: viewer.scene.objects[\"1CZILmCaHETO8tf3SgGEXu\"],\n * worldPos: [5.235526066859247, 0.11580773869801986, 17.824891550941565]\n * },\n * visible: true\n * });\n *\n * angleMeasurementsPlugin.createMeasurement({\n * id: \"angleMeasurement2\",\n * origin: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FNr2\"],\n * worldPos: [-0.00003814187850181838, 5.9996748076205115,17.79996871551525]\n * },\n * corner: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FNqI\"],\n * worldPos: [-0.0005214119318139865, 3.1010044228517595, 17.787656604483363]\n *\n * },\n * target: {\n * entity: viewer.scene.objects[\"1s1jVhK8z0pgKYcr9jt7AB\"],\n * worldPos: [ 8.380657312957396, 3.1055697628459553, 17.799220108187185]\n * },\n * visible: true\n * });\n * });\n * ````\n *\n * ## Example 5: Creating AngleMeasurements with Touch Input\n *\n * In our fifth example, we'll show how to create angle measurements with touch input, with snapping\n * to the nearest vertex or edge. While creating the measurements, a long-touch when setting the\n * start, corner or end point will cause the point to snap to the nearest vertex or edge. A quick\n * touch-release will immediately set the point at the tapped position on the object surface.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#angle_createWithTouch_snapping)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin, AngleMeasurementsTouchControl} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const angleMeasurements = new AngleMeasurementsPlugin(viewer);\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/duplex.xkt\"\n * });\n *\n * const angleMeasurements = new AngleMeasurementsPlugin(viewer);\n *\n * const angleMeasurementsTouchControl = new AngleMeasurementsTouchControl(angleMeasurements, {\n * pointerLens : new PointerLens(viewer),\n * snapToVertex: true,\n * snapToEdge: true\n * })\n *\n * angleMeasurementsTouchControl.activate();\n * ````\n */\nclass AngleMeasurementsPlugin extends Plugin {\n\n /**\n * @constructor\n * @param {Viewer} viewer The Viewer.\n * @param {Object} [cfg] Plugin configuration.\n * @param {String} [cfg.id=\"AngleMeasurements\"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.\n * @param {HTMLElement} [cfg.container] Container DOM element for markers and labels. Defaults to ````document.body````.\n * @param {string} [cfg.defaultColor=null] The default color of the dots, wire and label.\n * @param {boolean} [cfg.defaultLabelsVisible=true] The default value of {@link AngleMeasurement.labelsVisible}.\n * @param {number} [cfg.zIndex] If set, the wires, dots and labels will have this zIndex (+1 for dots and +2 for labels).\n * @param {PointerCircle} [cfg.pointerLens] A PointerLens to help the user position the pointer. This can be shared with other plugins.\n */\n constructor(viewer, cfg = {}) {\n\n super(\"AngleMeasurements\", viewer);\n\n this._container = cfg.container || document.body;\n\n this._defaultControl = null;\n\n this._measurements = {};\n\n this.defaultColor = cfg.defaultColor !== undefined ? cfg.defaultColor : \"#00BBFF\";\n this.defaultLabelsVisible = cfg.defaultLabelsVisible !== false;\n this.zIndex = cfg.zIndex || 10000;\n\n this._onMouseOver = (event, measurement) => {\n this.fire(\"mouseOver\", {\n plugin: this,\n angleMeasurement: measurement,\n measurement,\n event\n });\n };\n\n this._onMouseLeave = (event, measurement) => {\n this.fire(\"mouseLeave\", {\n plugin: this,\n angleMeasurement: measurement,\n measurement,\n event\n });\n };\n\n this._onContextMenu = (event, measurement) => {\n this.fire(\"contextMenu\", {\n plugin: this,\n angleMeasurement: measurement,\n measurement,\n event\n });\n };\n }\n\n /**\n * Gets the plugin's HTML container element, if any.\n * @returns {*|HTMLElement|HTMLElement}\n */\n getContainerElement() {\n return this._container;\n }\n\n\n /**\n * @private\n */\n send(name, value) {\n\n }\n\n /**\n * Gets the default {@link AngleMeasurementsMouseControl}.\n *\n * @type {AngleMeasurementsMouseControl}\n * @deprecated\n */\n get control() {\n if (!this._defaultControl) {\n this._defaultControl = new AngleMeasurementsMouseControl(this, {});\n }\n return this._defaultControl;\n }\n\n /**\n * Gets the existing {@link AngleMeasurement}s, each mapped to its {@link AngleMeasurement#id}.\n *\n * @type {{String:AngleMeasurement}}\n */\n get measurements() {\n return this._measurements;\n }\n\n /**\n * Creates an {@link AngleMeasurement}.\n *\n * The AngleMeasurement is then registered by {@link AngleMeasurement#id} in {@link AngleMeasurementsPlugin#measurements}.\n *\n * @param {Object} params {@link AngleMeasurement} configuration.\n * @param {String} params.id Unique ID to assign to {@link AngleMeasurement#id}. The AngleMeasurement will be registered by this in {@link AngleMeasurementsPlugin#measurements} and {@link Scene.components}. Must be unique among all components in the {@link Viewer}.\n * @param {Number[]} params.origin.worldPos Origin World-space 3D position.\n * @param {Entity} params.origin.entity Origin Entity.\n * @param {Number[]} params.corner.worldPos Corner World-space 3D position.\n * @param {Entity} params.corner.entity Corner Entity.\n * @param {Number[]} params.target.worldPos Target World-space 3D position.\n * @param {Entity} params.target.entity Target Entity.\n * @param {Boolean} [params.visible=true] Whether to initially show the {@link AngleMeasurement}.\n * @returns {AngleMeasurement} The new {@link AngleMeasurement}.\n */\n createMeasurement(params = {}) {\n if (this.viewer.scene.components[params.id]) {\n this.error(\"Viewer scene component with this ID already exists: \" + params.id);\n delete params.id;\n }\n const origin = params.origin;\n const corner = params.corner;\n const target = params.target;\n const measurement = new AngleMeasurement(this, {\n id: params.id,\n plugin: this,\n container: this._container,\n origin: {\n entity: origin.entity,\n worldPos: origin.worldPos\n },\n corner: {\n entity: corner.entity,\n worldPos: corner.worldPos\n },\n target: {\n entity: target.entity,\n worldPos: target.worldPos\n },\n\n visible: params.visible,\n originVisible: true,\n originWireVisible: true,\n cornerVisible: true,\n targetWireVisible: true,\n targetVisible: true,\n onMouseOver: this._onMouseOver,\n onMouseLeave: this._onMouseLeave,\n onContextMenu: this._onContextMenu\n });\n this._measurements[measurement.id] = measurement;\n measurement.on(\"destroyed\", () => {\n delete this._measurements[measurement.id];\n });\n measurement.clickable = true;\n this.fire(\"measurementCreated\", measurement);\n return measurement;\n }\n\n /**\n * Destroys a {@link AngleMeasurement}.\n *\n * @param {String} id ID of AngleMeasurement to destroy.\n */\n destroyMeasurement(id) {\n const measurement = this._measurements[id];\n if (!measurement) {\n this.log(\"AngleMeasurement not found: \" + id);\n return;\n }\n measurement.destroy();\n this.fire(\"measurementDestroyed\", measurement);\n }\n\n /**\n * Shows all or hides the angle label of each {@link AngleMeasurement}.\n *\n * @param {Boolean} labelsShown Whether or not to show the labels.\n */\n setLabelsShown(labelsShown) {\n for (const [key, measurement] of Object.entries(this.measurements)) {\n measurement.labelShown = labelsShown;\n }\n }\n\n /**\n * Destroys all {@link AngleMeasurement}s.\n */\n clear() {\n const ids = Object.keys(this._measurements);\n for (var i = 0, len = ids.length; i < len; i++) {\n this.destroyMeasurement(ids[i]);\n }\n }\n\n /**\n * Destroys this AngleMeasurementsPlugin.\n *\n * Destroys all {@link AngleMeasurement}s first.\n */\n destroy() {\n this.clear();\n super.destroy();\n }\n}\n\nconst WAITING_FOR_ORIGIN_TOUCH_START$1 = 0;\nconst WAITING_FOR_ORIGIN_QUICK_TOUCH_END$1 = 1;\nconst WAITING_FOR_ORIGIN_LONG_TOUCH_END$1 = 2;\n\nconst WAITING_FOR_CORNER_TOUCH_START = 3;\nconst WAITING_FOR_CORNER_QUICK_TOUCH_END = 4;\nconst WAITING_FOR_CORNER_LONG_TOUCH_END = 5;\n\nconst WAITING_FOR_TARGET_TOUCH_START$1 = 6;\nconst WAITING_FOR_TARGET_QUICK_TOUCH_END$1 = 7;\nconst WAITING_FOR_TARGET_LONG_TOUCH_END$1 = 8;\n\nconst TOUCH_CANCELING$1 = 7;\n\n/**\n * Creates {@link AngleMeasurement}s from touch input.\n *\n * See {@link AngleMeasurementsPlugin} for more info.\n *\n */\nclass AngleMeasurementsTouchControl extends AngleMeasurementsControl {\n\n /**\n * Creates a AngleMeasurementsTouchControl bound to the given AngleMeasurementsPlugin.\n */\n constructor(angleMeasurementsPlugin, cfg = {}) {\n\n super(angleMeasurementsPlugin.viewer.scene);\n\n this.pointerLens = cfg.pointerLens;\n this.pointerCircle = new PointerCircle(angleMeasurementsPlugin.viewer);\n\n this._active = false;\n\n const markerDiv = document.createElement('div');\n const canvas = this.scene.canvas.canvas;\n canvas.parentNode.insertBefore(markerDiv, canvas);\n\n markerDiv.style.background = \"black\";\n markerDiv.style.border = \"2px solid blue\";\n markerDiv.style.borderRadius = \"10px\";\n markerDiv.style.width = \"5px\";\n markerDiv.style.height = \"5px\";\n markerDiv.style.margin = \"-200px -200px\";\n markerDiv.style.zIndex = \"100\";\n markerDiv.style.position = \"absolute\";\n markerDiv.style.pointerEvents = \"none\";\n\n this.markerDiv = markerDiv;\n\n this._currentAngleMeasurement = null;\n\n this._onCanvasTouchStart = null;\n this._onCanvasTouchEnd = null;\n this._longTouchTimeoutMs = 300;\n this._snapping = cfg.snapping !== false;\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n\n this._attachPlugin(angleMeasurementsPlugin, cfg);\n }\n\n _attachPlugin(angleMeasurementsPlugin) {\n\n /**\n * The {@link AngleMeasurementsPlugin} that owns this AngleMeasurementsTouchControl.\n * @type {AngleMeasurementsPlugin}\n */\n this.angleMeasurementsPlugin = angleMeasurementsPlugin;\n\n /**\n * The {@link AngleMeasurementsPlugin} that owns this AngleMeasurementsTouchControl.\n * @type {AngleMeasurementsPlugin}\n */\n this.plugin = angleMeasurementsPlugin;\n }\n\n /** Gets if this AngleMeasurementsTouchControl is currently active, where it is responding to input.\n *\n * @returns {Boolean}\n */\n get active() {\n return this._active;\n }\n\n /**\n * Sets whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsMouseControl.\n *\n * This is `true` by default.\n *\n * Internally, this deactivates then activates the AngleMeasurementsMouseControl when changed, which means that\n * it will destroy any AngleMeasurements currently under construction, and incurs some overhead, since it unbinds\n * and rebinds various input handlers.\n *\n * @param {boolean} snapping Whether to enable snap-to-vertex and snap-edge for this AngleMeasurementsMouseControl.\n */\n set snapping(snapping) {\n if (snapping !== this._snapping) {\n this._snapping = snapping;\n this.deactivate();\n this.activate();\n } else {\n this._snapping = snapping;\n }\n }\n\n /**\n * Gets whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsMouseControl.\n *\n * This is `true` by default.\n *\n * @returns {boolean} Whether snap-to-vertex and snap-to-edge are enabled for this AngleMeasurementsMouseControl.\n */\n get snapping() {\n return this._snapping;\n }\n\n /**\n * Activates this AngleMeasurementsTouchControl, ready to respond to input.\n */\n activate() {\n\n if (this._active) {\n return;\n }\n\n const plugin = this.plugin;\n const scene = this.scene;\n const canvas = scene.canvas.canvas;\n plugin.pointerLens;\n const pointerWorldPos = math.vec3();\n\n const touchTolerance = 20;\n\n let longTouchTimeout = null;\n\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n\n const touchStartCanvasPos = math.vec2();\n const touchMoveCanvasPos = math.vec2();\n const touchEndCanvasPos = math.vec2();\n\n let touchId = null;\n\n const disableCameraNavigation = () => {\n this.plugin.viewer.cameraControl.active = false;\n };\n\n const enableCameraNavigation = () => {\n this.plugin.viewer.cameraControl.active = true;\n };\n\n const cancel = () => {\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n enableCameraNavigation();\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n };\n\n canvas.addEventListener(\"touchstart\", this._onCanvasTouchStart = (event) => {\n\n const currentNumTouches = event.touches.length;\n\n if (currentNumTouches !== 1) {\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n return;\n }\n\n const touch = event.touches[0];\n const touchX = touch.clientX;\n const touchY = touch.clientY;\n\n touchStartCanvasPos.set([touchX, touchY]);\n touchMoveCanvasPos.set([touchX, touchY]);\n\n switch (this._touchState) {\n\n case WAITING_FOR_ORIGIN_TOUCH_START$1:\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n cancel();\n return;\n }\n const snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.snapped) {\n pointerWorldPos.set(snapPickResult.worldPos);\n this.pointerCircle.start(snapPickResult.snappedCanvasPos);\n } else {\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n pointerWorldPos.set(pickResult.worldPos);\n this.pointerCircle.start(pickResult.canvasPos);\n } else {\n return;\n }\n }\n longTouchTimeout = setTimeout(() => {\n if (currentNumTouches !== 1 ||\n touchMoveCanvasPos[0] > touchStartCanvasPos[0] + touchTolerance ||\n touchMoveCanvasPos[0] < touchStartCanvasPos[0] - touchTolerance ||\n touchMoveCanvasPos[1] > touchStartCanvasPos[1] + touchTolerance ||\n touchMoveCanvasPos[1] < touchStartCanvasPos[1] - touchTolerance) {\n return; // Has moved\n }\n // Long touch\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = touchStartCanvasPos;\n this.pointerLens.cursorPos = touchStartCanvasPos;\n }\n if (this.pointerLens) {\n this.pointerLens.canvasPos = touchMoveCanvasPos;\n this.pointerLens.snapped = false;\n }\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.canvasPos;\n this.pointerLens.snapped = true;\n }\n // pointerWorldPos.set(snapPickResult.worldPos);\n if (!this._currentAngleMeasurement) {\n this._currentAngleMeasurement = plugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: snapPickResult.worldPos,\n entity: snapPickResult.entity\n },\n corner: {\n worldPos: snapPickResult.worldPos,\n entity: snapPickResult.entity\n },\n target: {\n worldPos: snapPickResult.worldPos,\n entity: snapPickResult.entity\n }\n });\n this._currentAngleMeasurement.clickable = false;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = false;\n this._currentAngleMeasurement.cornerVisible = false;\n this._currentAngleMeasurement.cornerWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n } else {\n this._currentAngleMeasurement.origin.worldPos = pointerWorldPos;\n }\n this.angleMeasurementsPlugin.fire(\"measurementStart\", this._currentAngleMeasurement);\n // if (this.pointerLens) {\n // this.pointerLens.cursorPos = pickResult.canvasPos;\n // this.pointerLens.snapped = false;\n // }\n this._touchState = WAITING_FOR_ORIGIN_LONG_TOUCH_END$1;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_ORIGIN_TOUCH_START -> WAITING_FOR_ORIGIN_LONG_TOUCH_END\")\n disableCameraNavigation();\n }, this._longTouchTimeoutMs);\n this._touchState = WAITING_FOR_ORIGIN_QUICK_TOUCH_END$1;\n //console.log(\"touchstart: this._touchState= WAITING_FOR_ORIGIN_TOUCH_START -> WAITING_FOR_ORIGIN_QUICK_TOUCH_END\")\n\n touchId = touch.identifier;\n\n break;\n\n case WAITING_FOR_CORNER_TOUCH_START:\n\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n return;\n }\n if (currentNumTouches === 1) { // One finger down\n longTouchTimeout = setTimeout(() => {\n longTouchTimeout = null;\n if (currentNumTouches !== 1 ||\n touchMoveCanvasPos[0] > touchStartCanvasPos[0] + touchTolerance ||\n touchMoveCanvasPos[0] < touchStartCanvasPos[0] - touchTolerance ||\n touchMoveCanvasPos[1] > touchStartCanvasPos[1] + touchTolerance ||\n touchMoveCanvasPos[1] < touchStartCanvasPos[1] - touchTolerance) {\n // Has moved\n return;\n }\n\n // Long touch\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = touchStartCanvasPos;\n this.pointerLens.snapped = false;\n }\n\n const snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.snapped) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n this.pointerCircle.start(snapPickResult.snappedCanvasPos);\n pointerWorldPos.set(snapPickResult.worldPos);\n this._currentAngleMeasurement.corner.worldPos = snapPickResult.worldPos;\n this._currentAngleMeasurement.corner.entity = snapPickResult.entity;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n this.angleMeasurementsPlugin.fire(\"measurementStart\", this._currentAngleMeasurement);\n } else {\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n this.pointerCircle.start(pickResult.canvasPos);\n pointerWorldPos.set(pickResult.worldPos);\n this._currentAngleMeasurement.corner.worldPos = pickResult.worldPos;\n this._currentAngleMeasurement.corner.entity = pickResult.entity;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n this.angleMeasurementsPlugin.fire(\"measurementStart\", this._currentAngleMeasurement);\n } else {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = null;\n this.pointerLens.snapped = false;\n\n }\n }\n }\n this._touchState = WAITING_FOR_CORNER_LONG_TOUCH_END;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_CORNER_TOUCH_START -> WAITING_FOR_CORNER_LONG_TOUCH_END\")\n\n disableCameraNavigation();\n\n }, this._longTouchTimeoutMs);\n\n this._touchState = WAITING_FOR_CORNER_QUICK_TOUCH_END;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_CORNER_TOUCH_START -> WAITING_FOR_CORNER_QUICK_TOUCH_END\")\n }\n\n touchId = touch.identifier;\n\n break;\n\n case WAITING_FOR_TARGET_TOUCH_START$1:\n\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n return;\n }\n if (currentNumTouches === 1) { // One finger down\n longTouchTimeout = setTimeout(() => {\n longTouchTimeout = null;\n if (currentNumTouches !== 1 ||\n touchMoveCanvasPos[0] > touchStartCanvasPos[0] + touchTolerance ||\n touchMoveCanvasPos[0] < touchStartCanvasPos[0] - touchTolerance ||\n touchMoveCanvasPos[1] > touchStartCanvasPos[1] + touchTolerance ||\n touchMoveCanvasPos[1] < touchStartCanvasPos[1] - touchTolerance) {\n // Has moved\n return;\n }\n\n // Long touch\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = touchStartCanvasPos;\n this.pointerLens.snapped = false;\n }\n\n const snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.snapped) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n this.pointerCircle.start(snapPickResult.snappedCanvasPos);\n pointerWorldPos.set(snapPickResult.worldPos);\n this._currentAngleMeasurement.target.worldPos = snapPickResult.worldPos;\n this._currentAngleMeasurement.target.entity = snapPickResult.entity;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = true;\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.targetWireVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n this.angleMeasurementsPlugin.fire(\"measurementStart\", this._currentAngleMeasurement);\n } else {\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n this.pointerCircle.start(pickResult.canvasPos);\n pointerWorldPos.set(pickResult.worldPos);\n this._currentAngleMeasurement.target.worldPos = pickResult.worldPos;\n this._currentAngleMeasurement.target.entity = pickResult.entity;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = true;\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.targetWireVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n this.angleMeasurementsPlugin.fire(\"measurementStart\", this._currentAngleMeasurement);\n } else {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = null;\n this.pointerLens.snapped = false;\n\n }\n }\n }\n this._touchState = WAITING_FOR_TARGET_LONG_TOUCH_END$1;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_TARGET_TOUCH_START -> WAITING_FOR_TARGET_LONG_TOUCH_END\")\n\n disableCameraNavigation();\n\n }, this._longTouchTimeoutMs);\n\n this._touchState = WAITING_FOR_TARGET_QUICK_TOUCH_END$1;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_TARGET_TOUCH_START -> WAITING_FOR_TARGET_QUICK_TOUCH_END\")\n }\n\n touchId = touch.identifier;\n\n break;\n\n\n default:\n if (longTouchTimeout !== null) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n this._touchState = TOUCH_CANCELING$1;\n // console.log(\"touchstart: this._touchState= default -> TOUCH_CANCELING\")\n return;\n }\n\n }, {passive: true});\n\n\n canvas.addEventListener(\"touchmove\", (event) => {\n\n this.pointerCircle.stop();\n\n const currentNumTouches = event.touches.length;\n\n if (currentNumTouches !== 1 || event.changedTouches.length !== 1) {\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n return;\n }\n\n const touch = event.touches[0];\n const touchX = touch.clientX;\n const touchY = touch.clientY;\n\n if (touch.identifier !== touchId) {\n return;\n }\n\n touchMoveCanvasPos.set([touchX, touchY]);\n\n let snapPickResult;\n let pickResult;\n\n switch (this._touchState) {\n\n case WAITING_FOR_ORIGIN_LONG_TOUCH_END$1:\n if (this.pointerLens) {\n this.pointerLens.canvasPos = touchMoveCanvasPos;\n }\n snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && (snapPickResult.snapped)) {\n if (this.pointerLens) {\n this.pointerLens.snappedCanvasPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n pointerWorldPos.set(snapPickResult.worldPos);\n this._currentAngleMeasurement.origin.worldPos = snapPickResult.worldPos;\n } else {\n pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n pointerWorldPos.set(pickResult.worldPos);\n this._currentAngleMeasurement.origin.worldPos = pickResult.worldPos;\n } else {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = null;\n this.pointerLens.snapped = false;\n }\n }\n }\n this._touchState = WAITING_FOR_ORIGIN_LONG_TOUCH_END$1;\n // console.log(\"touchmove: this._touchState= WAITING_FOR_ORIGIN_LONG_TOUCH_END -> WAITING_FOR_ORIGIN_LONG_TOUCH_END\")\n break;\n\n case WAITING_FOR_CORNER_LONG_TOUCH_END:\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n this._touchState = TOUCH_CANCELING$1;\n // console.log(\"touchmove: this._touchState= QUICK_TOUCH_FINDING_CORNER -> TOUCH_CANCELING\")\n return;\n }\n if (this.pointerLens) {\n this.pointerLens.canvasPos = touchMoveCanvasPos;\n }\n snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n this._currentAngleMeasurement.corner.worldPos = snapPickResult.worldPos;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n } else {\n pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n this._currentAngleMeasurement.corner.worldPos = pickResult.worldPos;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n }\n }\n this._touchState = WAITING_FOR_CORNER_LONG_TOUCH_END;\n break;\n\n\n case WAITING_FOR_TARGET_LONG_TOUCH_END$1:\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n this._touchState = TOUCH_CANCELING$1;\n // console.log(\"touchmove: this._touchState= QUICK_TOUCH_FINDING_TARGET -> TOUCH_CANCELING\")\n return;\n }\n if (this.pointerLens) {\n this.pointerLens.canvasPos = touchMoveCanvasPos;\n }\n snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n this._currentAngleMeasurement.target.worldPos = snapPickResult.worldPos;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = true;\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.targetWireVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n } else {\n pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n this._currentAngleMeasurement.target.worldPos = pickResult.worldPos;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = true;\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.targetWireVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n }\n }\n this._touchState = WAITING_FOR_TARGET_LONG_TOUCH_END$1;\n break;\n }\n }, {passive: true});\n\n canvas.addEventListener(\"touchend\", this._onCanvasTouchEnd = (event) => {\n\n this.pointerCircle.stop();\n\n const numChangedTouches = event.changedTouches.length;\n\n if (numChangedTouches !== 1) {\n return;\n }\n\n const touch = event.changedTouches[0];\n const touchX = touch.clientX;\n const touchY = touch.clientY;\n\n if (touch.identifier !== touchId) {\n return;\n }\n\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n\n touchEndCanvasPos.set([touchX, touchY]);\n\n switch (this._touchState) {\n\n case WAITING_FOR_ORIGIN_QUICK_TOUCH_END$1: {\n if (numChangedTouches !== 1 ||\n touchX > touchStartCanvasPos[0] + touchTolerance ||\n touchX < touchStartCanvasPos[0] - touchTolerance ||\n touchY > touchStartCanvasPos[1] + touchTolerance ||\n touchY < touchStartCanvasPos[1] - touchTolerance) {\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n return;\n }\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n this._currentAngleMeasurement = plugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: pickResult.worldPos\n },\n corner: {\n worldPos: pickResult.worldPos\n },\n target: {\n worldPos: pickResult.worldPos\n }\n });\n this._currentAngleMeasurement.clickable = false;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = false;\n this._currentAngleMeasurement.cornerVisible = false;\n this._currentAngleMeasurement.cornerWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n this._touchState = WAITING_FOR_CORNER_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_QUICK_TOUCH_END -> WAITING_FOR_CORNER_TOUCH_START\")\n } else {\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_QUICK_TOUCH_END -> WAITING_FOR_ORIGIN_TOUCH_START\")\n }\n }\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_ORIGIN_LONG_TOUCH_END$1:\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n if (!this._currentAngleMeasurement) {\n if (this.pointerLens) {\n this.pointerLens.snapped = false;\n this.pointerLens.visible = false;\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_LONG_TOUCH_END (no measurement) -> WAITING_FOR_ORIGIN_TOUCH_START\")\n } else {\n this._touchState = WAITING_FOR_CORNER_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_LONG_TOUCH_END (picked, begin measurement) -> WAITING_FOR_CORNER_TOUCH_START\")\n }\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_CORNER_QUICK_TOUCH_END: {\n if (numChangedTouches !== 1 ||\n touchX > touchStartCanvasPos[0] + touchTolerance ||\n touchX < touchStartCanvasPos[0] - touchTolerance ||\n touchY > touchStartCanvasPos[1] + touchTolerance ||\n touchY < touchStartCanvasPos[1] - touchTolerance) {\n this._touchState = WAITING_FOR_CORNER_TOUCH_START;\n return;\n }\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n this._currentAngleMeasurement.corner.worldPos = pickResult.worldPos;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = false;\n this._currentAngleMeasurement.targetVisible = false;\n this._currentAngleMeasurement.targetWireVisible = false;\n this._currentAngleMeasurement.angleVisible = false;\n this._touchState = WAITING_FOR_TARGET_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_CORNER_TOUCH_START -> WAITING_FOR_ORIGIN_TOUCH_START\")\n } else {\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_CORNER_TOUCH_START -> WAITING_FOR_ORIGIN_TOUCH_START\")\n }\n\n }\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_CORNER_LONG_TOUCH_END:\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n this._touchState = WAITING_FOR_TARGET_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_CORNER_LONG_TOUCH_END -> WAITING_FOR_ORIGIN_TOUCH_START\")\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_TARGET_QUICK_TOUCH_END$1: {\n if (numChangedTouches !== 1 ||\n touchX > touchStartCanvasPos[0] + touchTolerance ||\n touchX < touchStartCanvasPos[0] - touchTolerance ||\n touchY > touchStartCanvasPos[1] + touchTolerance ||\n touchY < touchStartCanvasPos[1] - touchTolerance) {\n this._touchState = WAITING_FOR_TARGET_TOUCH_START$1;\n return;\n }\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n this._currentAngleMeasurement.target.worldPos = pickResult.worldPos;\n this._currentAngleMeasurement.originVisible = true;\n this._currentAngleMeasurement.originWireVisible = true;\n this._currentAngleMeasurement.cornerVisible = true;\n this._currentAngleMeasurement.cornerWireVisible = true;\n this._currentAngleMeasurement.targetVisible = true;\n this._currentAngleMeasurement.targetWireVisible = true;\n this._currentAngleMeasurement.angleVisible = true;\n this.angleMeasurementsPlugin.fire(\"measurementEnd\", this._currentAngleMeasurement);\n this._currentAngleMeasurement = null;\n } else {\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_TARGET_TOUCH_START -> WAITING_FOR_ORIGIN_TOUCH_START\")\n }\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_TARGET_LONG_TOUCH_END$1:\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n if (!this._currentAngleMeasurement || !this._currentAngleMeasurement.targetVisible) {\n if (this._currentAngleMeasurement) {\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_TARGET_LONG_TOUCH_END (no target found) -> WAITING_FOR_ORIGIN_TOUCH_START\")\n } else {\n this._currentAngleMeasurement.clickable = true;\n this.angleMeasurementsPlugin.fire(\"measurementEnd\", this._currentAngleMeasurement);\n this._currentAngleMeasurement = null;\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START$1;\n // console.log(\"touchend: this._touchState= WAITING_FOR_TARGET_LONG_TOUCH_END -> WAITING_FOR_ORIGIN_TOUCH_START\")\n }\n enableCameraNavigation();\n break;\n }\n\n }, {passive: true});\n\n this._active = true;\n }\n\n /**\n * Deactivates this AngleMeasurementsTouchControl, making it unresponsive to input.\n *\n * Destroys any {@link AngleMeasurement} under construction.\n */\n deactivate() {\n if (!this._active) {\n return;\n }\n if (this.plugin.pointerLens) {\n this.plugin.pointerLens.visible = false;\n }\n this.reset();\n const canvas = this.plugin.viewer.scene.canvas.canvas;\n canvas.removeEventListener(\"touchstart\", this._onCanvasTouchStart);\n canvas.removeEventListener(\"touchend\", this._onCanvasTouchEnd);\n if (this._currentAngleMeasurement) {\n this.angleMeasurementsPlugin.fire(\"measurementCancel\", this._currentAngleMeasurement);\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n this._active = false;\n this.plugin.viewer.cameraControl.active = true;\n }\n\n /**\n * Resets this AngleMeasurementsTouchControl.\n *\n * Destroys any {@link AngleMeasurement} under construction.\n *\n * Does nothing if the AngleMeasurementsTouchControl is not active.\n */\n reset() {\n if (!this._active) {\n return;\n }\n if (this._currentAngleMeasurement) {\n this.angleMeasurementsPlugin.fire(\"measurementCancel\", this._currentAngleMeasurement);\n this._currentAngleMeasurement.destroy();\n this._currentAngleMeasurement = null;\n }\n }\n\n /**\n * Gets the {@link AngleMeasurement} under construction by this AngleMeasurementsTouchControl, if any.\n *\n * @returns {null|AngleMeasurement}\n */\n get currentMeasurement() {\n return this._currentAngleMeasurement;\n }\n\n /**\n * Destroys this AngleMeasurementsTouchControl.\n */\n destroy() {\n this.deactivate();\n super.destroy();\n }\n}\n\n/**\n * A {@link Marker} with an HTML label attached to it, managed by an {@link AnnotationsPlugin}.\n *\n * See {@link AnnotationsPlugin} for more info.\n */\nclass Annotation extends Marker {\n\n /**\n * @private\n */\n constructor(owner, cfg) {\n\n super(owner, cfg);\n\n /**\n * The {@link AnnotationsPlugin} this Annotation was created by.\n * @type {AnnotationsPlugin}\n */\n this.plugin = cfg.plugin;\n\n this._container = cfg.container;\n if (!this._container) {\n throw \"config missing: container\";\n }\n\n if ((!cfg.markerElement) && (!cfg.markerHTML)) {\n throw \"config missing: need either markerElement or markerHTML\";\n }\n if ((!cfg.labelElement) && (!cfg.labelHTML)) {\n throw \"config missing: need either labelElement or labelHTML\";\n }\n\n this._htmlDirty = false;\n\n if (cfg.markerElement) {\n this._marker = cfg.markerElement;\n this._marker.addEventListener(\"click\", this._onMouseClickedExternalMarker = () => {\n this.plugin.fire(\"markerClicked\", this);\n });\n this._marker.addEventListener(\"mouseenter\", this._onMouseEnterExternalMarker = () => {\n this.plugin.fire(\"markerMouseEnter\", this);\n });\n this._marker.addEventListener(\"mouseleave\", this._onMouseLeaveExternalMarker = () => {\n this.plugin.fire(\"markerMouseLeave\", this);\n });\n this._markerExternal = true; // Don't destroy marker when destroying Annotation\n } else {\n this._markerHTML = cfg.markerHTML;\n this._htmlDirty = true;\n this._markerExternal = false;\n }\n\n if (cfg.labelElement) {\n this._label = cfg.labelElement;\n this._labelExternal = true; // Don't destroy marker when destroying Annotation\n } else {\n this._labelHTML = cfg.labelHTML;\n this._htmlDirty = true;\n this._labelExternal = false;\n }\n\n this._markerShown = !!cfg.markerShown;\n this._labelShown = !!cfg.labelShown;\n this._values = cfg.values || {};\n this._layoutDirty = true;\n this._visibilityDirty = true;\n\n this._buildHTML();\n\n this._onTick = this.scene.on(\"tick\", () => {\n if (this._htmlDirty) {\n this._buildHTML();\n this._htmlDirty = false;\n this._layoutDirty = true;\n this._visibilityDirty = true;\n }\n if (this._layoutDirty || this._visibilityDirty) {\n if (this._markerShown || this._labelShown) {\n this._updatePosition();\n this._layoutDirty = false;\n }\n }\n if (this._visibilityDirty) {\n this._marker.style.visibility = (this.visible && this._markerShown) ? \"visible\" : \"hidden\";\n this._label.style.visibility = (this.visible && this._markerShown && this._labelShown) ? \"visible\" : \"hidden\";\n this._visibilityDirty = false;\n }\n });\n\n this.on(\"canvasPos\", () => {\n this._layoutDirty = true;\n });\n\n this.on(\"visible\", () => {\n this._visibilityDirty = true;\n });\n\n this.setMarkerShown(cfg.markerShown !== false);\n this.setLabelShown(cfg.labelShown);\n\n /**\n * Optional World-space position for {@link Camera#eye}, used when this Annotation is associated with a {@link Camera} position.\n *\n * Undefined by default.\n *\n * @type {Number[]} Eye position.\n */\n this.eye = cfg.eye ? cfg.eye.slice() : null;\n\n /**\n * Optional World-space position for {@link Camera#look}, used when this Annotation is associated with a {@link Camera} position.\n *\n * Undefined by default.\n *\n * @type {Number[]} The \"look\" vector.\n */\n this.look = cfg.look ? cfg.look.slice() : null;\n\n /**\n * Optional World-space position for {@link Camera#up}, used when this Annotation is associated with a {@link Camera} position.\n *\n * Undefined by default.\n *\n * @type {Number[]} The \"up\" vector.\n */\n this.up = cfg.up ? cfg.up.slice() : null;\n\n /**\n * Optional projection type for {@link Camera#projection}, used when this Annotation is associated with a {@link Camera} position.\n *\n * Undefined by default.\n *\n * @type {String} The projection type - \"perspective\" or \"ortho\"..\n */\n this.projection = cfg.projection;\n }\n\n /**\n * @private\n */\n _buildHTML() {\n if (!this._markerExternal) {\n if (this._marker) {\n this._container.removeChild(this._marker);\n this._marker = null;\n }\n let markerHTML = this._markerHTML || \"

\"; // Make marker\n if (utils.isArray(markerHTML)) {\n markerHTML = markerHTML.join(\"\");\n }\n markerHTML = this._renderTemplate(markerHTML.trim());\n const markerFragment = document.createRange().createContextualFragment(markerHTML);\n this._marker = markerFragment.firstChild;\n this._container.appendChild(this._marker);\n this._marker.style.visibility = this._markerShown ? \"visible\" : \"hidden\";\n this._marker.addEventListener(\"click\", () => {\n this.plugin.fire(\"markerClicked\", this);\n });\n this._marker.addEventListener(\"mouseenter\", () => {\n this.plugin.fire(\"markerMouseEnter\", this);\n });\n this._marker.addEventListener(\"mouseleave\", () => {\n this.plugin.fire(\"markerMouseLeave\", this);\n });\n this._marker.addEventListener('wheel', (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new WheelEvent('wheel', event));\n });\n }\n if (!this._labelExternal) {\n if (this._label) {\n this._container.removeChild(this._label);\n this._label = null;\n }\n let labelHTML = this._labelHTML || \"

\"; // Make label\n if (utils.isArray(labelHTML)) {\n labelHTML = labelHTML.join(\"\");\n }\n labelHTML = this._renderTemplate(labelHTML.trim());\n const labelFragment = document.createRange().createContextualFragment(labelHTML);\n this._label = labelFragment.firstChild;\n this._container.appendChild(this._label);\n this._label.style.visibility = (this._markerShown && this._labelShown) ? \"visible\" : \"hidden\";\n this._label.addEventListener('wheel', (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new WheelEvent('wheel', event));\n });\n }\n }\n\n /**\n * @private\n */\n _updatePosition() {\n const boundary = this.scene.canvas.boundary;\n const left = boundary[0];\n const top = boundary[1];\n const canvasPos = this.canvasPos;\n this._marker.style.left = (Math.floor(left + canvasPos[0]) - 12) + \"px\";\n this._marker.style.top = (Math.floor(top + canvasPos[1]) - 12) + \"px\";\n this._marker.style[\"z-index\"] = 90005 + Math.floor(this._viewPos[2]) + 1;\n const offsetX = 20;\n const offsetY = -17;\n this._label.style.left = 20 + Math.floor(left + canvasPos[0] + offsetX) + \"px\";\n this._label.style.top = Math.floor(top + canvasPos[1] + offsetY) + \"px\";\n this._label.style[\"z-index\"] = 90005 + Math.floor(this._viewPos[2]) + 1;\n }\n\n /**\n * @private\n */\n _renderTemplate(template) {\n for (var key in this._values) {\n if (this._values.hasOwnProperty(key)) {\n const value = this._values[key];\n template = template.replace(new RegExp('{{' + key + '}}', 'g'), value);\n }\n }\n return template;\n }\n\n /**\n * Sets whether or not to show this Annotation's marker.\n *\n * The marker shows the Annotation's position.\n *\n * The marker is only visible when both this property and {@link Annotation#visible} are ````true````.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @param {Boolean} shown Whether to show the marker.\n */\n setMarkerShown(shown) {\n shown = !!shown;\n if (this._markerShown === shown) {\n return;\n }\n this._markerShown = shown;\n this._visibilityDirty = true;\n }\n\n /**\n * Gets whether or not to show this Annotation's marker.\n *\n * The marker shows the Annotation's position.\n *\n * The marker is only visible when both this property and {@link Annotation#visible} are ````true````.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @returns {Boolean} Whether to show the marker.\n */\n getMarkerShown() {\n return this._markerShown;\n }\n\n /**\n * Sets whether or not to show this Annotation's label.\n *\n * The label is only visible when both this property and {@link Annotation#visible} are ````true````.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @param {Boolean} shown Whether to show the label.\n */\n setLabelShown(shown) {\n shown = !!shown;\n if (this._labelShown === shown) {\n return;\n }\n this._labelShown = shown;\n this._visibilityDirty = true;\n }\n\n /**\n * Gets whether or not to show this Annotation's label.\n *\n * The label is only visible when both this property and {@link Annotation#visible} are ````true````.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @returns {Boolean} Whether to show the label.\n */\n getLabelShown() {\n return this._labelShown;\n }\n\n /**\n * Sets the value of a field within the HTML templates for either the Annotation's marker or label.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @param {String} key Identifies the field.\n * @param {String} value The field's value.\n */\n setField(key, value) {\n this._values[key] = value || \"\";\n this._htmlDirty = true;\n }\n\n /**\n * Gets the value of a field within the HTML templates for either the Annotation's marker or label.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @param {String} key Identifies the field.\n * @returns {String} The field's value.\n */\n getField(key) {\n return this._values[key];\n }\n\n /**\n * Sets values for multiple placeholders within the Annotation's HTML templates for marker and label.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @param {{String:(String|Number)}} values Map of field values.\n */\n setValues(values) {\n for (var key in values) {\n if (values.hasOwnProperty(key)) {\n const value = values[key];\n this.setField(key, value);\n }\n }\n }\n\n /**\n * Gets the values that were set for the placeholders within this Annotation's HTML marker and label templates.\n *\n * See {@link AnnotationsPlugin} for more info.\n *\n * @RETURNS {{String:(String|Number)}} Map of field values.\n */\n getValues() {\n return this._values;\n }\n\n /**\n * Destroys this Annotation.\n *\n * You can also call {@link AnnotationsPlugin#destroyAnnotation}.\n */\n destroy() {\n if (this._marker) {\n if (!this._markerExternal) {\n this._marker.parentNode.removeChild(this._marker);\n } else {\n this._marker.removeEventListener(\"click\", this._onMouseClickedExternalMarker);\n this._marker.removeEventListener(\"mouseenter\", this._onMouseEnterExternalMarker);\n this._marker.removeEventListener(\"mouseleave\", this._onMouseLeaveExternalMarker);\n this._marker = null;\n }\n }\n if (this._label) {\n if (!this._labelExternal) {\n this._label.parentNode.removeChild(this._label);\n }\n this._label = null;\n }\n this.scene.off(this._onTick);\n super.destroy();\n }\n}\n\nconst tempVec3a$K = math.vec3();\nconst tempVec3b$z = math.vec3();\nconst tempVec3c$v = math.vec3();\n\n/**\n * {@link Viewer} plugin that creates {@link Annotation}s.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_clickFlyToPosition)\n *\n * * [[Example 1: Create annotations with mouse](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_createWithMouse)]\n * * [[Example 2: Click annotations to toggle labels](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_clickShowLabels)]\n * * [[Example 3: Hover annotations to show labels](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_hoverShowLabels)]\n * * [[Example 4: Click annotations to fly to viewpoint](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_clickFlyToPosition)]\n * * [[Example 5: Create Annotations with externally-created elements](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_externalElements)]\n *\n * ## Overview\n *\n * * An {@link Annotation} is a 3D position with a label attached.\n * * Annotations render themselves with HTML elements that float over the canvas; customize the appearance of\n * individual Annotations using HTML template; configure default appearance by setting templates on the AnnotationsPlugin.\n * * Dynamically insert data values into each Annotation's HTML templates; configure default values on the AnnotationsPlugin.\n * * Optionally configure Annotation with externally-created DOM elements for markers and labels; these override templates and data values.\n * * Optionally configure Annotations to hide themselves whenever occluded by {@link Entity}s.\n * * Optionally configure each Annotation with a position we can jump or fly the {@link Camera} to.\n *\n * ## Example 1: Loading a model and creating an annotation\n *\n * In the example below, we'll use an {@link XKTLoaderPlugin} to load a model, and an AnnotationsPlugin\n * to create an {@link Annotation} on it.\n *\n * We'll configure the AnnotationsPlugin with default HTML templates for each Annotation's position (its \"marker\") and\n * label, along with some default data values to insert into them.\n *\n * When we create our Annotation, we'll give it some specific data values to insert into the templates, overriding some of\n * the defaults we configured on the plugin. Note the correspondence between the placeholders in the templates\n * and the keys in the values map.\n *\n * We'll also configure the Annotation to hide itself whenever it's position is occluded by any {@link Entity}s (this is default behavior). The\n * {@link Scene} periodically occlusion-tests all Annotations on every 20th \"tick\" (which represents a rendered frame). We\n * can adjust that frequency via property {@link Scene#ticksPerOcclusionTest}.\n *\n * Finally, we'll query the Annotation's position occlusion/visibility status, and subscribe to change events on those properties.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_clickShowLabels)]\n *\n * ````JavaScript\n * import {Viewer, XKTLoaderPlugin,AnnotationsPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const annotations = new AnnotationsPlugin(viewer, {\n *\n * // Default HTML template for marker position\n * markerHTML: \"
{{glyph}}
\",\n *\n * // Default HTML template for label\n * labelHTML: \"
\" +\n * \"
{{title}}
{{description}}
\",\n *\n * // Default values to insert into the marker and label templates\n * values: {\n * markerBGColor: \"red\",\n * labelBGColor: \"red\",\n * glyph: \"X\",\n * title: \"Untitled\",\n * description: \"No description\"\n * }\n * });\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/geometry.xkt\"\n * });\n *\n * model.on(\"loaded\", () => {\n *\n * const entity = viewer.scene.meshes[\"\"];\n *\n * // Create an annotation\n * const myAnnotation1 = annotations.createAnnotation({\n *\n * id: \"myAnnotation\",\n *\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"], // Optional, associate with an Entity\n *\n * worldPos: [0, 0, 0], // 3D World-space position\n *\n * occludable: true, // Optional, default, makes Annotation invisible when occluded by Entities\n * markerShown: true, // Optional, default is true, makes position visible (when not occluded)\n * labelShown: true // Optional, default is false, makes label visible (when not occluded)\n *\n * values: { // Optional, overrides AnnotationPlugin's defaults\n * glyph: \"A\",\n * title: \"My Annotation\",\n * description: \"This is my annotation.\"\n * }\n * });\n *\n * // Listen for change of the Annotation's 3D World-space position\n *\n * myAnnotation1.on(\"worldPos\", function(worldPos) {\n * //...\n * });\n *\n * // Listen for change of the Annotation's 3D View-space position, which happens\n * // when either worldPos was updated or the Camera was moved\n *\n * myAnnotation1.on(\"viewPos\", function(viewPos) {\n * //...\n * });\n *\n * // Listen for change of the Annotation's 2D Canvas-space position, which happens\n * // when worldPos or viewPos was updated, or Camera's projection was updated\n *\n * myAnnotation1.on(\"canvasPos\", function(canvasPos) {\n * //...\n * });\n *\n * // Listen for change of Annotation visibility. The Annotation becomes invisible when it falls outside the canvas,\n * // or its position is occluded by some Entity. Note that, when not occluded, the position is only\n * // shown when Annotation#markerShown is true, and the label is only shown when Annotation#labelShown is true.\n *\n * myAnnotation1.on(\"visible\", function(visible) { // Marker visibility has changed\n * if (visible) {\n * this.log(\"Annotation is visible\");\n * } else {\n * this.log(\"Annotation is invisible\");\n * }\n * });\n *\n * // Listen for destruction of the Annotation\n *\n * myAnnotation1.on(\"destroyed\", () => {\n * //...\n * });\n * });\n * ````\n *\n * Let's query our {@link Annotation}'s current position in the World, View and Canvas coordinate systems:\n *\n * ````javascript\n * const worldPos = myAnnotation.worldPos; // [x,y,z]\n * const viewPos = myAnnotation.viewPos; // [x,y,z]\n * const canvasPos = myAnnotation.canvasPos; // [x,y]\n * ````\n *\n * We can query it's current visibility, which is ````false```` when its position is occluded by some {@link Entity}:\n *\n * ````\n * const visible = myAnnotation1.visible;\n * ````\n *\n * To listen for change events on our Annotation's position and visibility:\n *\n * ````javascript\n * // World-space position changes when we assign a new value to Annotation#worldPos\n * myAnnotation1.on(\"worldPos\", (worldPos) => {\n * //...\n * });\n *\n * // View-space position changes when either worldPos was updated or the Camera was moved\n * myAnnotation1.on(\"viewPos\", (viewPos) => {\n * //...\n * });\n *\n * // Canvas-space position changes when worldPos or viewPos was updated, or Camera's projection was updated\n * myAnnotation1.on(\"canvasPos\", (canvasPos) => {\n * //...\n * });\n *\n * // Annotation is invisible when its position falls off the canvas or is occluded by some Entity\n * myAnnotation1.on(\"visible\", (visible) => {\n * //...\n * });\n * ````\n *\n * Finally, let's dynamically update the values for a couple of placeholders in our Annotation's label:\n *\n * ```` javascript\n * myAnnotation1.setValues({\n * title: \"Here's a new title\",\n * description: \"Here's a new description\"\n * });\n * ````\n *\n *\n * ## Example 2: Creating an Annotation with a unique appearance\n *\n * Now let's create a second {@link Annotation}, this time with its own custom HTML label template, which includes\n * an image. In the Annotation's values, we'll also provide a new title and description, custom colors for the marker\n * and label, plus a URL for the image in the label template. To render its marker, the Annotation will fall back\n * on the AnnotationPlugin's default marker template.\n *\n * ````javascript\n * const myAnnotation2 = annotations.createAnnotation({\n *\n * id: \"myAnnotation2\",\n *\n * worldPos: [-0.163, 1.810, 7.977],\n *\n * occludable: true,\n * markerShown: true,\n * labelShown: true,\n *\n * // Custom label template is the same as the Annotation's, with the addition of an image element\n * labelHTML: \"
\\\n *
{{title}}
\\\n *
{{description}}
\\\n *
myImage\\\n *
\",\n *\n * // Custom template values override all the AnnotationPlugin's defaults, and includes an additional value\n * // for the image element's URL\n * values: {\n * glyph: \"A3\",\n * title: \"The West wall\",\n * description: \"Annotations can contain
custom HTML like this
image:\",\n * markerBGColor: \"green\",\n * labelBGColor: \"green\",\n * imageSrc: \"https://xeokit.io/img/docs/BIMServerLoaderPlugin/schependomlaan.png\"\n * }\n * });\n * ````\n *\n * ## Example 3: Creating an Annotation with a camera position\n *\n * We can optionally configure each {@link Annotation} with a position to fly or jump the {@link Camera} to.\n *\n * Let's create another Annotation, this time providing it with ````eye````, ````look```` and ````up```` properties\n * indicating a viewpoint on whatever it's annotating:\n *\n * ````javascript\n * const myAnnotation3 = annotations.createAnnotation({\n *\n * id: \"myAnnotation3\",\n *\n * worldPos: [-0.163, 3.810, 7.977],\n *\n * eye: [0,0,-10],\n * look: [-0.163, 3.810, 7.977],\n * up: [0,1,0];\n *\n * occludable: true,\n * markerShown: true,\n * labelShown: true,\n *\n * labelHTML: \"
\\\n *
{{title}}
\\\n *
{{description}}
\\\n *
myImage\\\n *
\",\n *\n * values: {\n * glyph: \"A3\",\n * title: \"The West wall\",\n * description: \"Annotations can contain
custom HTML like this
image:\",\n * markerBGColor: \"green\",\n * labelBGColor: \"green\",\n * imageSrc: \"https://xeokit.io/img/docs/BIMServerLoaderPlugin/schependomlaan.png\"\n * }\n * });\n * ````\n *\n * Now we can fly the {@link Camera} to the Annotation's viewpoint, like this:\n *\n * ````javascript\n * viewer.cameraFlight.flyTo(myAnnotation3);\n * ````\n *\n * Or jump the Camera, like this:\n *\n * ````javascript\n * viewer.cameraFlight.jumpTo(myAnnotation3);\n * ````\n *\n * ## Example 4: Creating an Annotation using externally-created DOM elements\n *\n * Now let's create another {@link Annotation}, this time providing it with pre-existing DOM elements for its marker\n * and label. Note that AnnotationsPlugin will ignore any ````markerHTML````, ````labelHTML````\n * or ````values```` properties when provide ````markerElementId```` or ````labelElementId````.\n *\n * ````javascript\n * const myAnnotation2 = annotations.createAnnotation({\n *\n * id: \"myAnnotation2\",\n *\n * worldPos: [-0.163, 1.810, 7.977],\n *\n * occludable: true,\n * markerShown: true,\n * labelShown: true,\n *\n * markerElementId: \"myMarkerElement\",\n * labelElementId: \"myLabelElement\"\n * });\n * ````\n *\n * ## Example 5: Creating annotations by clicking on objects\n *\n * AnnotationsPlugin makes it easy to create {@link Annotation}s on the surfaces of {@link Entity}s as we click on them.\n *\n * The {@link AnnotationsPlugin#createAnnotation} method can accept a {@link PickResult} returned\n * by {@link Scene#pick}, from which it initializes the {@link Annotation}'s {@link Annotation#worldPos} and\n * {@link Annotation#entity}. Note that this only works when {@link Scene#pick} was configured to perform a 3D\n * surface-intersection pick (see {@link Scene#pick} for more info).\n *\n * Let's now extend our example to create an Annotation wherever we click on the surface of of our model:\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/index.html#annotations_createWithMouse)]\n *\n * ````javascript\n * var i = 1; // Used to create unique Annotation IDs\n *\n * viewer.scene.input.on(\"mouseclicked\", (coords) => {\n *\n * var pickRecord = viewer.scene.pick({\n * canvasPos: coords,\n * pickSurface: true // <<------ This causes picking to find the intersection point on the entity\n * });\n *\n * if (pickRecord) {\n *\n * const annotation = annotations.createAnnotation({\n * id: \"myAnnotationOnClick\" + i,\n * pickRecord: pickRecord,\n * occludable: true, // Optional, default is true\n * markerShown: true, // Optional, default is true\n * labelShown: true, // Optional, default is true\n * values: { // HTML template values\n * glyph: \"A\" + i,\n * title: \"My annotation \" + i,\n * description: \"My description \" + i\n * },\n });\n *\n * i++;\n * }\n * });\n * ````\n *\n * Note that when the Annotation is occludable, there is potential for the {@link Annotation#worldPos} to become\n * visually embedded within the surface of its Entity when viewed from a distance. This happens as a result of limited\n * GPU accuracy GPU accuracy, especially when the near and far view-space clipping planes, specified by {@link Perspective#near}\n * and {@link Perspective#far}, or {@link Ortho#near} and {@link Perspective#far}, are far away from each other.\n *\n * To prevent this, we can offset Annotations from their Entity surfaces by an amount that we set\n * on {@link AnnotationsPlugin#surfaceOffset}:\n *\n * ````javascript\n * annotations.surfaceOffset = 0.3; // Default value\n * ````\n *\n * Annotations subsequently created with {@link AnnotationsPlugin#createAnnotation} using a {@link PickResult} will then\n * be offset by that amount.\n *\n * Another thing we can do to prevent this unwanted occlusion is keep the distance between the view-space clipping\n * planes to a minimum, which improves the accuracy of the Annotation occlusion test. In general, a good default\n * value for ````Perspective#far```` and ````Ortho#far```` is around ````2.000````.\n */\nclass AnnotationsPlugin extends Plugin {\n\n /**\n * @constructor\n * @param {Viewer} viewer The Viewer.\n * @param {Object} cfg Plugin configuration.\n * @param {String} [cfg.id=\"Annotations\"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.\n * @param {String} [cfg.markerHTML] HTML text template for Annotation markers. Defaults to ````
````. Ignored on {@link Annotation}s configured with a ````markerElementId````.\n * @param {String} [cfg.labelHTML] HTML text template for Annotation labels. Defaults to ````
````. Ignored on {@link Annotation}s configured with a ````labelElementId````.\n * @param {HTMLElement} [cfg.container] Container DOM element for markers and labels. Defaults to ````document.body````.\n * @param {{String:(String|Number)}} [cfg.values={}] Map of default values to insert into the HTML templates for the marker and label.\n * @param {Number} [cfg.surfaceOffset=0.3] The amount by which each {@link Annotation} is offset from the surface of\n * its {@link Entity} when we create the Annotation by supplying a {@link PickResult} to {@link AnnotationsPlugin#createAnnotation}.\n */\n constructor(viewer, cfg) {\n\n super(\"Annotations\", viewer);\n\n this._labelHTML = cfg.labelHTML || \"
\";\n this._markerHTML = cfg.markerHTML || \"
\";\n this._container = cfg.container || document.body;\n this._values = cfg.values || {};\n\n /**\n * The {@link Annotation}s created by {@link AnnotationsPlugin#createAnnotation}, each mapped to its {@link Annotation#id}.\n * @type {{String:Annotation}}\n */\n this.annotations = {};\n\n this.surfaceOffset = cfg.surfaceOffset;\n }\n\n /**\n * Gets the plugin's HTML container element, if any.\n * @returns {*|HTMLElement|HTMLElement}\n */\n getContainerElement() {\n return this._container;\n }\n\n /**\n * @private\n */\n send(name, value) {\n switch (name) {\n case \"clearAnnotations\":\n this.clear();\n break;\n }\n }\n\n /**\n * Sets the amount by which each {@link Annotation} is offset from the surface of its {@link Entity}, when we\n * create the Annotation by supplying a {@link PickResult} to {@link AnnotationsPlugin#createAnnotation}.\n *\n * See the class comments for more info.\n *\n * This is ````0.3```` by default.\n *\n * @param {Number} surfaceOffset The surface offset.\n */\n set surfaceOffset(surfaceOffset) {\n if (surfaceOffset === undefined || surfaceOffset === null) {\n surfaceOffset = 0.3;\n }\n this._surfaceOffset = surfaceOffset;\n }\n\n /**\n * Gets the amount by which an {@link Annotation} is offset from the surface of its {@link Entity} when\n * created by {@link AnnotationsPlugin#createAnnotation}, when we\n * create the Annotation by supplying a {@link PickResult} to {@link AnnotationsPlugin#createAnnotation}.\n *\n * This is ````0.3```` by default.\n *\n * @returns {Number} The surface offset.\n */\n get surfaceOffset() {\n return this._surfaceOffset;\n }\n\n /**\n * Creates an {@link Annotation}.\n *\n * The Annotation is then registered by {@link Annotation#id} in {@link AnnotationsPlugin#annotations}.\n *\n * @param {Object} params Annotation configuration.\n * @param {String} params.id Unique ID to assign to {@link Annotation#id}. The Annotation will be registered by this in {@link AnnotationsPlugin#annotations} and {@link Scene.components}. Must be unique among all components in the {@link Viewer}.\n * @param {String} [params.markerElementId] ID of pre-existing DOM element to render the marker. This overrides ````markerHTML```` and does not support ````values```` (data is baked into the label DOM element).\n * @param {String} [params.labelElementId] ID of pre-existing DOM element to render the label. This overrides ````labelHTML```` and does not support ````values```` (data is baked into the label DOM element).\n * @param {String} [params.markerHTML] HTML text template for the Annotation marker. Defaults to the marker HTML given to the AnnotationsPlugin constructor. Ignored if you provide ````markerElementId````.\n * @param {String} [params.labelHTML] HTML text template for the Annotation label. Defaults to the label HTML given to the AnnotationsPlugin constructor. Ignored if you provide ````labelElementId````.\n * @param {Number[]} [params.worldPos=[0,0,0]] World-space position of the Annotation marker, assigned to {@link Annotation#worldPos}.\n * @param {Entity} [params.entity] Optional {@link Entity} to associate the Annotation with. Causes {@link Annotation#visible} to be ````false```` whenever {@link Entity#visible} is also ````false````.\n * @param {PickResult} [params.pickResult] Sets the Annotation's World-space position and direction vector from the given {@link PickResult}'s {@link PickResult#worldPos} and {@link PickResult#worldNormal}, and the Annotation's Entity from {@link PickResult#entity}. Causes ````worldPos```` and ````entity```` parameters to be ignored, if they are also given.\n * @param {Boolean} [params.occludable=false] Indicates whether or not the {@link Annotation} marker and label are hidden whenever the marker occluded by {@link Entity}s in the {@link Scene}. The\n * {@link Scene} periodically occlusion-tests all Annotations on every 20th \"tick\" (which represents a rendered frame). We can adjust that frequency via property {@link Scene#ticksPerOcclusionTest}.\n * @param {{String:(String|Number)}} [params.values={}] Map of values to insert into the HTML templates for the marker and label. These will be inserted in addition to any values given to the AnnotationsPlugin constructor.\n * @param {Boolean} [params.markerShown=true] Whether to initially show the {@link Annotation} marker.\n * @param {Boolean} [params.labelShown=false] Whether to initially show the {@link Annotation} label.\n * @param {Number[]} [params.eye] Optional World-space position for {@link Camera#eye}, used when this Annotation is associated with a {@link Camera} position.\n * @param {Number[]} [params.look] Optional World-space position for {@link Camera#look}, used when this Annotation is associated with a {@link Camera} position.\n * @param {Number[]} [params.up] Optional World-space position for {@link Camera#up}, used when this Annotation is associated with a {@link Camera} position.\n * @param {String} [params.projection] Optional projection type for {@link Camera#projection}, used when this Annotation is associated with a {@link Camera} position.\n * @returns {Annotation} The new {@link Annotation}.\n */\n createAnnotation(params) {\n if (this.viewer.scene.components[params.id]) {\n this.error(\"Viewer component with this ID already exists: \" + params.id);\n delete params.id;\n }\n var worldPos;\n var entity;\n params.pickResult = params.pickResult || params.pickRecord;\n if (params.pickResult) {\n const pickResult = params.pickResult;\n if (!pickResult.worldPos || !pickResult.worldNormal) {\n this.error(\"Param 'pickResult' does not have both worldPos and worldNormal\");\n } else {\n const normalizedWorldNormal = math.normalizeVec3(pickResult.worldNormal, tempVec3a$K);\n const offsetVec = math.mulVec3Scalar(normalizedWorldNormal, this._surfaceOffset, tempVec3b$z);\n const offsetWorldPos = math.addVec3(pickResult.worldPos, offsetVec, tempVec3c$v);\n worldPos = offsetWorldPos;\n entity = pickResult.entity;\n }\n } else {\n worldPos = params.worldPos;\n entity = params.entity;\n }\n\n var markerElement = null;\n if (params.markerElementId) {\n markerElement = document.getElementById(params.markerElementId);\n if (!markerElement) {\n this.error(\"Can't find DOM element for 'markerElementId' value '\" + params.markerElementId + \"' - defaulting to internally-generated empty DIV\");\n }\n }\n\n var labelElement = null;\n if (params.labelElementId) {\n labelElement = document.getElementById(params.labelElementId);\n if (!labelElement) {\n this.error(\"Can't find DOM element for 'labelElementId' value '\" + params.labelElementId + \"' - defaulting to internally-generated empty DIV\");\n }\n }\n\n const annotation = new Annotation(this.viewer.scene, {\n id: params.id,\n plugin: this,\n entity: entity,\n worldPos: worldPos,\n container: this._container,\n markerElement: markerElement,\n labelElement: labelElement,\n markerHTML: params.markerHTML || this._markerHTML,\n labelHTML: params.labelHTML || this._labelHTML,\n occludable: params.occludable,\n values: utils.apply(params.values, utils.apply(this._values, {})),\n markerShown: params.markerShown,\n labelShown: params.labelShown,\n eye: params.eye,\n look: params.look,\n up: params.up,\n projection: params.projection,\n visible: (params.visible !== false)\n });\n this.annotations[annotation.id] = annotation;\n annotation.on(\"destroyed\", () => {\n delete this.annotations[annotation.id];\n this.fire(\"annotationDestroyed\", annotation.id);\n });\n this.fire(\"annotationCreated\", annotation.id);\n return annotation;\n }\n\n /**\n * Destroys an {@link Annotation}.\n *\n * @param {String} id ID of Annotation to destroy.\n */\n destroyAnnotation(id) {\n var annotation = this.annotations[id];\n if (!annotation) {\n this.log(\"Annotation not found: \" + id);\n return;\n }\n annotation.destroy();\n }\n\n /**\n * Destroys all {@link Annotation}s.\n */\n clear() {\n const ids = Object.keys(this.annotations);\n for (var i = 0, len = ids.length; i < len; i++) {\n this.destroyAnnotation(ids[i]);\n }\n }\n\n /**\n * Destroys this AnnotationsPlugin.\n *\n * Destroys all {@link Annotation}s first.\n */\n destroy() {\n this.clear();\n super.destroy();\n }\n}\n\nconst defaultCSS = \".sk-fading-circle {\\\n background: transparent;\\\n margin: 20px auto;\\\n width: 50px;\\\n height:50px;\\\n position: relative;\\\n }\\\n .sk-fading-circle .sk-circle {\\\n width: 120%;\\\n height: 120%;\\\n position: absolute;\\\n left: 0;\\\n top: 0;\\\n }\\\n .sk-fading-circle .sk-circle:before {\\\n content: '';\\\n display: block;\\\n margin: 0 auto;\\\n width: 15%;\\\n height: 15%;\\\n background-color: #ff8800;\\\n border-radius: 100%;\\\n -webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;\\\n animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;\\\n }\\\n .sk-fading-circle .sk-circle2 {\\\n -webkit-transform: rotate(30deg);\\\n -ms-transform: rotate(30deg);\\\n transform: rotate(30deg);\\\n }\\\n .sk-fading-circle .sk-circle3 {\\\n -webkit-transform: rotate(60deg);\\\n -ms-transform: rotate(60deg);\\\n transform: rotate(60deg);\\\n }\\\n .sk-fading-circle .sk-circle4 {\\\n -webkit-transform: rotate(90deg);\\\n -ms-transform: rotate(90deg);\\\n transform: rotate(90deg);\\\n }\\\n .sk-fading-circle .sk-circle5 {\\\n -webkit-transform: rotate(120deg);\\\n -ms-transform: rotate(120deg);\\\n transform: rotate(120deg);\\\n }\\\n .sk-fading-circle .sk-circle6 {\\\n -webkit-transform: rotate(150deg);\\\n -ms-transform: rotate(150deg);\\\n transform: rotate(150deg);\\\n }\\\n .sk-fading-circle .sk-circle7 {\\\n -webkit-transform: rotate(180deg);\\\n -ms-transform: rotate(180deg);\\\n transform: rotate(180deg);\\\n }\\\n .sk-fading-circle .sk-circle8 {\\\n -webkit-transform: rotate(210deg);\\\n -ms-transform: rotate(210deg);\\\n transform: rotate(210deg);\\\n }\\\n .sk-fading-circle .sk-circle9 {\\\n -webkit-transform: rotate(240deg);\\\n -ms-transform: rotate(240deg);\\\n transform: rotate(240deg);\\\n }\\\n .sk-fading-circle .sk-circle10 {\\\n -webkit-transform: rotate(270deg);\\\n -ms-transform: rotate(270deg);\\\n transform: rotate(270deg);\\\n }\\\n .sk-fading-circle .sk-circle11 {\\\n -webkit-transform: rotate(300deg);\\\n -ms-transform: rotate(300deg);\\\n transform: rotate(300deg);\\\n }\\\n .sk-fading-circle .sk-circle12 {\\\n -webkit-transform: rotate(330deg);\\\n -ms-transform: rotate(330deg);\\\n transform: rotate(330deg);\\\n }\\\n .sk-fading-circle .sk-circle2:before {\\\n -webkit-animation-delay: -1.1s;\\\n animation-delay: -1.1s;\\\n }\\\n .sk-fading-circle .sk-circle3:before {\\\n -webkit-animation-delay: -1s;\\\n animation-delay: -1s;\\\n }\\\n .sk-fading-circle .sk-circle4:before {\\\n -webkit-animation-delay: -0.9s;\\\n animation-delay: -0.9s;\\\n }\\\n .sk-fading-circle .sk-circle5:before {\\\n -webkit-animation-delay: -0.8s;\\\n animation-delay: -0.8s;\\\n }\\\n .sk-fading-circle .sk-circle6:before {\\\n -webkit-animation-delay: -0.7s;\\\n animation-delay: -0.7s;\\\n }\\\n .sk-fading-circle .sk-circle7:before {\\\n -webkit-animation-delay: -0.6s;\\\n animation-delay: -0.6s;\\\n }\\\n .sk-fading-circle .sk-circle8:before {\\\n -webkit-animation-delay: -0.5s;\\\n animation-delay: -0.5s;\\\n }\\\n .sk-fading-circle .sk-circle9:before {\\\n -webkit-animation-delay: -0.4s;\\\n animation-delay: -0.4s;\\\n }\\\n .sk-fading-circle .sk-circle10:before {\\\n -webkit-animation-delay: -0.3s;\\\n animation-delay: -0.3s;\\\n }\\\n .sk-fading-circle .sk-circle11:before {\\\n -webkit-animation-delay: -0.2s;\\\n animation-delay: -0.2s;\\\n }\\\n .sk-fading-circle .sk-circle12:before {\\\n -webkit-animation-delay: -0.1s;\\\n animation-delay: -0.1s;\\\n }\\\n @-webkit-keyframes sk-circleFadeDelay {\\\n 0%, 39%, 100% { opacity: 0; }\\\n 40% { opacity: 1; }\\\n }\\\n @keyframes sk-circleFadeDelay {\\\n 0%, 39%, 100% { opacity: 0; }\\\n 40% { opacity: 1; }\\\n }\";\n\n/**\n * @desc Displays a progress animation at the center of its {@link Canvas} while things are loading or otherwise busy.\n *\n *\n * * Located at {@link Canvas#spinner}.\n * * Automatically shown while things are loading, however may also be shown by application code wanting to indicate busyness.\n * * {@link Spinner#processes} holds the count of active processes. As a process starts, it increments {@link Spinner#processes}, then decrements it on completion or failure.\n * * A Spinner is only visible while {@link Spinner#processes} is greater than zero.\n *\n * ````javascript\n * var spinner = viewer.scene.canvas.spinner;\n *\n * // Increment count of busy processes represented by the spinner;\n * // assuming the count was zero, this now shows the spinner\n * spinner.processes++;\n *\n * // Increment the count again, by some other process; spinner already visible, now requires two decrements\n * // before it becomes invisible again\n * spinner.processes++;\n *\n * // Decrement the count; count still greater than zero, so spinner remains visible\n * spinner.process--;\n *\n * // Decrement the count; count now zero, so spinner becomes invisible\n * spinner.process--;\n * ````\n */\nclass Spinner extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Spinner\";\n }\n\n /**\n @private\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._canvas = cfg.canvas;\n this._element = null;\n this._isCustom = false; // True when the element is custom HTML\n\n if (cfg.elementId) { // Custom spinner element supplied\n this._element = document.getElementById(cfg.elementId);\n if (!this._element) {\n this.error(\"Can't find given Spinner HTML element: '\" + cfg.elementId + \"' - will automatically create default element\");\n } else {\n this._adjustPosition();\n }\n }\n\n if (!this._element) {\n this._createDefaultSpinner();\n }\n\n this.processes = 0;\n }\n\n /** @private */\n _createDefaultSpinner() {\n this._injectDefaultCSS();\n const element = document.createElement('div');\n const style = element.style;\n style[\"z-index\"] = \"9000\";\n style.position = \"absolute\";\n element.innerHTML = '
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
\\\n
';\n this._canvas.parentElement.appendChild(element);\n this._element = element;\n this._isCustom = false;\n this._adjustPosition();\n }\n\n /**\n * @private\n */\n _injectDefaultCSS() {\n const elementId = \"xeokit-spinner-css\";\n if (document.getElementById(elementId)) {\n return;\n }\n const defaultCSSNode = document.createElement('style');\n defaultCSSNode.innerHTML = defaultCSS;\n defaultCSSNode.id = elementId;\n document.body.appendChild(defaultCSSNode);\n }\n\n /**\n * @private\n */\n _adjustPosition() { // (Re)positions spinner DIV over the center of the canvas - called by Canvas\n if (this._isCustom) {\n return;\n }\n const canvas = this._canvas;\n const element = this._element;\n const style = element.style;\n style[\"left\"] = (canvas.offsetLeft + (canvas.clientWidth * 0.5) - (element.clientWidth * 0.5)) + \"px\";\n style[\"top\"] = (canvas.offsetTop + (canvas.clientHeight * 0.5) - (element.clientHeight * 0.5)) + \"px\";\n }\n\n /**\n * Sets the number of processes this Spinner represents.\n *\n * The Spinner is visible while this property is greater than zero.\n *\n * Increment this property whenever you commence some process during which you want the Spinner to be visible, then decrement it again when the process is complete.\n *\n * Clamps to zero if you attempt to set to to a negative value.\n *\n * Fires a {@link Spinner#processes:event} event on change.\n\n * Default value is ````0````.\n *\n * @param {Number} value New processes count.\n */\n set processes(value) {\n value = value || 0;\n if (this._processes === value) {\n return;\n }\n if (value < 0) {\n return;\n }\n const prevValue = this._processes;\n this._processes = value;\n const element = this._element;\n if (element) {\n element.style[\"visibility\"] = (this._processes > 0) ? \"visible\" : \"hidden\";\n }\n /**\n Fired whenever this Spinner's {@link Spinner#visible} property changes.\n\n @event processes\n @param value The property's new value\n */\n this.fire(\"processes\", this._processes);\n if (this._processes === 0 && this._processes !== prevValue) {\n /**\n Fired whenever this Spinner's {@link Spinner#visible} property becomes zero.\n\n @event zeroProcesses\n */\n this.fire(\"zeroProcesses\", this._processes);\n }\n }\n\n /**\n * Gets the number of processes this Spinner represents.\n *\n * The Spinner is visible while this property is greater than zero.\n *\n * @returns {Number} Current processes count.\n */\n get processes() {\n return this._processes;\n }\n\n _destroy() {\n if (this._element && (!this._isCustom)) {\n this._element.parentNode.removeChild(this._element);\n this._element = null;\n }\n const styleElement = document.getElementById(\"xeokit-spinner-css\");\n if (styleElement) {\n styleElement.parentNode.removeChild(styleElement);\n }\n }\n}\n\nconst WEBGL_CONTEXT_NAMES = [\n \"webgl2\",\n \"experimental-webgl\",\n \"webkit-3d\",\n \"moz-webgl\",\n \"moz-glweb20\"\n];\n\n/**\n * @desc Manages its {@link Scene}'s HTML canvas.\n *\n * * Provides the HTML canvas element in {@link Canvas#canvas}.\n * * Has a {@link Spinner}, provided at {@link Canvas#spinner}, which manages the loading progress indicator.\n */\nclass Canvas extends Component {\n\n /**\n * @constructor\n * @private\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._backgroundColor = math.vec3([\n cfg.backgroundColor ? cfg.backgroundColor[0] : 1,\n cfg.backgroundColor ? cfg.backgroundColor[1] : 1,\n cfg.backgroundColor ? cfg.backgroundColor[2] : 1]);\n this._backgroundColorFromAmbientLight = !!cfg.backgroundColorFromAmbientLight;\n\n /**\n * The HTML canvas.\n *\n * @property canvas\n * @type {HTMLCanvasElement}\n * @final\n */\n this.canvas = cfg.canvas;\n\n /**\n * The WebGL rendering context.\n *\n * @property gl\n * @type {WebGLRenderingContext}\n * @final\n */\n this.gl = null;\n\n /**\n * True when WebGL 2 support is enabled.\n *\n * @property webgl2\n * @type {Boolean}\n * @final\n */\n this.webgl2 = false; // Will set true in _initWebGL if WebGL is requested and we succeed in getting it.\n\n /**\n * Indicates if this Canvas is transparent.\n *\n * @property transparent\n * @type {Boolean}\n * @default {false}\n * @final\n */\n this.transparent = !!cfg.transparent;\n\n /**\n * Attributes for the WebGL context\n *\n * @type {{}|*}\n */\n this.contextAttr = cfg.contextAttr || {};\n this.contextAttr.alpha = this.transparent;\n\n this.contextAttr.preserveDrawingBuffer = !!this.contextAttr.preserveDrawingBuffer;\n this.contextAttr.stencil = false;\n this.contextAttr.premultipliedAlpha = (!!this.contextAttr.premultipliedAlpha); // False by default: https://github.com/xeokit/xeokit-sdk/issues/251\n this.contextAttr.antialias = (this.contextAttr.antialias !== false);\n\n // If the canvas uses css styles to specify the sizes make sure the basic\n // width and height attributes match or the WebGL context will use 300 x 150\n\n this.resolutionScale = cfg.resolutionScale;\n\n this.canvas.width = Math.round(this.canvas.clientWidth * this._resolutionScale);\n this.canvas.height = Math.round(this.canvas.clientHeight * this._resolutionScale);\n\n /**\n * Boundary of the Canvas in absolute browser window coordinates.\n *\n * ### Usage:\n *\n * ````javascript\n * var boundary = myScene.canvas.boundary;\n *\n * var xmin = boundary[0];\n * var ymin = boundary[1];\n * var width = boundary[2];\n * var height = boundary[3];\n * ````\n *\n * @property boundary\n * @type {Number[]}\n * @final\n */\n this.boundary = [\n this.canvas.offsetLeft, this.canvas.offsetTop,\n this.canvas.clientWidth, this.canvas.clientHeight\n ];\n\n // Get WebGL context\n\n this._initWebGL(cfg);\n\n // Bind context loss and recovery handlers\n\n const self = this;\n\n this.canvas.addEventListener(\"webglcontextlost\", this._webglcontextlostListener = function (event) {\n console.time(\"webglcontextrestored\");\n self.scene._webglContextLost();\n /**\n * Fired whenever the WebGL context has been lost\n * @event webglcontextlost\n */\n self.fire(\"webglcontextlost\");\n event.preventDefault();\n },\n false);\n\n this.canvas.addEventListener(\"webglcontextrestored\", this._webglcontextrestoredListener = function (event) {\n self._initWebGL();\n if (self.gl) {\n self.scene._webglContextRestored(self.gl);\n /**\n * Fired whenever the WebGL context has been restored again after having previously being lost\n * @event webglContextRestored\n * @param value The WebGL context object\n */\n self.fire(\"webglcontextrestored\", self.gl);\n event.preventDefault();\n }\n console.timeEnd(\"webglcontextrestored\");\n },\n false);\n\n // Attach to resize events on the canvas\n let dirtyBoundary = true; // make sure we publish the 1st boundary event\n\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (entry.contentBoxSize) {\n dirtyBoundary = true;\n }\n }\n });\n\n resizeObserver.observe(this.canvas);\n\n // Publish canvas size and position changes on each scene tick\n this._tick = this.scene.on(\"tick\", () => {\n // Only publish if the canvas bounds changed\n if (!dirtyBoundary) {\n return;\n }\n\n dirtyBoundary = false;\n\n // Set the real size of the canvas (the drawable w*h)\n self.canvas.width = Math.round(self.canvas.clientWidth * self._resolutionScale);\n self.canvas.height = Math.round(self.canvas.clientHeight * self._resolutionScale);\n\n // Publish the boundary change\n self.boundary[0] = self.canvas.offsetLeft;\n self.boundary[1] = self.canvas.offsetTop;\n self.boundary[2] = self.canvas.clientWidth;\n self.boundary[3] = self.canvas.clientHeight;\n\n self.fire(\"boundary\", self.boundary);\n });\n\n this._spinner = new Spinner(this.scene, {\n canvas: this.canvas,\n elementId: cfg.spinnerElementId\n });\n }\n\n /**\n @private\n */\n get type() {\n return \"Canvas\";\n }\n\n /**\n * Gets whether the canvas clear color will be derived from {@link AmbientLight} or {@link Canvas#backgroundColor}\n * when {@link Canvas#transparent} is ```true```.\n *\n * When {@link Canvas#transparent} is ```true``` and this is ````true````, then the canvas clear color will\n * be taken from the {@link Scene}'s ambient light color.\n *\n * When {@link Canvas#transparent} is ```true``` and this is ````false````, then the canvas clear color will\n * be taken from {@link Canvas#backgroundColor}.\n *\n * Default value is ````true````.\n *\n * @type {Boolean}\n */\n get backgroundColorFromAmbientLight() {\n return this._backgroundColorFromAmbientLight;\n }\n\n /**\n * Sets if the canvas background color is derived from an {@link AmbientLight}.\n *\n * This only has effect when the canvas is not transparent. When not enabled, the background color\n * will be the canvas element's HTML/CSS background color.\n *\n * Default value is ````true````.\n *\n * @type {Boolean}\n */\n set backgroundColorFromAmbientLight(backgroundColorFromAmbientLight) {\n this._backgroundColorFromAmbientLight = (backgroundColorFromAmbientLight !== false);\n this.glRedraw();\n }\n\n /**\n * Gets the canvas clear color.\n *\n * Default value is ````[1, 1, 1]````.\n *\n * @type {Number[]}\n */\n get backgroundColor() {\n return this._backgroundColor;\n }\n\n /**\n * Sets the canvas clear color.\n *\n * Default value is ````[1, 1, 1]````.\n *\n * @type {Number[]}\n */\n set backgroundColor(value) {\n if (value) {\n this._backgroundColor[0] = value[0];\n this._backgroundColor[1] = value[1];\n this._backgroundColor[2] = value[2];\n } else {\n this._backgroundColor[0] = 1.0;\n this._backgroundColor[1] = 1.0;\n this._backgroundColor[2] = 1.0;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the scale of the canvas back buffer relative to the CSS-defined size of the canvas.\n *\n * This is a common way to trade off rendering quality for speed. If the canvas size is defined in CSS, then\n * setting this to a value between ````[0..1]```` (eg ````0.5````) will render into a smaller back buffer, giving\n * a performance boost.\n *\n * @returns {*|number} The resolution scale.\n */\n get resolutionScale() {\n return this._resolutionScale;\n }\n\n /**\n * Sets the scale of the canvas back buffer relative to the CSS-defined size of the canvas.\n *\n * This is a common way to trade off rendering quality for speed. If the canvas size is defined in CSS, then\n * setting this to a value between ````[0..1]```` (eg ````0.5````) will render into a smaller back buffer, giving\n * a performance boost.\n *\n * @param {*|number} resolutionScale The resolution scale.\n */\n set resolutionScale(resolutionScale) {\n resolutionScale = resolutionScale || 1.0;\n if (resolutionScale === this._resolutionScale) {\n return;\n }\n this._resolutionScale = resolutionScale;\n const canvas = this.canvas;\n canvas.width = Math.round(canvas.clientWidth * this._resolutionScale);\n canvas.height = Math.round(canvas.clientHeight * this._resolutionScale);\n this.glRedraw();\n }\n\n /**\n * The busy {@link Spinner} for this Canvas.\n *\n * @property spinner\n * @type Spinner\n * @final\n */\n get spinner() {\n return this._spinner;\n }\n\n /**\n * Creates a default canvas in the DOM.\n * @private\n */\n _createCanvas() {\n\n const canvasId = \"xeokit-canvas-\" + math.createUUID();\n const body = document.getElementsByTagName(\"body\")[0];\n const div = document.createElement('div');\n\n const style = div.style;\n style.height = \"100%\";\n style.width = \"100%\";\n style.padding = \"0\";\n style.margin = \"0\";\n style.background = \"rgba(0,0,0,0);\";\n style.float = \"left\";\n style.left = \"0\";\n style.top = \"0\";\n style.position = \"absolute\";\n style.opacity = \"1.0\";\n style[\"z-index\"] = \"-10000\";\n\n div.innerHTML += '';\n\n body.appendChild(div);\n\n this.canvas = document.getElementById(canvasId);\n }\n\n _getElementXY(e) {\n let x = 0, y = 0;\n while (e) {\n x += (e.offsetLeft - e.scrollLeft);\n y += (e.offsetTop - e.scrollTop);\n e = e.offsetParent;\n }\n return {x: x, y: y};\n }\n\n /**\n * Initialises the WebGL context\n * @private\n */\n _initWebGL() {\n\n // Default context attribute values\n\n if (!this.gl) {\n for (let i = 0; !this.gl && i < WEBGL_CONTEXT_NAMES.length; i++) {\n try {\n this.gl = this.canvas.getContext(WEBGL_CONTEXT_NAMES[i], this.contextAttr);\n } catch (e) { // Try with next context name\n }\n }\n }\n\n if (!this.gl) {\n\n this.error('Failed to get a WebGL context');\n\n /**\n * Fired whenever the canvas failed to get a WebGL context, which probably means that WebGL\n * is either unsupported or has been disabled.\n * @event webglContextFailed\n */\n this.fire(\"webglContextFailed\", true, true);\n }\n\n // data-textures: avoid to re-bind same texture\n {\n const gl = this.gl;\n\n let lastTextureUnit = \"__\";\n\n let originalActiveTexture = gl.activeTexture;\n\n gl.activeTexture = function (arg1) {\n if (lastTextureUnit === arg1) {\n return;\n }\n\n lastTextureUnit = arg1;\n\n originalActiveTexture.call (this, arg1);\n };\n\n let lastBindTexture = {};\n\n let originalBindTexture = gl.bindTexture;\n\n gl.bindTexture = function (arg1, arg2) {\n if (lastBindTexture[lastTextureUnit] === arg2)\n {\n return;\n }\n\n lastBindTexture[lastTextureUnit] = arg2;\n\n originalBindTexture.call (this, arg1, arg2);\n };\n\n // setInterval (\n // () => {\n // console.log (`${avoidedRebinds} avoided texture binds/sec`);\n // avoidedRebinds = 0;\n // },\n // 1000\n // );\n }\n\n if (this.gl) {\n // Setup extension (if necessary) and hints for fragment shader derivative functions\n if (this.webgl2) {\n this.gl.hint(this.gl.FRAGMENT_SHADER_DERIVATIVE_HINT, this.gl.FASTEST);\n\n // data-textures: not using standard-derivatives\n if (!(this.gl instanceof WebGL2RenderingContext)) ;\n }\n }\n }\n\n /**\n * @private\n * @deprecated\n */\n getSnapshot(params) {\n throw \"Canvas#getSnapshot() has been replaced by Viewer#getSnapshot() - use that method instead.\";\n }\n\n /**\n * Reads colors of pixels from the last rendered frame.\n *\n * Call this method like this:\n *\n * ````JavaScript\n *\n * // Ignore transparent pixels (default is false)\n * var opaqueOnly = true;\n *\n * var colors = new Float32Array(8);\n *\n * viewer.scene.canvas.readPixels([ 100, 22, 12, 33 ], colors, 2, opaqueOnly);\n * ````\n *\n * Then the r,g,b components of the colors will be set to the colors at those pixels.\n *\n * @param {Number[]} pixels\n * @param {Number[]} colors\n * @param {Number} size\n * @param {Boolean} opaqueOnly\n */\n readPixels(pixels, colors, size, opaqueOnly) {\n return this.scene._renderer.readPixels(pixels, colors, size, opaqueOnly);\n }\n\n /**\n * Simulates lost WebGL context.\n */\n loseWebGLContext() {\n if (this.canvas.loseContext) {\n this.canvas.loseContext();\n }\n }\n\n destroy() {\n this.scene.off(this._tick);\n this._spinner._destroy();\n // Memory leak avoidance\n this.canvas.removeEventListener(\"webglcontextlost\", this._webglcontextlostListener);\n this.canvas.removeEventListener(\"webglcontextrestored\", this._webglcontextrestoredListener);\n this.gl = null;\n super.destroy();\n }\n}\n\n/**\n * @desc Provides rendering context to {@link Drawable\"}s as xeokit renders them for a frame.\n *\n * Also creates RTC viewing and picking matrices, caching and reusing matrices within each frame.\n *\n * @private\n */\nclass FrameContext {\n\n constructor(scene) {\n\n this._scene = scene;\n\n this._matPool = [];\n this._matPoolNextFreeIndex = 0;\n\n this._rtcViewMats = {};\n this._rtcPickViewMats = {};\n\n this.reset();\n }\n\n /**\n * Called by the renderer before each frame.\n * @private\n */\n reset() {\n\n this._matPoolNextFreeIndex = 0;\n this._rtcViewMats = {};\n this._rtcPickViewMats = {};\n\n /**\n * The WebGL rendering context.\n * @type {WebGLRenderingContext}\n */\n this.gl = this._scene.canvas.gl;\n\n /**\n * ID of the last {@link WebGLProgram} that was bound during the current frame.\n * @property lastProgramId\n * @type {Number}\n */\n this.lastProgramId = null;\n\n /**\n * Whether to render a physically-based representation for triangle surfaces.\n *\n * When ````false````, we'll render them with a fast vertex-shaded Gouraud-shaded representation, which\n * is great for zillions of objects.\n *\n * When ````true````, we'll render them at a better visual quality, using smooth, per-fragment shading\n * and a more realistic lighting model.\n *\n * @property quality\n * @default false\n * @type {Boolean}\n */\n this.pbrEnabled = false;\n\n /**\n * Whether to render color textures for triangle surfaces.\n *\n * @property quality\n * @default false\n * @type {Boolean}\n */\n this.colorTextureEnabled = false;\n\n /**\n * Whether SAO is currently enabled during the current frame.\n * @property withSAO\n * @default false\n * @type {Boolean}\n */\n this.withSAO = false;\n\n /**\n * Whether backfaces are currently enabled during the current frame.\n * @property backfaces\n * @default false\n * @type {Boolean}\n */\n this.backfaces = false;\n\n /**\n * The vertex winding order for what we currently consider to be a backface during current\n * frame: true == \"cw\", false == \"ccw\".\n * @property frontFace\n * @default true\n * @type {Boolean}\n */\n this.frontface = true;\n\n /**\n * The next available texture unit to bind a {@link Texture} to.\n * @defauilt 0\n * @property textureUnit\n * @type {number}\n */\n this.textureUnit = 0;\n\n /**\n * Performance statistic that counts how many times the renderer has called ````gl.drawElements()```` has been\n * called so far within the current frame.\n * @default 0\n * @property drawElements\n * @type {number}\n */\n this.drawElements = 0;\n\n /**\n * Performance statistic that counts how many times ````gl.drawArrays()```` has been called so far within\n * the current frame.\n * @default 0\n * @property drawArrays\n * @type {number}\n */\n this.drawArrays = 0;\n\n /**\n * Performance statistic that counts how many times ````gl.useProgram()```` has been called so far within\n * the current frame.\n * @default 0\n * @property useProgram\n * @type {number}\n */\n this.useProgram = 0;\n\n /**\n * Statistic that counts how many times ````gl.bindTexture()```` has been called so far within the current frame.\n * @default 0\n * @property bindTexture\n * @type {number}\n */\n this.bindTexture = 0;\n\n /**\n * Counts how many times the renderer has called ````gl.bindArray()```` so far within the current frame.\n * @defaulr 0\n * @property bindArray\n * @type {number}\n */\n this.bindArray = 0;\n\n /**\n * Indicates which pass the renderer is currently rendering.\n *\n * See {@link Scene/passes:property\"}}Scene#passes{{/crossLink}}, which configures how many passes we render\n * per frame, which typically set to ````2```` when rendering a stereo view.\n *\n * @property pass\n * @type {number}\n */\n this.pass = 0;\n\n /**\n * The 4x4 viewing transform matrix the renderer is currently using when rendering castsShadows.\n *\n * This sets the viewpoint to look from the point of view of each {@link DirLight}\n * or {@link PointLight} that casts a shadow.\n *\n * @property shadowViewMatrix\n * @type {Number[]}\n */\n this.shadowViewMatrix = null;\n\n /**\n * The 4x4 viewing projection matrix the renderer is currently using when rendering shadows.\n *\n * @property shadowProjMatrix\n * @type {Number[]}\n */\n this.shadowProjMatrix = null;\n\n /**\n * The 4x4 viewing transform matrix the renderer is currently using when rendering a ray-pick.\n *\n * This sets the viewpoint to look along the ray given to {@link Scene/pick:method\"}}Scene#pick(){{/crossLink}}\n * when picking with a ray.\n *\n * @property pickViewMatrix\n * @type {Number[]}\n */\n this.pickViewMatrix = null;\n\n /**\n * The 4x4 orthographic projection transform matrix the renderer is currently using when rendering a ray-pick.\n *\n * @property pickProjMatrix\n * @type {Number[]}\n */\n this.pickProjMatrix = null;\n\n /**\n * Distance to the near clipping plane when rendering depth fragments for GPU-accelerated 3D picking.\n *\n * @property pickZNear\n * @type {Number|*}\n */\n this.pickZNear = 0.01;\n\n /**\n * Distance to the far clipping plane when rendering depth fragments for GPU-accelerated 3D picking.\n *\n * @property pickZFar\n * @type {Number|*}\n */\n this.pickZFar = 5000;\n\n /**\n * Whether or not the renderer is currently picking invisible objects.\n *\n * @property pickInvisible\n * @type {Number}\n */\n this.pickInvisible = false;\n\n /**\n * Used to draw only requested elements / indices.\n *\n * @property pickElementsCount\n * @type {Number}\n */\n this.pickElementsCount = null;\n\n /**\n * Used to draw only requested elements / indices.\n *\n * @property pickElementsOffset\n * @type {Number}\n */\n this.pickElementsOffset = null;\n\n /** The current line width.\n *\n * @property lineWidth\n * @type Number\n */\n this.lineWidth = 1;\n\n /**\n * Collects info from SceneModel.drawSnapInit and SceneModel.drawSnap,\n * which is then used in Renderer to determine snap-picking results.\n *\n * @type {{}}\n */\n this.snapPickLayerParams = {};\n\n /**\n * Collects info from SceneModel.drawSnapInit and SceneModel.drawSnap,\n * which is then used in Renderer to determine snap-picking results.\n * @type {number}\n */\n this.snapPickLayerNumber = 0;\n\n /**\n * Collects info from SceneModel.drawSnapInit and SceneModel.drawSnap,\n * which is then used in Renderer to determine snap-picking results.\n * @type {Number[]}\n */\n this.snapPickCoordinateScale = math.vec3();\n\n /**\n * Collects info from SceneModel.drawSnapInit and SceneModel.drawSnap,\n * which is then used in Renderer to determine snap-picking results.\n * @type {Number[]}\n */\n this.snapPickOrigin = math.vec3();\n }\n\n /**\n * Get View matrix for the given RTC center.\n */\n getRTCViewMatrix(originHash, origin) {\n let rtcViewMat = this._rtcViewMats[originHash];\n if (!rtcViewMat) {\n rtcViewMat = this._getNewMat();\n createRTCViewMat(this._scene.camera.viewMatrix, origin, rtcViewMat);\n this._rtcViewMats[originHash] = rtcViewMat;\n }\n return rtcViewMat;\n }\n\n /**\n * Get picking View RTC matrix for the given RTC center.\n */\n getRTCPickViewMatrix(originHash, origin) {\n let rtcPickViewMat = this._rtcPickViewMats[originHash];\n if (!rtcPickViewMat) {\n rtcPickViewMat = this._getNewMat();\n const pickViewMat = this.pickViewMatrix || this._scene.camera.viewMatrix;\n createRTCViewMat(pickViewMat, origin, rtcPickViewMat);\n this._rtcPickViewMats[originHash] = rtcPickViewMat;\n }\n return rtcPickViewMat;\n }\n\n _getNewMat() {\n let mat = this._matPool[this._matPoolNextFreeIndex];\n if (!mat) {\n mat = math.mat4();\n this._matPool[this._matPoolNextFreeIndex] = mat;\n }\n this._matPoolNextFreeIndex++;\n return mat;\n }\n}\n\n/**\n * @private\n * @type {{WEBGL: boolean, SUPPORTED_EXTENSIONS: {}}}\n */\nconst WEBGL_INFO = {\n WEBGL: false,\n SUPPORTED_EXTENSIONS: {}\n};\n\nconst canvas = document.createElement(\"canvas\");\n\nif (canvas) {\n\n const gl = canvas.getContext(\"webgl\", {antialias: true}) || canvas.getContext(\"experimental-webgl\", {antialias: true});\n\n WEBGL_INFO.WEBGL = !!gl;\n\n if (WEBGL_INFO.WEBGL) {\n WEBGL_INFO.ANTIALIAS = gl.getContextAttributes().antialias;\n if (gl.getShaderPrecisionFormat) {\n if (gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision > 0) {\n WEBGL_INFO.FS_MAX_FLOAT_PRECISION = \"highp\";\n } else if (gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT).precision > 0) {\n WEBGL_INFO.FS_MAX_FLOAT_PRECISION = \"mediump\";\n } else {\n WEBGL_INFO.FS_MAX_FLOAT_PRECISION = \"lowp\";\n }\n } else {\n WEBGL_INFO.FS_MAX_FLOAT_PRECISION = \"mediump\";\n }\n WEBGL_INFO.DEPTH_BUFFER_BITS = gl.getParameter(gl.DEPTH_BITS);\n WEBGL_INFO.MAX_TEXTURE_SIZE = gl.getParameter(gl.MAX_TEXTURE_SIZE);\n WEBGL_INFO.MAX_CUBE_MAP_SIZE = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);\n WEBGL_INFO.MAX_RENDERBUFFER_SIZE = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);\n WEBGL_INFO.MAX_TEXTURE_UNITS = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);\n WEBGL_INFO.MAX_TEXTURE_IMAGE_UNITS = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);\n WEBGL_INFO.MAX_VERTEX_ATTRIBS = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);\n WEBGL_INFO.MAX_VERTEX_UNIFORM_VECTORS = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);\n WEBGL_INFO.MAX_FRAGMENT_UNIFORM_VECTORS = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);\n WEBGL_INFO.MAX_VARYING_VECTORS = gl.getParameter(gl.MAX_VARYING_VECTORS);\n gl.getSupportedExtensions().forEach(function (ext) {\n WEBGL_INFO.SUPPORTED_EXTENSIONS[ext] = true;\n });\n }\n}\n\n/**\n * @desc Pick result returned by {@link Scene#pick}.\n *\n */\nclass PickResult {\n\n /**\n * @private\n */\n constructor() {\n\n /**\n * Picked entity.\n * Null when no entity was picked.\n * @property entity\n * @type {Entity|*}\n */\n this.entity = null;\n\n /**\n * Type of primitive that was picked - usually \"triangle\".\n * Null when no primitive was picked.\n * @property primitive\n * @type {String}\n */\n this.primitive = null;\n\n /**\n * Index of primitive that was picked.\n * -1 when no entity was picked.\n * @property primIndex\n * @type {number}\n */\n this.primIndex = -1;\n\n /**\n * True when the picked surface position is full precision.\n * When false, the picked surface position should be regarded as approximate.\n * Full-precision surface picking is performed with the {@link Scene#pick} method's ````pickSurfacePrecision```` option.\n * @property pickSurfacePrecision\n * @type {Boolean}\n */\n this.pickSurfacePrecision = false;\n\n /**\n * True when picked from touch input, else false when from mouse input.\n * @type {boolean}\n */\n this.touchInput = false;\n\n /**\n * True when snapped to nearest edge.\n * @type {boolean}\n */\n this.snappedToEdge = false;\n\n /**\n * True when snapped to nearest vertex.\n * @type {boolean}\n */\n this.snappedToVertex = false;\n\n this._origin = new Float64Array([0, 0, 0]);\n this._direction = new Float64Array([0, 0, 0]);\n this._indices = new Int32Array(3);\n this._localPos = new Float64Array([0, 0, 0]);\n this._worldPos = new Float64Array([0, 0, 0]);\n this._viewPos = new Float64Array([0, 0, 0]);\n this._canvasPos = new Int16Array([0, 0]);\n this._snappedCanvasPos = new Int16Array([0, 0]);\n this._bary = new Float64Array([0, 0, 0]);\n this._worldNormal = new Float64Array([0, 0, 0]);\n this._uv = new Float64Array([0, 0]);\n\n this.reset();\n }\n\n /**\n * Canvas pick coordinates.\n * @property canvasPos\n * @type {Number[]}\n */\n get canvasPos() {\n return this._gotCanvasPos ? this._canvasPos : null;\n }\n\n /**\n * @private\n * @param value\n */\n set canvasPos(value) {\n if (value) {\n this._canvasPos[0] = value[0];\n this._canvasPos[1] = value[1];\n this._gotCanvasPos = true;\n } else {\n this._gotCanvasPos = false;\n }\n }\n\n /**\n * World-space 3D ray origin when raypicked.\n * @property origin\n * @type {Number[]}\n */\n get origin() {\n return this._gotOrigin ? this._origin : null;\n }\n\n /**\n * @private\n * @param value\n */\n set origin(value) {\n if (value) {\n this._origin[0] = value[0];\n this._origin[1] = value[1];\n this._origin[2] = value[2];\n this._gotOrigin = true;\n } else {\n this._gotOrigin = false;\n }\n }\n\n /**\n * World-space 3D ray direction when raypicked.\n * @property direction\n * @type {Number[]}\n */\n get direction() {\n return this._gotDirection ? this._direction : null;\n }\n\n /**\n * @private\n * @param value\n */\n set direction(value) {\n if (value) {\n this._direction[0] = value[0];\n this._direction[1] = value[1];\n this._direction[2] = value[2];\n this._gotDirection = true;\n } else {\n this._gotDirection = false;\n }\n }\n \n /**\n * Picked triangle's vertex indices.\n * @property indices\n * @type {Int32Array}\n */\n get indices() {\n return this.entity && this._gotIndices ? this._indices : null;\n }\n\n /**\n * @private\n * @param value\n */\n set indices(value) {\n if (value) {\n this._indices[0] = value[0];\n this._indices[1] = value[1];\n this._indices[2] = value[2];\n this._gotIndices = true;\n } else {\n this._gotIndices = false;\n }\n }\n\n /**\n * Picked Local-space point.\n * @property localPos\n * @type {Number[]}\n */\n get localPos() {\n return this.entity && this._gotLocalPos ? this._localPos : null;\n }\n\n /**\n * @private\n * @param value\n */\n set localPos(value) {\n if (value) {\n this._localPos[0] = value[0];\n this._localPos[1] = value[1];\n this._localPos[2] = value[2];\n this._gotLocalPos = true;\n } else {\n this._gotLocalPos = false;\n }\n }\n\n /**\n * Canvas cursor coordinates, snapped when snap picking, otherwise same as {@link PickResult#pointerPos}.\n * @property snappedCanvasPos\n * @type {Number[]}\n */\n get snappedCanvasPos() {\n return this._gotSnappedCanvasPos ? this._snappedCanvasPos : null;\n }\n\n /**\n * @private\n * @param value\n */\n set snappedCanvasPos(value) {\n if (value) {\n this._snappedCanvasPos[0] = value[0];\n this._snappedCanvasPos[1] = value[1];\n this._gotSnappedCanvasPos = true;\n } else {\n this._gotSnappedCanvasPos = false;\n }\n }\n\n /**\n * Picked World-space point.\n * @property worldPos\n * @type {Number[]}\n */\n get worldPos() {\n return this._gotWorldPos ? this._worldPos : null;\n }\n\n /**\n * @private\n * @param value\n */\n set worldPos(value) {\n if (value) {\n this._worldPos[0] = value[0];\n this._worldPos[1] = value[1];\n this._worldPos[2] = value[2];\n this._gotWorldPos = true;\n } else {\n this._gotWorldPos = false;\n }\n }\n\n /**\n * Picked View-space point.\n * @property viewPos\n * @type {Number[]}\n */\n get viewPos() {\n return this.entity && this._gotViewPos ? this._viewPos : null;\n }\n\n /**\n * @private\n * @param value\n */\n set viewPos(value) {\n if (value) {\n this._viewPos[0] = value[0];\n this._viewPos[1] = value[1];\n this._viewPos[2] = value[2];\n this._gotViewPos = true;\n } else {\n this._gotViewPos = false;\n }\n }\n\n /**\n * Barycentric coordinate within picked triangle.\n * @property bary\n * @type {Number[]}\n */\n get bary() {\n return this.entity && this._gotBary ? this._bary : null;\n }\n\n /**\n * @private\n * @param value\n */\n set bary(value) {\n if (value) {\n this._bary[0] = value[0];\n this._bary[1] = value[1];\n this._bary[2] = value[2];\n this._gotBary = true;\n } else {\n this._gotBary = false;\n }\n }\n\n /**\n * Normal vector at picked position on surface.\n * @property worldNormal\n * @type {Number[]}\n */\n get worldNormal() {\n return this.entity && this._gotWorldNormal ? this._worldNormal : null;\n }\n\n /**\n * @private\n * @param value\n */\n set worldNormal(value) {\n if (value) {\n this._worldNormal[0] = value[0];\n this._worldNormal[1] = value[1];\n this._worldNormal[2] = value[2];\n this._gotWorldNormal = true;\n } else {\n this._gotWorldNormal = false;\n }\n }\n\n /**\n * UV coordinates at picked position on surface.\n * @property uv\n * @type {Number[]}\n */\n get uv() {\n return this.entity && this._gotUV ? this._uv : null;\n }\n\n /**\n * @private\n * @param value\n */\n set uv(value) {\n if (value) {\n this._uv[0] = value[0];\n this._uv[1] = value[1];\n this._gotUV = true;\n } else {\n this._gotUV = false;\n }\n }\n\n /**\n * True if snapped to edge or vertex.\n * @returns {boolean}\n */\n get snapped() {\n return this.snappedToEdge || this.snappedToVertex;\n }\n\n /**\n * @private\n */\n reset() {\n this.entity = null;\n this.primIndex = -1;\n this.primitive = null;\n this.pickSurfacePrecision = false;\n this._gotCanvasPos = false;\n this._gotSnappedCanvasPos = false;\n this._gotOrigin = false;\n this._gotDirection = false;\n this._gotIndices = false;\n this._gotLocalPos = false;\n this._gotWorldPos = false;\n this._gotViewPos = false;\n this._gotBary = false;\n this._gotWorldNormal = false;\n this._gotUV = false;\n this.touchInput = false;\n this.snappedToEdge = false;\n this.snappedToVertex = false;\n }\n}\n\n/**\n * @desc Represents a vertex or fragment stage within a {@link Program}.\n * @private\n */\nclass Shader {\n\n constructor(gl, type, source) {\n\n this.allocated = false;\n this.compiled = false;\n this.handle = gl.createShader(type);\n\n if (!this.handle) {\n this.errors = [\n \"Failed to allocate\"\n ];\n return;\n }\n\n this.allocated = true;\n\n gl.shaderSource(this.handle, source);\n gl.compileShader(this.handle);\n\n this.compiled = gl.getShaderParameter(this.handle, gl.COMPILE_STATUS);\n\n if (!this.compiled) {\n\n if (!gl.isContextLost()) { // Handled explicitly elsewhere, so won't re-handle here\n\n const lines = source.split(\"\\n\");\n const numberedLines = [];\n for (let i = 0; i < lines.length; i++) {\n numberedLines.push((i + 1) + \": \" + lines[i] + \"\\n\");\n }\n this.errors = [];\n this.errors.push(\"\");\n this.errors.push(gl.getShaderInfoLog(this.handle));\n this.errors = this.errors.concat(numberedLines.join(\"\"));\n }\n }\n }\n\n destroy() {\n\n }\n}\n\n/**\n * @desc A low-level component that represents a WebGL Sampler.\n * @private\n */\nclass Sampler {\n\n constructor(gl, location) {\n this.bindTexture = function (texture, unit) {\n if (texture.bind(unit)) {\n gl.uniform1i(location, unit);\n return true;\n }\n return false;\n };\n }\n}\n\n/**\n * @desc Represents a WebGL vertex attribute buffer (VBO).\n * @private\n * @param gl {WebGLRenderingContext} The WebGL rendering context.\n */\nclass Attribute {\n\n constructor(gl, location) {\n this._gl = gl;\n this.location = location;\n }\n\n bindArrayBuffer(arrayBuf) {\n if (!arrayBuf) {\n return;\n }\n arrayBuf.bind();\n this._gl.enableVertexAttribArray(this.location);\n this._gl.vertexAttribPointer(this.location, arrayBuf.itemSize, arrayBuf.itemType, arrayBuf.normalized, arrayBuf.stride, arrayBuf.offset);\n }\n}\n\nconst ids$4 = new Map$1({});\n\nfunction joinSansComments(srcLines) {\n const src = [];\n let line;\n let n;\n for (let i = 0, len = srcLines.length; i < len; i++) {\n line = srcLines[i];\n n = line.indexOf(\"/\");\n if (n > 0) {\n if (line.charAt(n + 1) === \"/\") {\n line = line.substring(0, n);\n }\n }\n src.push(line);\n }\n return src.join(\"\\n\");\n}\n\nfunction logErrors(errors) {\n console.error(errors.join(\"\\n\"));\n}\n\n/**\n * @desc Represents a WebGL program.\n * @private\n */\nclass Program {\n\n constructor(gl, shaderSource) {\n this.id = ids$4.addItem({});\n this.source = shaderSource;\n this.init(gl);\n }\n\n init(gl) {\n this.gl = gl;\n this.allocated = false;\n this.compiled = false;\n this.linked = false;\n this.validated = false;\n this.errors = null;\n this.uniforms = {};\n this.samplers = {};\n this.attributes = {};\n this._vertexShader = new Shader(gl, gl.VERTEX_SHADER, joinSansComments(this.source.vertex));\n this._fragmentShader = new Shader(gl, gl.FRAGMENT_SHADER, joinSansComments(this.source.fragment));\n if (!this._vertexShader.allocated) {\n this.errors = [\"Vertex shader failed to allocate\"].concat(this._vertexShader.errors);\n logErrors(this.errors);\n return;\n }\n if (!this._fragmentShader.allocated) {\n this.errors = [\"Fragment shader failed to allocate\"].concat(this._fragmentShader.errors);\n logErrors(this.errors);\n return;\n }\n this.allocated = true;\n if (!this._vertexShader.compiled) {\n this.errors = [\"Vertex shader failed to compile\"].concat(this._vertexShader.errors);\n logErrors(this.errors);\n return;\n }\n if (!this._fragmentShader.compiled) {\n this.errors = [\"Fragment shader failed to compile\"].concat(this._fragmentShader.errors);\n logErrors(this.errors);\n return;\n }\n this.compiled = true;\n let a;\n let i;\n let u;\n let uName;\n let location;\n this.handle = gl.createProgram();\n if (!this.handle) {\n this.errors = [\"Failed to allocate program\"];\n return;\n }\n gl.attachShader(this.handle, this._vertexShader.handle);\n gl.attachShader(this.handle, this._fragmentShader.handle);\n gl.linkProgram(this.handle);\n this.linked = gl.getProgramParameter(this.handle, gl.LINK_STATUS);\n // HACK: Disable validation temporarily\n // Perhaps we should defer validation until render-time, when the program has values set for all inputs?\n this.validated = true;\n if (!this.linked || !this.validated) {\n this.errors = [];\n this.errors.push(\"\");\n this.errors.push(gl.getProgramInfoLog(this.handle));\n this.errors.push(\"\\nVertex shader:\\n\");\n this.errors = this.errors.concat(this.source.vertex);\n this.errors.push(\"\\nFragment shader:\\n\");\n this.errors = this.errors.concat(this.source.fragment);\n logErrors(this.errors);\n return;\n }\n const numUniforms = gl.getProgramParameter(this.handle, gl.ACTIVE_UNIFORMS);\n for (i = 0; i < numUniforms; ++i) {\n u = gl.getActiveUniform(this.handle, i);\n if (u) {\n uName = u.name;\n if (uName[uName.length - 1] === \"\\u0000\") {\n uName = uName.substr(0, uName.length - 1);\n }\n location = gl.getUniformLocation(this.handle, uName);\n if ((u.type === gl.SAMPLER_2D) || (u.type === gl.SAMPLER_CUBE) || (u.type === 35682)) {\n this.samplers[uName] = new Sampler(gl, location);\n } else if (gl instanceof WebGL2RenderingContext && (u.type === gl.UNSIGNED_INT_SAMPLER_2D || u.type === gl.INT_SAMPLER_2D)) {\n this.samplers[uName] = new Sampler(gl, location);\n } else {\n this.uniforms[uName] = location;\n }\n }\n }\n const numAttribs = gl.getProgramParameter(this.handle, gl.ACTIVE_ATTRIBUTES);\n for (i = 0; i < numAttribs; i++) {\n a = gl.getActiveAttrib(this.handle, i);\n if (a) {\n location = gl.getAttribLocation(this.handle, a.name);\n this.attributes[a.name] = new Attribute(gl, location);\n }\n }\n this.allocated = true;\n }\n\n bind() {\n if (!this.allocated) {\n return;\n }\n this.gl.useProgram(this.handle);\n }\n\n getLocation(name) {\n if (!this.allocated) {\n return;\n }\n return this.uniforms[name];\n }\n\n getAttribute(name) {\n if (!this.allocated) {\n return;\n }\n return this.attributes[name];\n }\n\n bindTexture(name, texture, unit) {\n if (!this.allocated) {\n return false;\n }\n const sampler = this.samplers[name];\n if (sampler) {\n return sampler.bindTexture(texture, unit);\n } else {\n return false;\n }\n }\n\n destroy() {\n if (!this.allocated) {\n return;\n }\n ids$4.removeItem(this.id);\n this.gl.deleteProgram(this.handle);\n this.gl.deleteShader(this._vertexShader.handle);\n this.gl.deleteShader(this._fragmentShader.handle);\n this.handle = null;\n this.attributes = null;\n this.uniforms = null;\n this.samplers = null;\n this.allocated = false;\n }\n}\n\n/**\n * @desc Represents a WebGL ArrayBuffer.\n *\n * @private\n */\nclass ArrayBuf {\n\n constructor(gl, type, data, numItems, itemSize, usage, normalized, stride, offset) {\n\n this._gl = gl;\n this.type = type;\n this.allocated = false;\n\n switch (data.constructor) {\n\n case Uint8Array:\n this.itemType = gl.UNSIGNED_BYTE;\n this.itemByteSize = 1;\n break;\n\n case Int8Array:\n this.itemType = gl.BYTE;\n this.itemByteSize = 1;\n break;\n\n case Uint16Array:\n this.itemType = gl.UNSIGNED_SHORT;\n this.itemByteSize = 2;\n break;\n\n case Int16Array:\n this.itemType = gl.SHORT;\n this.itemByteSize = 2;\n break;\n\n case Uint32Array:\n this.itemType = gl.UNSIGNED_INT;\n this.itemByteSize = 4;\n break;\n\n case Int32Array:\n this.itemType = gl.INT;\n this.itemByteSize = 4;\n break;\n\n default:\n this.itemType = gl.FLOAT;\n this.itemByteSize = 4;\n }\n\n this.usage = usage;\n this.length = 0;\n this.dataLength = numItems;\n this.numItems = 0;\n this.itemSize = itemSize;\n this.normalized = !!normalized;\n this.stride = stride || 0;\n this.offset = offset || 0;\n\n this._allocate(data);\n }\n\n _allocate(data) {\n this.allocated = false;\n this._handle = this._gl.createBuffer();\n if (!this._handle) {\n throw \"Failed to allocate WebGL ArrayBuffer\";\n }\n if (this._handle) {\n this._gl.bindBuffer(this.type, this._handle);\n this._gl.bufferData(this.type, data.length > this.dataLength ? data.slice(0, this.dataLength) : data, this.usage);\n this._gl.bindBuffer(this.type, null);\n this.length = data.length;\n this.numItems = this.length / this.itemSize;\n this.allocated = true;\n }\n }\n\n setData(data, offset) {\n if (!this.allocated) {\n return;\n }\n if (data.length + (offset || 0) > this.length) { // Needs reallocation\n this.destroy();\n this._allocate(data);\n } else { // No reallocation needed\n this._gl.bindBuffer(this.type, this._handle);\n if (offset || offset === 0) {\n this._gl.bufferSubData(this.type, offset * this.itemByteSize, data);\n } else {\n this._gl.bufferData(this.type, data, this.usage);\n }\n this._gl.bindBuffer(this.type, null);\n }\n }\n\n bind() {\n if (!this.allocated) {\n return;\n }\n this._gl.bindBuffer(this.type, this._handle);\n }\n\n unbind() {\n if (!this.allocated) {\n return;\n }\n this._gl.bindBuffer(this.type, null);\n }\n\n destroy() {\n if (!this.allocated) {\n return;\n }\n this._gl.deleteBuffer(this._handle);\n this._handle = null;\n this.allocated = false;\n }\n}\n\n/**\n * @private\n */\nclass OcclusionLayer {\n\n constructor(scene, origin) {\n\n this.scene = scene;\n this.aabb = math.AABB3();\n this.origin = math.vec3(origin);\n this.originHash = this.origin.join();\n this.numMarkers = 0;\n this.markers = {};\n this.markerList = []; // Ordered array of Markers\n this.markerIndices = {}; // ID map of Marker indices in _markerList\n this.positions = []; // Packed array of World-space marker positions\n this.indices = []; // Indices corresponding to array above\n this.positionsBuf = null;\n this.lenPositionsBuf = 0;\n this.indicesBuf = null;\n this.sectionPlanesActive = [];\n this.culledBySectionPlanes = false;\n this.occlusionTestList = []; // List of\n this.lenOcclusionTestList = 0;\n this.pixels = [];\n this.aabbDirty = false;\n this.markerListDirty = false;\n this.positionsDirty = true;\n this.occlusionTestListDirty = false;\n }\n\n addMarker(marker) {\n this.markers[marker.id] = marker;\n this.markerListDirty = true;\n this.numMarkers++;\n }\n\n markerWorldPosUpdated(marker) {\n if (!this.markers[marker.id]) { // Not added\n return;\n }\n const i = this.markerIndices[marker.id];\n this.positions[i * 3 + 0] = marker.worldPos[0];\n this.positions[i * 3 + 1] = marker.worldPos[1];\n this.positions[i * 3 + 2] = marker.worldPos[2];\n this.positionsDirty = true; // TODO: avoid reallocating VBO each time\n }\n\n removeMarker(marker) {\n delete this.markers[marker.id];\n this.markerListDirty = true;\n this.numMarkers--;\n }\n\n update() {\n if (this.markerListDirty) {\n this._buildMarkerList();\n this.markerListDirty = false;\n this.positionsDirty = true;\n this.occlusionTestListDirty = true;\n }\n if (this.positionsDirty) { ////////////// TODO: Don't rebuild this when positions change, very wasteful\n this._buildPositions();\n this.positionsDirty = false;\n this.aabbDirty = true;\n this.vbosDirty = true;\n }\n if (this.aabbDirty) {\n this._buildAABB();\n this.aabbDirty = false;\n }\n if (this.vbosDirty) {\n this._buildVBOs();\n this.vbosDirty = false;\n }\n if (this.occlusionTestListDirty) {\n this._buildOcclusionTestList();\n }\n this._updateActiveSectionPlanes();\n }\n\n _buildMarkerList() {\n this.numMarkers = 0;\n for (var id in this.markers) {\n if (this.markers.hasOwnProperty(id)) {\n this.markerList[this.numMarkers] = this.markers[id];\n this.markerIndices[id] = this.numMarkers;\n this.numMarkers++;\n }\n }\n this.markerList.length = this.numMarkers;\n }\n\n _buildPositions() {\n let j = 0;\n for (let i = 0; i < this.numMarkers; i++) {\n if (this.markerList[i]) {\n const marker = this.markerList[i];\n const worldPos = marker.worldPos;\n this.positions[j++] = worldPos[0];\n this.positions[j++] = worldPos[1];\n this.positions[j++] = worldPos[2];\n this.indices[i] = i;\n }\n }\n this.positions.length = this.numMarkers * 3;\n this.indices.length = this.numMarkers;\n }\n\n _buildAABB() {\n const aabb = this.aabb;\n math.collapseAABB3(aabb);\n math.expandAABB3Points3(aabb, this.positions);\n const origin = this.origin;\n aabb[0] += origin[0];\n aabb[1] += origin[1];\n aabb[2] += origin[2];\n aabb[3] += origin[0];\n aabb[4] += origin[1];\n aabb[5] += origin[2];\n }\n\n _buildVBOs() {\n if (this.positionsBuf) {\n if (this.lenPositionsBuf === this.positions.length) { // Just updating buffer elements, don't need to reallocate\n this.positionsBuf.setData(this.positions); // Indices don't need updating\n return;\n }\n this.positionsBuf.destroy();\n this.positionsBuf = null;\n this.indicesBuf.destroy();\n this.indicesBuf = null;\n }\n const gl = this.scene.canvas.gl;\n const lenPositions = this.numMarkers * 3;\n const lenIndices = this.numMarkers;\n this.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this.positions), lenPositions, 3, gl.STATIC_DRAW);\n this.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), lenIndices, 1, gl.STATIC_DRAW);\n this.lenPositionsBuf = this.positions.length;\n }\n\n _buildOcclusionTestList() {\n const canvas = this.scene.canvas;\n const near = this.scene.camera.perspective.near; // Assume near enough to ortho near\n const boundary = canvas.boundary;\n const canvasWidth = boundary[2];\n const canvasHeight = boundary[3];\n let lenPixels = 0;\n this.lenOcclusionTestList = 0;\n for (let i = 0; i < this.numMarkers; i++) {\n const marker = this.markerList[i];\n const viewPos = marker.viewPos;\n if (viewPos[2] > -near) { // Clipped by near plane\n marker._setVisible(false);\n continue;\n }\n const canvasPos = marker.canvasPos;\n const canvasX = canvasPos[0];\n const canvasY = canvasPos[1];\n if ((canvasX + 10) < 0 || (canvasY + 10) < 0 || (canvasX - 10) > canvasWidth || (canvasY - 10) > canvasHeight) {\n marker._setVisible(false);\n continue;\n }\n if (marker.entity && !marker.entity.visible) {\n marker._setVisible(false);\n continue;\n }\n if (marker.occludable) {\n this.occlusionTestList[this.lenOcclusionTestList++] = marker;\n this.pixels[lenPixels++] = canvasX;\n this.pixels[lenPixels++] = canvasY;\n continue;\n }\n marker._setVisible(true);\n }\n }\n\n _updateActiveSectionPlanes() {\n const sectionPlanes = this.scene._sectionPlanesState.sectionPlanes;\n const numSectionPlanes = sectionPlanes.length;\n if (numSectionPlanes > 0) {\n for (let i = 0; i < numSectionPlanes; i++) {\n const sectionPlane = sectionPlanes[i];\n if (!sectionPlane.active) {\n this.sectionPlanesActive[i] = false;\n } else {\n const intersect = math.planeAABB3Intersect(sectionPlane.dir, sectionPlane.dist, this.aabb);\n const outside = (intersect === -1);\n if (outside) {\n this.culledBySectionPlanes = true;\n return;\n }\n const intersecting = (intersect === 0);\n this.sectionPlanesActive[i] = intersecting;\n }\n }\n }\n this.culledBySectionPlanes = false;\n }\n\n destroy() {\n this.markers = {};\n this.markerList.length = 0;\n if (this.positionsBuf) {\n this.positionsBuf.destroy();\n }\n if (this.indicesBuf) {\n this.indicesBuf.destroy();\n }\n }\n}\n\nconst MARKER_COLOR = math.vec3([1.0, 0.0, 0.0]);\nconst POINT_SIZE = 20;\nconst MARKER_SPRITE_CLIPZ_OFFSET = -0.001; // Amount that we offset sprite clip Z coords to raise them from surfaces\n\nconst tempVec3a$J = math.vec3();\n\n/**\n * Manages occlusion testing. Private member of a Renderer.\n * @private\n */\nclass OcclusionTester {\n\n constructor(scene, renderBufferManager) {\n\n this._scene = scene;\n\n this._renderBufferManager = renderBufferManager;\n\n this._occlusionLayers = {};\n this._occlusionLayersList = [];\n this._occlusionLayersListDirty = false;\n\n this._shaderSource = null;\n this._program = null;\n\n this._shaderSourceHash = null;\n\n this._shaderSourceDirty = true; // Need to build shader source code ?\n this._programDirty = false; // Need to build shader program ?\n\n this._markersToOcclusionLayersMap = {};\n\n this._onCameraViewMatrix = scene.camera.on(\"viewMatrix\", () => {\n this._occlusionTestListDirty = true;\n });\n\n this._onCameraProjMatrix = scene.camera.on(\"projMatrix\", () => {\n this._occlusionTestListDirty = true;\n });\n\n this._onCanvasBoundary = scene.canvas.on(\"boundary\", () => {\n this._occlusionTestListDirty = true;\n });\n }\n\n /**\n * Adds a Marker for occlusion testing.\n * @param marker\n */\n addMarker(marker) {\n const originHash = marker.origin.join();\n let occlusionLayer = this._occlusionLayers[originHash];\n if (!occlusionLayer) {\n occlusionLayer = new OcclusionLayer(this._scene, marker.origin);\n this._occlusionLayers[occlusionLayer.originHash] = occlusionLayer;\n this._occlusionLayersListDirty = true;\n }\n occlusionLayer.addMarker(marker);\n this._markersToOcclusionLayersMap[marker.id] = occlusionLayer;\n this._occlusionTestListDirty = true;\n }\n\n /**\n * Notifies OcclusionTester that a Marker has updated its World-space position.\n * @param marker\n */\n markerWorldPosUpdated(marker) {\n const occlusionLayer = this._markersToOcclusionLayersMap[marker.id];\n if (!occlusionLayer) {\n return;\n }\n const originHash = marker.origin.join();\n if (originHash !== occlusionLayer.originHash) {\n if (occlusionLayer.numMarkers === 1) {\n occlusionLayer.destroy();\n delete this._occlusionLayers[occlusionLayer.originHash];\n this._occlusionLayersListDirty = true;\n } else {\n occlusionLayer.removeMarker(marker);\n }\n let newOcclusionLayer = this._occlusionLayers[originHash];\n if (!newOcclusionLayer) {\n newOcclusionLayer = new OcclusionLayer(this._scene, marker.origin);\n this._occlusionLayers[originHash] = occlusionLayer;\n this._occlusionLayersListDirty = true;\n }\n newOcclusionLayer.addMarker(marker);\n this._markersToOcclusionLayersMap[marker.id] = newOcclusionLayer;\n } else {\n occlusionLayer.markerWorldPosUpdated(marker);\n }\n }\n\n /**\n * Removes a Marker from occlusion testing.\n * @param marker\n */\n removeMarker(marker) {\n const originHash = marker.origin.join();\n let occlusionLayer = this._occlusionLayers[originHash];\n if (!occlusionLayer) {\n return;\n }\n if (occlusionLayer.numMarkers === 1) {\n occlusionLayer.destroy();\n delete this._occlusionLayers[occlusionLayer.originHash];\n this._occlusionLayersListDirty = true;\n } else {\n occlusionLayer.removeMarker(marker);\n }\n delete this._markersToOcclusionLayersMap[marker.id];\n }\n\n /**\n * Returns true if an occlusion test is needed.\n *\n * @returns {Boolean}\n */\n get needOcclusionTest() {\n return this._occlusionTestListDirty;\n }\n\n /**\n * Binds the render buffer. After calling this, the caller then renders object silhouettes to the render buffer,\n * then calls drawMarkers() and doOcclusionTest().\n */\n bindRenderBuf() {\n\n const shaderSourceHash = [this._scene.canvas.canvas.id, this._scene._sectionPlanesState.getHash()].join(\";\");\n\n if (shaderSourceHash !== this._shaderSourceHash) {\n this._shaderSourceHash = shaderSourceHash;\n this._shaderSourceDirty = true;\n }\n\n if (this._shaderSourceDirty) {\n this._buildShaderSource();\n this._shaderSourceDirty = false;\n this._programDirty = true;\n }\n\n if (this._programDirty) {\n this._buildProgram();\n this._programDirty = false;\n this._occlusionTestListDirty = true;\n }\n\n if (this._occlusionLayersListDirty) {\n this._buildOcclusionLayersList();\n this._occlusionLayersListDirty = false;\n }\n\n if (this._occlusionTestListDirty) {\n for (let i = 0, len = this._occlusionLayersList.length; i < len; i++) {\n const occlusionLayer = this._occlusionLayersList[i];\n occlusionLayer.occlusionTestListDirty = true;\n }\n this._occlusionTestListDirty = false;\n }\n\n {\n this._readPixelBuf = this._renderBufferManager.getRenderBuffer(\"occlusionReadPix\");\n this._readPixelBuf.bind();\n this._readPixelBuf.clear();\n }\n }\n\n _buildOcclusionLayersList() {\n let numOcclusionLayers = 0;\n for (let originHash in this._occlusionLayers) {\n if (this._occlusionLayers.hasOwnProperty(originHash)) {\n this._occlusionLayersList[numOcclusionLayers++] = this._occlusionLayers[originHash];\n }\n }\n this._occlusionLayersList.length = numOcclusionLayers;\n }\n\n _buildShaderSource() {\n this._shaderSource = {\n vertex: this._buildVertexShaderSource(),\n fragment: this._buildFragmentShaderSource()\n };\n }\n\n _buildVertexShaderSource() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.sectionPlanes.length > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// OcclusionTester vertex shader\");\n \n src.push(\"in vec3 position;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 worldPosition = vec4(position, 1.0); \");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition;\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n }\n src.push(\" vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\" gl_PointSize = \" + POINT_SIZE + \".0;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n } else {\n src.push(\"clipPos.z += \" + MARKER_SPRITE_CLIPZ_OFFSET + \";\");\n }\n src.push(\" gl_Position = clipPos;\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShaderSource() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.sectionPlanes.length > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// OcclusionTester fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n for (let i = 0; i < sectionPlanesState.sectionPlanes.length; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.sectionPlanes.length; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vec4(1.0, 0.0, 0.0, 1.0); \");\n src.push(\"}\");\n return src;\n }\n\n _buildProgram() {\n if (this._program) {\n this._program.destroy();\n }\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const sectionPlanesState = scene._sectionPlanesState;\n this._program = new Program(gl, this._shaderSource);\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n const sectionPlanes = sectionPlanesState.sectionPlanes;\n for (let i = 0, len = sectionPlanes.length; i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._aPosition = program.getAttribute(\"position\");\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n }\n\n /**\n * Draws {@link Marker}s to the render buffer.\n */\n drawMarkers() {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const sectionPlanesState = scene._sectionPlanesState;\n const camera = scene.camera;\n const project = scene.camera.project;\n\n program.bind();\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera._project._state.matrix);\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n for (let i = 0, len = this._occlusionLayersList.length; i < len; i++) {\n\n const occlusionLayer = this._occlusionLayersList[i];\n\n occlusionLayer.update();\n\n if (occlusionLayer.culledBySectionPlanes) {\n continue;\n }\n\n const origin = occlusionLayer.origin;\n\n gl.uniformMatrix4fv(this._uViewMatrix, false, createRTCViewMat(camera.viewMatrix, origin));\n\n const numSectionPlanes = sectionPlanesState.sectionPlanes.length;\n if (numSectionPlanes > 0) {\n const sectionPlanes = sectionPlanesState.sectionPlanes;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n const active = occlusionLayer.sectionPlanesActive[sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n gl.uniform3fv(sectionPlaneUniforms.pos, getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$J));\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n }\n }\n }\n\n this._aPosition.bindArrayBuffer(occlusionLayer.positionsBuf);\n\n const indicesBuf = occlusionLayer.indicesBuf;\n indicesBuf.bind();\n gl.drawElements(gl.POINTS, indicesBuf.numItems, indicesBuf.itemType, 0);\n }\n }\n\n /**\n * Sets visibilities of {@link Marker}s according to whether or not they are obscured by anything in the render buffer.\n */\n doOcclusionTest() {\n\n {\n\n const resolutionScale = this._scene.canvas.resolutionScale;\n\n const markerR = MARKER_COLOR[0] * 255;\n const markerG = MARKER_COLOR[1] * 255;\n const markerB = MARKER_COLOR[2] * 255;\n\n for (let i = 0, len = this._occlusionLayersList.length; i < len; i++) {\n\n const occlusionLayer = this._occlusionLayersList[i];\n\n for (let i = 0; i < occlusionLayer.lenOcclusionTestList; i++) {\n\n const marker = occlusionLayer.occlusionTestList[i];\n const j = i * 2;\n const color = this._readPixelBuf.read(Math.round(occlusionLayer.pixels[j] * resolutionScale), Math.round(occlusionLayer.pixels[j + 1] * resolutionScale));\n const visible = (color[0] === markerR) && (color[1] === markerG) && (color[2] === markerB);\n\n marker._setVisible(visible);\n }\n }\n }\n }\n\n /**\n * Unbinds render buffer.\n */\n unbindRenderBuf() {\n {\n this._readPixelBuf.unbind();\n }\n }\n\n /**\n * Destroys this OcclusionTester.\n */\n destroy() {\n if (this.destroyed) {\n return;\n }\n for (let i = 0, len = this._occlusionLayersList.length; i < len; i++) {\n const occlusionLayer = this._occlusionLayersList[i];\n occlusionLayer.destroy();\n }\n\n if (this._program) {\n this._program.destroy();\n }\n\n this._scene.camera.off(this._onCameraViewMatrix);\n this._scene.camera.off(this._onCameraProjMatrix);\n this._scene.canvas.off(this._onCanvasBoundary);\n this.destroyed = true;\n }\n}\n\nconst tempVec2 = math.vec2();\n\n/**\n * SAO implementation inspired from previous SAO work in THREE.js by ludobaka / ludobaka.github.io and bhouston\n * @private\n */\nclass SAOOcclusionRenderer {\n\n constructor(scene) {\n\n this._scene = scene;\n\n this._numSamples = null;\n\n // The program\n\n this._program = null;\n this._programError = false;\n\n // Variable locations\n\n this._aPosition = null;\n this._aUV = null;\n\n this._uDepthTexture = \"uDepthTexture\";\n\n this._uCameraNear = null;\n this._uCameraFar = null;\n this._uCameraProjectionMatrix = null;\n this._uCameraInverseProjectionMatrix = null;\n\n this._uScale = null;\n this._uIntensity = null;\n this._uBias = null;\n this._uKernelRadius = null;\n this._uMinResolution = null;\n this._uRandomSeed = null;\n\n // VBOs\n\n this._uvBuf = null;\n this._positionsBuf = null;\n this._indicesBuf = null;\n }\n\n render(depthRenderBuffer) {\n\n this._build();\n\n if (this._programError) {\n return;\n }\n\n if (!this._getInverseProjectMat) { // HACK: scene.camera not defined until render time\n this._getInverseProjectMat = (() => {\n let projMatDirty = true;\n this._scene.camera.on(\"projMatrix\", function () {\n projMatDirty = true;\n });\n const inverseProjectMat = math.mat4();\n return () => {\n if (projMatDirty) {\n math.inverseMat4(scene.camera.projMatrix, inverseProjectMat);\n }\n return inverseProjectMat;\n }\n })();\n }\n\n const gl = this._scene.canvas.gl;\n const program = this._program;\n const scene = this._scene;\n const sao = scene.sao;\n const viewportWidth = gl.drawingBufferWidth;\n const viewportHeight = gl.drawingBufferHeight;\n const projectState = scene.camera.project._state;\n const near = projectState.near;\n const far = projectState.far;\n const projectionMatrix = projectState.matrix;\n const inverseProjectionMatrix = this._getInverseProjectMat();\n const randomSeed = Math.random();\n const perspective = (scene.camera.projection === \"perspective\");\n\n tempVec2[0] = viewportWidth;\n tempVec2[1] = viewportHeight;\n\n gl.viewport(0, 0, viewportWidth, viewportHeight);\n gl.clearColor(0, 0, 0, 1);\n gl.disable(gl.DEPTH_TEST);\n gl.disable(gl.BLEND);\n gl.frontFace(gl.CCW);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n program.bind();\n\n gl.uniform1f(this._uCameraNear, near);\n gl.uniform1f(this._uCameraFar, far);\n\n gl.uniformMatrix4fv(this._uCameraProjectionMatrix, false, projectionMatrix);\n gl.uniformMatrix4fv(this._uCameraInverseProjectionMatrix, false, inverseProjectionMatrix);\n\n gl.uniform1i(this._uPerspective, perspective);\n\n gl.uniform1f(this._uScale, sao.scale * (far / 5));\n gl.uniform1f(this._uIntensity, sao.intensity);\n gl.uniform1f(this._uBias, sao.bias);\n gl.uniform1f(this._uKernelRadius, sao.kernelRadius);\n gl.uniform1f(this._uMinResolution, sao.minResolution);\n gl.uniform2fv(this._uViewport, tempVec2);\n gl.uniform1f(this._uRandomSeed, randomSeed);\n\n const depthTexture = depthRenderBuffer.getDepthTexture();\n\n program.bindTexture(this._uDepthTexture, depthTexture, 0);\n\n this._aUV.bindArrayBuffer(this._uvBuf);\n this._aPosition.bindArrayBuffer(this._positionsBuf);\n this._indicesBuf.bind();\n\n gl.drawElements(gl.TRIANGLES, this._indicesBuf.numItems, this._indicesBuf.itemType, 0);\n }\n\n _build() {\n\n let dirty = false;\n\n const sao = this._scene.sao;\n\n if (sao.numSamples !== this._numSamples) {\n this._numSamples = Math.floor(sao.numSamples);\n dirty = true;\n }\n\n if (!dirty) {\n return;\n }\n\n const gl = this._scene.canvas.gl;\n\n if (this._program) {\n this._program.destroy();\n this._program = null;\n }\n\n this._program = new Program(gl, {\n\n vertex: [`#version 300 es\n precision highp float;\n precision highp int;\n \n in vec3 aPosition;\n in vec2 aUV; \n \n out vec2 vUV;\n \n void main () {\n gl_Position = vec4(aPosition, 1.0);\n vUV = aUV;\n }`],\n\n fragment: [\n `#version 300 es \n precision highp float;\n precision highp int; \n \n #define NORMAL_TEXTURE 0\n #define PI 3.14159265359\n #define PI2 6.28318530718\n #define EPSILON 1e-6\n #define NUM_SAMPLES ${this._numSamples}\n #define NUM_RINGS 4 \n \n in vec2 vUV;\n \n uniform sampler2D uDepthTexture;\n \n uniform float uCameraNear;\n uniform float uCameraFar;\n uniform mat4 uProjectMatrix;\n uniform mat4 uInverseProjectMatrix;\n \n uniform bool uPerspective;\n\n uniform float uScale;\n uniform float uIntensity;\n uniform float uBias;\n uniform float uKernelRadius;\n uniform float uMinResolution;\n uniform vec2 uViewport;\n uniform float uRandomSeed;\n\n float pow2( const in float x ) { return x*x; }\n \n highp float rand( const in vec2 uv ) {\n const highp float a = 12.9898, b = 78.233, c = 43758.5453;\n highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n return fract(sin(sn) * c);\n }\n\n vec3 packNormalToRGB( const in vec3 normal ) {\n return normalize( normal ) * 0.5 + 0.5;\n }\n\n vec3 unpackRGBToNormal( const in vec3 rgb ) {\n return 2.0 * rgb.xyz - 1.0;\n }\n\n const float packUpscale = 256. / 255.;\n const float unpackDownScale = 255. / 256.; \n\n const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\n const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. ); \n\n const float shiftRights = 1. / 256.;\n\n vec4 packFloatToRGBA( const in float v ) {\n vec4 r = vec4( fract( v * packFactors ), v );\n r.yzw -= r.xyz * shiftRights; \n return r * packUpscale;\n }\n\n float unpackRGBAToFloat( const in vec4 v ) { \n return dot( floor( v * 255.0 + 0.5 ) / 255.0, unPackFactors );\n }\n \n float perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n return ( near * far ) / ( ( far - near ) * invClipZ - far );\n }\n\n float orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n return linearClipZ * ( near - far ) - near;\n }\n \n float getDepth( const in vec2 screenPosition ) {\n return vec4(texture(uDepthTexture, screenPosition)).r;\n }\n\n float getViewZ( const in float depth ) {\n if (uPerspective) {\n return perspectiveDepthToViewZ( depth, uCameraNear, uCameraFar );\n } else {\n return orthographicDepthToViewZ( depth, uCameraNear, uCameraFar );\n }\n }\n\n vec3 getViewPos( const in vec2 screenPos, const in float depth, const in float viewZ ) {\n \tfloat clipW = uProjectMatrix[2][3] * viewZ + uProjectMatrix[3][3];\n \tvec4 clipPosition = vec4( ( vec3( screenPos, depth ) - 0.5 ) * 2.0, 1.0 );\n \tclipPosition *= clipW; \n \treturn ( uInverseProjectMatrix * clipPosition ).xyz;\n }\n\n vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPos ) { \n return normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) );\n }\n\n float scaleDividedByCameraFar;\n float minResolutionMultipliedByCameraFar;\n\n float getOcclusion( const in vec3 centerViewPosition, const in vec3 centerViewNormal, const in vec3 sampleViewPosition ) {\n \tvec3 viewDelta = sampleViewPosition - centerViewPosition;\n \tfloat viewDistance = length( viewDelta );\n \tfloat scaledScreenDistance = scaleDividedByCameraFar * viewDistance;\n \treturn max(0.0, (dot(centerViewNormal, viewDelta) - minResolutionMultipliedByCameraFar) / scaledScreenDistance - uBias) / (1.0 + pow2( scaledScreenDistance ) );\n }\n\n const float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES );\n const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES );\n\n float getAmbientOcclusion( const in vec3 centerViewPosition ) {\n \n \tscaleDividedByCameraFar = uScale / uCameraFar;\n \tminResolutionMultipliedByCameraFar = uMinResolution * uCameraFar;\n \tvec3 centerViewNormal = getViewNormal( centerViewPosition, vUV );\n\n \tfloat angle = rand( vUV + uRandomSeed ) * PI2;\n \tvec2 radius = vec2( uKernelRadius * INV_NUM_SAMPLES ) / uViewport;\n \tvec2 radiusStep = radius;\n\n \tfloat occlusionSum = 0.0;\n \tfloat weightSum = 0.0;\n\n \tfor( int i = 0; i < NUM_SAMPLES; i ++ ) {\n \t\tvec2 sampleUv = vUV + vec2( cos( angle ), sin( angle ) ) * radius;\n \t\tradius += radiusStep;\n \t\tangle += ANGLE_STEP;\n\n \t\tfloat sampleDepth = getDepth( sampleUv );\n \t\tif( sampleDepth >= ( 1.0 - EPSILON ) ) {\n \t\t\tcontinue;\n \t\t}\n\n \t\tfloat sampleViewZ = getViewZ( sampleDepth );\n \t\tvec3 sampleViewPosition = getViewPos( sampleUv, sampleDepth, sampleViewZ );\n \t\tocclusionSum += getOcclusion( centerViewPosition, centerViewNormal, sampleViewPosition );\n \t\tweightSum += 1.0;\n \t}\n\n \tif( weightSum == 0.0 ) discard;\n\n \treturn occlusionSum * ( uIntensity / weightSum );\n }\n\n out vec4 outColor;\n \n void main() {\n \n \tfloat centerDepth = getDepth( vUV );\n \t\n \tif( centerDepth >= ( 1.0 - EPSILON ) ) {\n \t\tdiscard;\n \t}\n\n \tfloat centerViewZ = getViewZ( centerDepth );\n \tvec3 viewPosition = getViewPos( vUV, centerDepth, centerViewZ );\n\n \tfloat ambientOcclusion = getAmbientOcclusion( viewPosition );\n \n \toutColor = packFloatToRGBA( 1.0- ambientOcclusion );\n }`]\n });\n\n if (this._program.errors) {\n console.error(this._program.errors.join(\"\\n\"));\n this._programError = true;\n return;\n }\n\n const uv = new Float32Array([1, 1, 0, 1, 0, 0, 1, 0]);\n const positions = new Float32Array([1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0]);\n \n // Mitigation: if Uint8Array is used, the geometry is corrupted on OSX when using Chrome with data-textures\n const indices = new Uint32Array([0, 1, 2, 0, 2, 3]);\n\n this._positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, positions.length, 3, gl.STATIC_DRAW);\n this._uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uv, uv.length, 2, gl.STATIC_DRAW);\n this._indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, indices.length, 1, gl.STATIC_DRAW);\n\n this._program.bind();\n\n this._uCameraNear = this._program.getLocation(\"uCameraNear\");\n this._uCameraFar = this._program.getLocation(\"uCameraFar\");\n\n this._uCameraProjectionMatrix = this._program.getLocation(\"uProjectMatrix\");\n this._uCameraInverseProjectionMatrix = this._program.getLocation(\"uInverseProjectMatrix\");\n\n this._uPerspective = this._program.getLocation(\"uPerspective\");\n\n this._uScale = this._program.getLocation(\"uScale\");\n this._uIntensity = this._program.getLocation(\"uIntensity\");\n this._uBias = this._program.getLocation(\"uBias\");\n this._uKernelRadius = this._program.getLocation(\"uKernelRadius\");\n this._uMinResolution = this._program.getLocation(\"uMinResolution\");\n this._uViewport = this._program.getLocation(\"uViewport\");\n this._uRandomSeed = this._program.getLocation(\"uRandomSeed\");\n\n this._aPosition = this._program.getAttribute(\"aPosition\");\n this._aUV = this._program.getAttribute(\"aUV\");\n\n this._dirty = false;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n this._program = null;\n }\n }\n}\n\nconst blurStdDev = 4;\nconst blurDepthCutoff = 0.01;\nconst KERNEL_RADIUS = 16;\n\nconst sampleOffsetsVert = new Float32Array(createSampleOffsets(KERNEL_RADIUS + 1, [0, 1]));\nconst sampleOffsetsHor = new Float32Array(createSampleOffsets(KERNEL_RADIUS + 1, [1, 0]));\nconst sampleWeights = new Float32Array(createSampleWeights(KERNEL_RADIUS + 1, blurStdDev));\n\nconst tempVec2a$1 = new Float32Array(2);\n\n/**\n * SAO implementation inspired from previous SAO work in THREE.js by ludobaka / ludobaka.github.io and bhouston\n * @private\n */\nclass SAODepthLimitedBlurRenderer {\n\n constructor(scene) {\n\n this._scene = scene;\n\n // The program\n\n this._program = null;\n this._programError = false;\n\n // Variable locations\n\n this._aPosition = null;\n this._aUV = null;\n\n this._uDepthTexture = \"uDepthTexture\";\n this._uOcclusionTexture = \"uOcclusionTexture\";\n\n this._uViewport = null;\n this._uCameraNear = null;\n this._uCameraFar = null;\n this._uCameraProjectionMatrix = null;\n this._uCameraInverseProjectionMatrix = null;\n\n // VBOs\n\n this._uvBuf = null;\n this._positionsBuf = null;\n this._indicesBuf = null;\n\n this.init();\n }\n\n init() {\n\n // Create program & VBOs, locate attributes and uniforms\n\n const gl = this._scene.canvas.gl;\n\n this._program = new Program(gl, {\n\n vertex: [\n `#version 300 es\n precision highp float;\n precision highp int;\n \n in vec3 aPosition;\n in vec2 aUV;\n uniform vec2 uViewport;\n out vec2 vUV;\n out vec2 vInvSize;\n void main () {\n vUV = aUV;\n vInvSize = 1.0 / uViewport;\n gl_Position = vec4(aPosition, 1.0);\n }`],\n\n fragment: [\n `#version 300 es\n precision highp float;\n precision highp int;\n \n #define PI 3.14159265359\n #define PI2 6.28318530718\n #define EPSILON 1e-6\n\n #define KERNEL_RADIUS ${KERNEL_RADIUS}\n\n in vec2 vUV;\n in vec2 vInvSize;\n \n uniform sampler2D uDepthTexture;\n uniform sampler2D uOcclusionTexture; \n \n uniform float uCameraNear;\n uniform float uCameraFar; \n uniform float uDepthCutoff;\n\n uniform vec2 uSampleOffsets[ KERNEL_RADIUS + 1 ];\n uniform float uSampleWeights[ KERNEL_RADIUS + 1 ];\n\n const float unpackDownscale = 255. / 256.; \n\n const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\n const vec4 unpackFactors = unpackDownscale / vec4( packFactors, 1. ); \n\n const float packUpscale = 256. / 255.;\n \n const float shiftRights = 1. / 256.;\n \n float unpackRGBAToFloat( const in vec4 v ) {\n return dot( floor( v * 255.0 + 0.5 ) / 255.0, unpackFactors );\n } \n\n vec4 packFloatToRGBA( const in float v ) {\n vec4 r = vec4( fract( v * packFactors ), v );\n r.yzw -= r.xyz * shiftRights; \n return r * packUpscale;\n }\n\n float viewZToOrthographicDepth( const in float viewZ) {\n return ( viewZ + uCameraNear ) / ( uCameraNear - uCameraFar );\n }\n \n float orthographicDepthToViewZ( const in float linearClipZ) {\n return linearClipZ * ( uCameraNear - uCameraFar ) - uCameraNear;\n }\n\n float viewZToPerspectiveDepth( const in float viewZ) {\n return (( uCameraNear + viewZ ) * uCameraFar ) / (( uCameraFar - uCameraNear ) * viewZ );\n }\n \n float perspectiveDepthToViewZ( const in float invClipZ) {\n return ( uCameraNear * uCameraFar ) / ( ( uCameraFar - uCameraNear ) * invClipZ - uCameraFar );\n }\n\n float getDepth( const in vec2 screenPosition ) {\n return vec4(texture(uDepthTexture, screenPosition)).r;\n }\n\n float getViewZ( const in float depth ) {\n return perspectiveDepthToViewZ( depth );\n }\n\n out vec4 outColor;\n \n void main() {\n \n float depth = getDepth( vUV );\n if( depth >= ( 1.0 - EPSILON ) ) {\n discard;\n }\n\n float centerViewZ = -getViewZ( depth );\n bool rBreak = false;\n bool lBreak = false;\n\n float weightSum = uSampleWeights[0];\n float occlusionSum = unpackRGBAToFloat(texture( uOcclusionTexture, vUV )) * weightSum;\n\n for( int i = 1; i <= KERNEL_RADIUS; i ++ ) {\n\n float sampleWeight = uSampleWeights[i];\n vec2 sampleUVOffset = uSampleOffsets[i] * vInvSize;\n\n vec2 sampleUV = vUV + sampleUVOffset;\n float viewZ = -getViewZ( getDepth( sampleUV ) );\n\n if( abs( viewZ - centerViewZ ) > uDepthCutoff ) {\n rBreak = true;\n }\n\n if( ! rBreak ) {\n occlusionSum += unpackRGBAToFloat(texture( uOcclusionTexture, sampleUV )) * sampleWeight;\n weightSum += sampleWeight;\n }\n\n sampleUV = vUV - sampleUVOffset;\n viewZ = -getViewZ( getDepth( sampleUV ) );\n\n if( abs( viewZ - centerViewZ ) > uDepthCutoff ) {\n lBreak = true;\n }\n\n if( ! lBreak ) {\n occlusionSum += unpackRGBAToFloat(texture( uOcclusionTexture, sampleUV )) * sampleWeight;\n weightSum += sampleWeight;\n }\n }\n\n outColor = packFloatToRGBA(occlusionSum / weightSum);\n }`\n ]\n });\n\n if (this._program.errors) {\n console.error(this._program.errors.join(\"\\n\"));\n this._programError = true;\n return;\n }\n\n const uv = new Float32Array([1, 1, 0, 1, 0, 0, 1, 0]);\n const positions = new Float32Array([1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0]);\n\n // Mitigation: if Uint8Array is used, the geometry is corrupted on OSX when using Chrome with data-textures\n const indices = new Uint32Array([0, 1, 2, 0, 2, 3]);\n\n this._positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, positions.length, 3, gl.STATIC_DRAW);\n this._uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uv, uv.length, 2, gl.STATIC_DRAW);\n this._indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, indices.length, 1, gl.STATIC_DRAW);\n\n this._program.bind();\n\n this._uViewport = this._program.getLocation(\"uViewport\");\n\n this._uCameraNear = this._program.getLocation(\"uCameraNear\");\n this._uCameraFar = this._program.getLocation(\"uCameraFar\");\n\n this._uDepthCutoff = this._program.getLocation(\"uDepthCutoff\");\n\n this._uSampleOffsets = gl.getUniformLocation(this._program.handle, \"uSampleOffsets\");\n this._uSampleWeights = gl.getUniformLocation(this._program.handle, \"uSampleWeights\");\n\n this._aPosition = this._program.getAttribute(\"aPosition\");\n this._aUV = this._program.getAttribute(\"aUV\");\n }\n\n render(depthRenderBuffer, occlusionRenderBuffer, direction) {\n\n if (this._programError) {\n return;\n }\n\n if (!this._getInverseProjectMat) { // HACK: scene.camera not defined until render time\n this._getInverseProjectMat = (() => {\n let projMatDirty = true;\n this._scene.camera.on(\"projMatrix\", function () {\n projMatDirty = true;\n });\n const inverseProjectMat = math.mat4();\n return () => {\n if (projMatDirty) {\n math.inverseMat4(scene.camera.projMatrix, inverseProjectMat);\n }\n return inverseProjectMat;\n }\n })();\n }\n\n const gl = this._scene.canvas.gl;\n const program = this._program;\n const scene = this._scene;\n const viewportWidth = gl.drawingBufferWidth;\n const viewportHeight = gl.drawingBufferHeight;\n const projectState = scene.camera.project._state;\n const near = projectState.near;\n const far = projectState.far;\n\n gl.viewport(0, 0, viewportWidth, viewportHeight);\n gl.clearColor(0, 0, 0, 1);\n gl.enable(gl.DEPTH_TEST);\n gl.disable(gl.BLEND);\n gl.frontFace(gl.CCW);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n program.bind();\n\n tempVec2a$1[0] = viewportWidth;\n tempVec2a$1[1] = viewportHeight;\n\n gl.uniform2fv(this._uViewport, tempVec2a$1);\n gl.uniform1f(this._uCameraNear, near);\n gl.uniform1f(this._uCameraFar, far);\n\n gl.uniform1f(this._uDepthCutoff, blurDepthCutoff);\n\n if (direction === 0) {// Horizontal\n gl.uniform2fv(this._uSampleOffsets, sampleOffsetsHor);\n } else { // Vertical\n gl.uniform2fv(this._uSampleOffsets, sampleOffsetsVert);\n }\n\n gl.uniform1fv(this._uSampleWeights, sampleWeights);\n\n const depthTexture = depthRenderBuffer.getDepthTexture();\n const occlusionTexture = occlusionRenderBuffer.getTexture();\n\n program.bindTexture(this._uDepthTexture, depthTexture, 0); // TODO: use FrameCtx.textureUnit\n program.bindTexture(this._uOcclusionTexture, occlusionTexture, 1);\n\n this._aUV.bindArrayBuffer(this._uvBuf);\n this._aPosition.bindArrayBuffer(this._positionsBuf);\n this._indicesBuf.bind();\n\n gl.drawElements(gl.TRIANGLES, this._indicesBuf.numItems, this._indicesBuf.itemType, 0);\n }\n\n destroy() {\n this._program.destroy();\n }\n}\n\nfunction createSampleWeights(kernelRadius, stdDev) {\n const weights = [];\n for (let i = 0; i <= kernelRadius; i++) {\n weights.push(gaussian(i, stdDev));\n }\n return weights; // TODO: Optimize\n}\n\nfunction gaussian(x, stdDev) {\n return Math.exp(-(x * x) / (2.0 * (stdDev * stdDev))) / (Math.sqrt(2.0 * Math.PI) * stdDev);\n}\n\nfunction createSampleOffsets(kernelRadius, uvIncrement) {\n const offsets = [];\n for (let i = 0; i <= kernelRadius; i++) {\n offsets.push(uvIncrement[0] * i);\n offsets.push(uvIncrement[1] * i);\n }\n return offsets;\n}\n\n/**\n * @desc Represents a WebGL render buffer.\n * @private\n */\nclass RenderBuffer {\n\n constructor(canvas, gl, options) {\n options = options || {};\n /** @type {WebGL2RenderingContext} */\n this.gl = gl;\n this.allocated = false;\n this.canvas = canvas;\n this.buffer = null;\n this.bound = false;\n this.size = options.size;\n this._hasDepthTexture = !!options.depthTexture;\n }\n\n setSize(size) {\n this.size = size;\n }\n\n webglContextRestored(gl) {\n this.gl = gl;\n this.buffer = null;\n this.allocated = false;\n this.bound = false;\n }\n\n bind(...internalformats) {\n this._touch(...internalformats);\n if (this.bound) {\n return;\n }\n const gl = this.gl;\n gl.bindFramebuffer(gl.FRAMEBUFFER, this.buffer.framebuf);\n this.bound = true;\n }\n\n /**\n * Create and specify a WebGL texture image.\n *\n * @param { number } width \n * @param { number } height \n * @param { GLenum } [internalformat=null] \n *\n * @returns { WebGLTexture }\n */\n createTexture(width, height, internalformat = null) {\n const gl = this.gl;\n\n const colorTexture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, colorTexture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n if (internalformat) {\n gl.texStorage2D(gl.TEXTURE_2D, 1, internalformat, width, height);\n } else {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n }\n\n return colorTexture;\n }\n\n /**\n *\n * @param {number[]} [internalformats=[]]\n * @returns\n */\n _touch(...internalformats) {\n\n let width;\n let height;\n const gl = this.gl;\n\n if (this.size) {\n width = this.size[0];\n height = this.size[1];\n\n } else {\n width = gl.drawingBufferWidth;\n height = gl.drawingBufferHeight;\n }\n\n if (this.buffer) {\n\n if (this.buffer.width === width && this.buffer.height === height) {\n return;\n\n } else {\n this.buffer.textures.forEach(texture => gl.deleteTexture(texture));\n gl.deleteFramebuffer(this.buffer.framebuf);\n gl.deleteRenderbuffer(this.buffer.renderbuf);\n }\n }\n\n const colorTextures = [];\n if (internalformats.length > 0) {\n colorTextures.push(...internalformats.map(internalformat => this.createTexture(width, height, internalformat)));\n } else {\n colorTextures.push(this.createTexture(width, height));\n }\n\n let depthTexture;\n\n if (this._hasDepthTexture) {\n depthTexture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, depthTexture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F, width, height, 0, gl.DEPTH_COMPONENT, gl.FLOAT, null);\n }\n\n const renderbuf = gl.createRenderbuffer();\n gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuf);\n gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT32F, width, height);\n\n const framebuf = gl.createFramebuffer();\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);\n for (let i = 0; i < colorTextures.length; i++) {\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, colorTextures[i], 0);\n }\n if (internalformats.length > 0) {\n gl.drawBuffers(colorTextures.map((_, i) => gl.COLOR_ATTACHMENT0 + i));\n }\n\n if (this._hasDepthTexture) {\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);\n } else {\n gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuf);\n }\n\n gl.bindTexture(gl.TEXTURE_2D, null);\n gl.bindRenderbuffer(gl.RENDERBUFFER, null);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n // Verify framebuffer is OK\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);\n if (!gl.isFramebuffer(framebuf)) {\n throw \"Invalid framebuffer\";\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n\n switch (status) {\n\n case gl.FRAMEBUFFER_COMPLETE:\n break;\n\n case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:\n throw \"Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT\";\n\n case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:\n throw \"Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\";\n\n case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:\n throw \"Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS\";\n\n case gl.FRAMEBUFFER_UNSUPPORTED:\n throw \"Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED\";\n\n default:\n throw \"Incomplete framebuffer: \" + status;\n }\n\n this.buffer = {\n framebuf: framebuf,\n renderbuf: renderbuf,\n texture: colorTextures[0],\n textures: colorTextures,\n depthTexture: depthTexture,\n width: width,\n height: height\n };\n\n this.bound = false;\n }\n\n clear() {\n if (!this.bound) {\n throw \"Render buffer not bound\";\n }\n const gl = this.gl;\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n }\n\n read(pickX, pickY, glFormat = null, glType = null, arrayType = Uint8Array, arrayMultiplier = 4, colorBufferIndex = 0) {\n const x = pickX;\n const y = this.buffer.height ? (this.buffer.height - pickY - 1) : (this.gl.drawingBufferHeight - pickY);\n const pix = new arrayType(arrayMultiplier);\n const gl = this.gl;\n gl.readBuffer(gl.COLOR_ATTACHMENT0 + colorBufferIndex);\n gl.readPixels(x, y, 1, 1, glFormat || gl.RGBA, glType || gl.UNSIGNED_BYTE, pix, 0);\n return pix;\n }\n\n readArray(glFormat = null, glType = null, arrayType = Uint8Array, arrayMultiplier = 4, colorBufferIndex = 0) {\n const pix = new arrayType(this.buffer.width*this.buffer.height * arrayMultiplier);\n const gl = this.gl;\n gl.readBuffer(gl.COLOR_ATTACHMENT0 + colorBufferIndex);\n gl.readPixels(0, 0, this.buffer.width, this.buffer.height, glFormat || gl.RGBA, glType || gl.UNSIGNED_BYTE, pix, 0);\n return pix;\n }\n\n /**\n * Returns an HTMLCanvas containing the contents of the RenderBuffer as an image.\n *\n * - The HTMLCanvas has a CanvasRenderingContext2D.\n * - Expects the caller to draw more things on the HTMLCanvas (annotations etc).\n *\n * @returns {HTMLCanvasElement}\n */\n readImageAsCanvas() {\n const gl = this.gl;\n const imageDataCache = this._getImageDataCache();\n const pixelData = imageDataCache.pixelData;\n const canvas = imageDataCache.canvas;\n const imageData = imageDataCache.imageData;\n const context = imageDataCache.context;\n gl.readPixels(0, 0, this.buffer.width, this.buffer.height, gl.RGBA, gl.UNSIGNED_BYTE, pixelData);\n const width = this.buffer.width;\n const height = this.buffer.height;\n const halfHeight = height / 2 | 0; // the | 0 keeps the result an int\n const bytesPerRow = width * 4;\n const temp = new Uint8Array(width * 4);\n for (let y = 0; y < halfHeight; ++y) {\n const topOffset = y * bytesPerRow;\n const bottomOffset = (height - y - 1) * bytesPerRow;\n temp.set(pixelData.subarray(topOffset, topOffset + bytesPerRow));\n pixelData.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);\n pixelData.set(temp, bottomOffset);\n }\n imageData.data.set(pixelData);\n context.putImageData(imageData, 0, 0);\n return canvas;\n }\n\n readImage(params) {\n const gl = this.gl;\n const imageDataCache = this._getImageDataCache();\n const pixelData = imageDataCache.pixelData;\n const canvas = imageDataCache.canvas;\n const imageData = imageDataCache.imageData;\n const context = imageDataCache.context;\n const { width, height } = this.buffer;\n gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelData);\n imageData.data.set(pixelData);\n context.putImageData(imageData, 0, 0);\n\n // flip Y\n context.save();\n context.globalCompositeOperation = 'copy';\n context.scale(1, -1);\n context.drawImage(canvas, 0, -height, width, height);\n context.restore();\n\n let format = params.format || \"png\";\n if (format !== \"jpeg\" && format !== \"png\" && format !== \"bmp\") {\n console.error(\"Unsupported image format: '\" + format + \"' - supported types are 'jpeg', 'bmp' and 'png' - defaulting to 'png'\");\n format = \"png\";\n }\n return canvas.toDataURL(`image/${format}`);\n }\n\n _getImageDataCache(type = Uint8Array, multiplier = 4) {\n\n const bufferWidth = this.buffer.width;\n const bufferHeight = this.buffer.height;\n\n let imageDataCache = this._imageDataCache;\n\n if (imageDataCache) {\n if (imageDataCache.width !== bufferWidth || imageDataCache.height !== bufferHeight) {\n this._imageDataCache = null;\n imageDataCache = null;\n }\n }\n\n if (!imageDataCache) {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n canvas.width = bufferWidth;\n canvas.height = bufferHeight;\n imageDataCache = {\n pixelData: new type(bufferWidth * bufferHeight * multiplier),\n canvas: canvas,\n context: context,\n imageData: context.createImageData(bufferWidth, bufferHeight),\n width: bufferWidth,\n height: bufferHeight\n };\n\n this._imageDataCache = imageDataCache;\n }\n imageDataCache.context.resetTransform(); // Prevents strange scale-accumulation effect with html2canvas\n return imageDataCache;\n }\n\n unbind() {\n const gl = this.gl;\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n this.bound = false;\n }\n\n getTexture(index = 0) {\n const self = this;\n return this._texture || (this._texture = {\n renderBuffer: this,\n bind: function (unit) {\n if (self.buffer && self.buffer.textures[index]) {\n self.gl.activeTexture(self.gl[\"TEXTURE\" + unit]);\n self.gl.bindTexture(self.gl.TEXTURE_2D, self.buffer.textures[index]);\n return true;\n }\n return false;\n },\n unbind: function (unit) {\n if (self.buffer && self.buffer.textures[index]) {\n self.gl.activeTexture(self.gl[\"TEXTURE\" + unit]);\n self.gl.bindTexture(self.gl.TEXTURE_2D, null);\n }\n }\n });\n }\n\n hasDepthTexture() {\n return this._hasDepthTexture;\n }\n\n getDepthTexture() {\n if (!this._hasDepthTexture) {\n return null;\n }\n const self = this;\n return this._depthTexture || (this._dethTexture = {\n renderBuffer: this,\n bind: function (unit) {\n if (self.buffer && self.buffer.depthTexture) {\n self.gl.activeTexture(self.gl[\"TEXTURE\" + unit]);\n self.gl.bindTexture(self.gl.TEXTURE_2D, self.buffer.depthTexture);\n return true;\n }\n return false;\n },\n unbind: function (unit) {\n if (self.buffer && self.buffer.depthTexture) {\n self.gl.activeTexture(self.gl[\"TEXTURE\" + unit]);\n self.gl.bindTexture(self.gl.TEXTURE_2D, null);\n }\n }\n });\n }\n\n destroy() {\n if (this.allocated) {\n const gl = this.gl;\n this.buffer.textures.forEach(texture => gl.deleteTexture(texture));\n gl.deleteTexture(this.buffer.depthTexture);\n gl.deleteFramebuffer(this.buffer.framebuf);\n gl.deleteRenderbuffer(this.buffer.renderbuf);\n this.allocated = false;\n this.buffer = null;\n this.bound = false;\n }\n this._imageDataCache = null;\n this._texture = null;\n this._depthTexture = null;\n }\n}\n\n/**\n * @private\n */\nclass RenderBufferManager {\n\n constructor(scene) {\n this.scene = scene;\n this._renderBuffersBasic = {};\n this._renderBuffersScaled = {};\n }\n\n getRenderBuffer(id, options) {\n const renderBuffers = (this.scene.canvas.resolutionScale === 1.0) ? this._renderBuffersBasic : this._renderBuffersScaled;\n let renderBuffer = renderBuffers[id];\n if (!renderBuffer) {\n renderBuffer = new RenderBuffer(this.scene.canvas.canvas, this.scene.canvas.gl, options);\n renderBuffers[id] = renderBuffer;\n }\n return renderBuffer;\n }\n\n destroy() {\n for (let id in this._renderBuffersBasic) {\n this._renderBuffersBasic[id].destroy();\n }\n for (let id in this._renderBuffersScaled) {\n this._renderBuffersScaled[id].destroy();\n }\n }\n}\n\n/**\n * @private\n */\nfunction getExtension (gl, name) {\n if (gl._cachedExtensions === undefined) {\n gl._cachedExtensions = {};\n }\n if (gl._cachedExtensions[name] !== undefined) {\n return gl._cachedExtensions[name];\n }\n let extension;\n switch (name) {\n case 'WEBGL_depth_texture':\n extension = gl.getExtension('WEBGL_depth_texture') || gl.getExtension('MOZ_WEBGL_depth_texture') || gl.getExtension('WEBKIT_WEBGL_depth_texture');\n break;\n case 'EXT_texture_filter_anisotropic':\n extension = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic');\n break;\n case 'WEBGL_compressed_texture_s3tc':\n extension = gl.getExtension('WEBGL_compressed_texture_s3tc') || gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');\n break;\n case 'WEBGL_compressed_texture_pvrtc':\n extension = gl.getExtension('WEBGL_compressed_texture_pvrtc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');\n break;\n default:\n extension = gl.getExtension(name);\n }\n gl._cachedExtensions[name] = extension;\n return extension;\n}\n\n/**\n * @private\n */\nconst Renderer$1 = function (scene, options) {\n\n options = options || {};\n\n const frameCtx = new FrameContext(scene);\n const canvas = scene.canvas.canvas;\n /**\n * @type {WebGL2RenderingContext}\n */\n const gl = scene.canvas.gl;\n const canvasTransparent = (!!options.transparent);\n const alphaDepthMask = options.alphaDepthMask;\n\n const pickIDs = new Map$1({});\n\n let drawableTypeInfo = {};\n let drawables = {};\n\n let drawableListDirty = true;\n let stateSortDirty = true;\n let imageDirty = true;\n\n let transparentEnabled = true;\n let edgesEnabled = true;\n let saoEnabled = true;\n let pbrEnabled = true;\n let colorTextureEnabled = true;\n\n const renderBufferManager = new RenderBufferManager(scene);\n\n let snapshotBound = false;\n\n const saoOcclusionRenderer = new SAOOcclusionRenderer(scene);\n const saoDepthLimitedBlurRenderer = new SAODepthLimitedBlurRenderer(scene);\n\n this.scene = scene;\n\n this._occlusionTester = null; // Lazy-created in #addMarker()\n\n this.capabilities = {\n astcSupported: !!getExtension(gl, 'WEBGL_compressed_texture_astc'),\n etc1Supported: true, // WebGL2\n etc2Supported: !!getExtension(gl, 'WEBGL_compressed_texture_etc'),\n dxtSupported: !!getExtension(gl, 'WEBGL_compressed_texture_s3tc'),\n bptcSupported: !!getExtension(gl, 'EXT_texture_compression_bptc'),\n pvrtcSupported: !!(getExtension(gl, 'WEBGL_compressed_texture_pvrtc') || getExtension(gl, 'WEBKIT_WEBGL_compressed_texture_pvrtc'))\n };\n\n this.setTransparentEnabled = function (enabled) {\n transparentEnabled = enabled;\n imageDirty = true;\n };\n\n this.setEdgesEnabled = function (enabled) {\n edgesEnabled = enabled;\n imageDirty = true;\n };\n\n this.setSAOEnabled = function (enabled) {\n saoEnabled = enabled;\n imageDirty = true;\n };\n\n this.setPBREnabled = function (enabled) {\n pbrEnabled = enabled;\n imageDirty = true;\n };\n\n this.setColorTextureEnabled = function (enabled) {\n colorTextureEnabled = enabled;\n imageDirty = true;\n };\n\n this.needStateSort = function () {\n stateSortDirty = true;\n };\n\n this.shadowsDirty = function () {\n };\n\n this.imageDirty = function () {\n imageDirty = true;\n };\n\n this.webglContextLost = function () {\n };\n\n this.webglContextRestored = function (gl) {\n\n // renderBufferManager.webglContextRestored(gl);\n\n saoOcclusionRenderer.init();\n saoDepthLimitedBlurRenderer.init();\n\n imageDirty = true;\n };\n\n /**\n * Inserts a drawable into this renderer.\n * @private\n */\n this.addDrawable = function (id, drawable) {\n const type = drawable.type;\n if (!type) {\n console.error(\"Renderer#addDrawable() : drawable with ID \" + id + \" has no 'type' - ignoring\");\n return;\n }\n let drawableInfo = drawableTypeInfo[type];\n if (!drawableInfo) {\n drawableInfo = {\n type: drawable.type,\n count: 0,\n isStateSortable: drawable.isStateSortable,\n stateSortCompare: drawable.stateSortCompare,\n drawableMap: {},\n drawableListPreCull: [],\n drawableList: []\n };\n drawableTypeInfo[type] = drawableInfo;\n }\n drawableInfo.count++;\n drawableInfo.drawableMap[id] = drawable;\n drawables[id] = drawable;\n drawableListDirty = true;\n };\n\n /**\n * Removes a drawable from this renderer.\n * @private\n */\n this.removeDrawable = function (id) {\n const drawable = drawables[id];\n if (!drawable) {\n console.error(\"Renderer#removeDrawable() : drawable not found with ID \" + id + \" - ignoring\");\n return;\n }\n const type = drawable.type;\n const drawableInfo = drawableTypeInfo[type];\n if (--drawableInfo.count <= 0) {\n delete drawableTypeInfo[type];\n } else {\n delete drawableInfo.drawableMap[id];\n }\n delete drawables[id];\n drawableListDirty = true;\n };\n\n /**\n * Gets a unique pick ID for the given Pickable. A Pickable can be a {@link Mesh} or a {@link PerformanceMesh}.\n * @returns {Number} New pick ID.\n */\n this.getPickID = function (entity) {\n return pickIDs.addItem(entity);\n };\n\n /**\n * Released a pick ID for reuse.\n * @param {Number} pickID Pick ID to release.\n */\n this.putPickID = function (pickID) {\n pickIDs.removeItem(pickID);\n };\n\n /**\n * Clears the canvas.\n * @private\n */\n this.clear = function (params) {\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\n if (canvasTransparent) {\n gl.clearColor(1, 1, 1, 1);\n } else {\n const backgroundColor = scene.canvas.backgroundColorFromAmbientLight ? this.lights.getAmbientColorAndIntensity() : scene.canvas.backgroundColor;\n gl.clearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], 1.0);\n }\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n };\n\n /**\n * Returns true if the next call to render() will draw something\n * @returns {Boolean}\n */\n this.needsRender = function () {\n const needsRender = (imageDirty || drawableListDirty || stateSortDirty);\n return needsRender;\n };\n\n /**\n * Renders inserted drawables.\n * @private\n */\n this.render = function (params) {\n params = params || {};\n if (params.force) {\n imageDirty = true;\n }\n updateDrawlist();\n if (imageDirty) {\n draw(params);\n stats.frame.frameCount++;\n imageDirty = false;\n }\n };\n\n function updateDrawlist() { // Prepares state-sorted array of drawables from maps of inserted drawables\n if (drawableListDirty) {\n buildDrawableList();\n drawableListDirty = false;\n stateSortDirty = true;\n }\n if (stateSortDirty) {\n sortDrawableList();\n stateSortDirty = false;\n imageDirty = true;\n }\n if (imageDirty) { // Image is usually dirty because the camera moved\n cullDrawableList();\n }\n }\n\n function buildDrawableList() {\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n const drawableInfo = drawableTypeInfo[type];\n const drawableMap = drawableInfo.drawableMap;\n const drawableListPreCull = drawableInfo.drawableListPreCull;\n let lenDrawableList = 0;\n for (let id in drawableMap) {\n if (drawableMap.hasOwnProperty(id)) {\n drawableListPreCull[lenDrawableList++] = drawableMap[id];\n }\n }\n drawableListPreCull.length = lenDrawableList;\n }\n }\n }\n\n function sortDrawableList() {\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n const drawableInfo = drawableTypeInfo[type];\n if (drawableInfo.isStateSortable) {\n drawableInfo.drawableListPreCull.sort(drawableInfo.stateSortCompare);\n }\n }\n }\n }\n\n function cullDrawableList() {\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n const drawableInfo = drawableTypeInfo[type];\n const drawableListPreCull = drawableInfo.drawableListPreCull;\n const drawableList = drawableInfo.drawableList;\n let lenDrawableList = 0;\n for (let i = 0, len = drawableListPreCull.length; i < len; i++) {\n const drawable = drawableListPreCull[i];\n drawable.rebuildRenderFlags();\n if (!drawable.renderFlags.culled) {\n drawableList[lenDrawableList++] = drawable;\n }\n }\n drawableList.length = lenDrawableList;\n }\n }\n }\n\n function draw(params) {\n\n const sao = scene.sao;\n\n if (saoEnabled && sao.possible) {\n drawSAOBuffers(params);\n }\n\n drawShadowMaps();\n\n drawColor(params);\n }\n\n function drawSAOBuffers(params) {\n\n const sao = scene.sao;\n\n // Render depth buffer\n\n const saoDepthRenderBuffer = renderBufferManager.getRenderBuffer(\"saoDepth\", {\n depthTexture: true\n });\n\n saoDepthRenderBuffer.bind();\n saoDepthRenderBuffer.clear();\n drawDepth(params);\n saoDepthRenderBuffer.unbind();\n\n // Render occlusion buffer\n\n const occlusionRenderBuffer1 = renderBufferManager.getRenderBuffer(\"saoOcclusion\");\n\n occlusionRenderBuffer1.bind();\n occlusionRenderBuffer1.clear();\n saoOcclusionRenderer.render(saoDepthRenderBuffer);\n occlusionRenderBuffer1.unbind();\n\n if (sao.blur) {\n\n // Horizontally blur occlusion buffer 1 into occlusion buffer 2\n\n const occlusionRenderBuffer2 = renderBufferManager.getRenderBuffer(\"saoOcclusion2\");\n\n occlusionRenderBuffer2.bind();\n occlusionRenderBuffer2.clear();\n saoDepthLimitedBlurRenderer.render(saoDepthRenderBuffer, occlusionRenderBuffer1, 0);\n occlusionRenderBuffer2.unbind();\n\n // Vertically blur occlusion buffer 2 back into occlusion buffer 1\n\n occlusionRenderBuffer1.bind();\n occlusionRenderBuffer1.clear();\n saoDepthLimitedBlurRenderer.render(saoDepthRenderBuffer, occlusionRenderBuffer2, 1);\n occlusionRenderBuffer1.unbind();\n }\n }\n\n function drawDepth(params) {\n\n frameCtx.reset();\n frameCtx.pass = params.pass;\n\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\n\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.DEPTH_TEST);\n gl.frontFace(gl.CCW);\n gl.enable(gl.CULL_FACE);\n gl.depthMask(true);\n\n if (params.clear !== false) {\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n }\n\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n\n const drawableInfo = drawableTypeInfo[type];\n const drawableList = drawableInfo.drawableList;\n\n for (let i = 0, len = drawableList.length; i < len; i++) {\n\n const drawable = drawableList[i];\n\n if (drawable.culled === true || drawable.visible === false || !drawable.drawDepth || !drawable.saoEnabled) {\n continue;\n }\n\n if (drawable.renderFlags.colorOpaque) {\n drawable.drawDepth(frameCtx);\n }\n }\n }\n }\n\n // const numVertexAttribs = WEBGL_INFO.MAX_VERTEX_ATTRIBS; // Fixes https://github.com/xeokit/xeokit-sdk/issues/174\n // for (let ii = 0; ii < numVertexAttribs; ii++) {\n // gl.disableVertexAttribArray(ii);\n // }\n\n }\n\n function drawShadowMaps() {\n\n let lights = scene._lightsState.lights;\n\n for (let i = 0, len = lights.length; i < len; i++) {\n const light = lights[i];\n if (!light.castsShadow) {\n continue;\n }\n drawShadowMap(light);\n }\n }\n\n function drawShadowMap(light) {\n\n const castsShadow = light.castsShadow;\n\n if (!castsShadow) {\n return;\n }\n\n const shadowRenderBuf = light.getShadowRenderBuf();\n\n if (!shadowRenderBuf) {\n return;\n }\n\n shadowRenderBuf.bind();\n\n frameCtx.reset();\n\n frameCtx.backfaces = true;\n frameCtx.frontface = true;\n frameCtx.shadowViewMatrix = light.getShadowViewMatrix();\n frameCtx.shadowProjMatrix = light.getShadowProjMatrix();\n\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\n\n gl.clearColor(0, 0, 0, 1);\n gl.enable(gl.DEPTH_TEST);\n gl.disable(gl.BLEND);\n\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n for (let type in drawableTypeInfo) {\n\n if (drawableTypeInfo.hasOwnProperty(type)) {\n\n const drawableInfo = drawableTypeInfo[type];\n const drawableList = drawableInfo.drawableList;\n\n for (let i = 0, len = drawableList.length; i < len; i++) {\n\n const drawable = drawableList[i];\n\n if (drawable.visible === false || !drawable.castsShadow || !drawable.drawShadow) {\n continue;\n }\n\n if (drawable.renderFlags.colorOpaque) { // Transparent objects don't cast shadows (yet)\n drawable.drawShadow(frameCtx);\n }\n }\n }\n }\n\n shadowRenderBuf.unbind();\n }\n\n function drawColor(params) {\n\n const normalDrawSAOBin = [];\n const normalEdgesOpaqueBin = [];\n const normalFillTransparentBin = [];\n const normalEdgesTransparentBin = [];\n\n const xrayedFillOpaqueBin = [];\n const xrayEdgesOpaqueBin = [];\n const xrayedFillTransparentBin = [];\n const xrayEdgesTransparentBin = [];\n\n const highlightedFillOpaqueBin = [];\n const highlightedEdgesOpaqueBin = [];\n const highlightedFillTransparentBin = [];\n const highlightedEdgesTransparentBin = [];\n\n const selectedFillOpaqueBin = [];\n const selectedEdgesOpaqueBin = [];\n const selectedFillTransparentBin = [];\n const selectedEdgesTransparentBin = [];\n\n\n const ambientColorAndIntensity = scene._lightsState.getAmbientColorAndIntensity();\n\n frameCtx.reset();\n frameCtx.pass = params.pass;\n frameCtx.withSAO = false;\n frameCtx.pbrEnabled = pbrEnabled && !!scene.pbrEnabled;\n frameCtx.colorTextureEnabled = colorTextureEnabled && !!scene.colorTextureEnabled;\n\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\n\n if (canvasTransparent) {\n gl.clearColor(0, 0, 0, 0);\n } else {\n const backgroundColor = scene.canvas.backgroundColorFromAmbientLight ? ambientColorAndIntensity : scene.canvas.backgroundColor;\n gl.clearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], 1.0);\n }\n\n gl.enable(gl.DEPTH_TEST);\n gl.frontFace(gl.CCW);\n gl.enable(gl.CULL_FACE);\n gl.depthMask(true);\n gl.lineWidth(1);\n\n frameCtx.lineWidth = 1;\n\n const saoPossible = scene.sao.possible;\n\n if (saoEnabled && saoPossible) {\n const occlusionRenderBuffer1 = renderBufferManager.getRenderBuffer(\"saoOcclusion\");\n frameCtx.occlusionTexture = occlusionRenderBuffer1 ? occlusionRenderBuffer1.getTexture() : null;\n } else {\n frameCtx.occlusionTexture = null;\n\n }\n\n let i;\n let len;\n let drawable;\n\n const startTime = Date.now();\n\n if (params.clear !== false) {\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n }\n\n let normalDrawSAOBinLen = 0;\n let normalEdgesOpaqueBinLen = 0;\n let normalFillTransparentBinLen = 0;\n let normalEdgesTransparentBinLen = 0;\n\n let xrayedFillOpaqueBinLen = 0;\n let xrayEdgesOpaqueBinLen = 0;\n let xrayedFillTransparentBinLen = 0;\n let xrayEdgesTransparentBinLen = 0;\n\n let highlightedFillOpaqueBinLen = 0;\n let highlightedEdgesOpaqueBinLen = 0;\n let highlightedFillTransparentBinLen = 0;\n let highlightedEdgesTransparentBinLen = 0;\n\n let selectedFillOpaqueBinLen = 0;\n let selectedEdgesOpaqueBinLen = 0;\n let selectedFillTransparentBinLen = 0;\n let selectedEdgesTransparentBinLen = 0;\n\n //------------------------------------------------------------------------------------------------------\n // Render normal opaque solids, defer others to bins to render after\n //------------------------------------------------------------------------------------------------------\n\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n\n const drawableInfo = drawableTypeInfo[type];\n const drawableList = drawableInfo.drawableList;\n\n for (i = 0, len = drawableList.length; i < len; i++) {\n\n drawable = drawableList[i];\n\n if (drawable.culled === true || drawable.visible === false) {\n continue;\n }\n\n const renderFlags = drawable.renderFlags;\n\n if (renderFlags.colorOpaque) {\n if (saoEnabled && saoPossible && drawable.saoEnabled) {\n normalDrawSAOBin[normalDrawSAOBinLen++] = drawable;\n } else {\n drawable.drawColorOpaque(frameCtx);\n }\n }\n\n if (transparentEnabled) {\n if (renderFlags.colorTransparent) {\n normalFillTransparentBin[normalFillTransparentBinLen++] = drawable;\n }\n }\n\n if (renderFlags.xrayedSilhouetteTransparent) {\n xrayedFillTransparentBin[xrayedFillTransparentBinLen++] = drawable;\n }\n\n if (renderFlags.xrayedSilhouetteOpaque) {\n xrayedFillOpaqueBin[xrayedFillOpaqueBinLen++] = drawable;\n }\n\n if (renderFlags.highlightedSilhouetteTransparent) {\n highlightedFillTransparentBin[highlightedFillTransparentBinLen++] = drawable;\n }\n\n if (renderFlags.highlightedSilhouetteOpaque) {\n highlightedFillOpaqueBin[highlightedFillOpaqueBinLen++] = drawable;\n }\n\n if (renderFlags.selectedSilhouetteTransparent) {\n selectedFillTransparentBin[selectedFillTransparentBinLen++] = drawable;\n }\n\n if (renderFlags.selectedSilhouetteOpaque) {\n selectedFillOpaqueBin[selectedFillOpaqueBinLen++] = drawable;\n }\n\n if (drawable.edges && edgesEnabled) {\n if (renderFlags.edgesOpaque) {\n normalEdgesOpaqueBin[normalEdgesOpaqueBinLen++] = drawable;\n }\n\n if (renderFlags.edgesTransparent) {\n normalEdgesTransparentBin[normalEdgesTransparentBinLen++] = drawable;\n }\n\n if (renderFlags.selectedEdgesTransparent) {\n selectedEdgesTransparentBin[selectedEdgesTransparentBinLen++] = drawable;\n }\n\n if (renderFlags.selectedEdgesOpaque) {\n selectedEdgesOpaqueBin[selectedEdgesOpaqueBinLen++] = drawable;\n }\n\n if (renderFlags.xrayedEdgesTransparent) {\n xrayEdgesTransparentBin[xrayEdgesTransparentBinLen++] = drawable;\n }\n\n if (renderFlags.xrayedEdgesOpaque) {\n xrayEdgesOpaqueBin[xrayEdgesOpaqueBinLen++] = drawable;\n }\n\n if (renderFlags.highlightedEdgesTransparent) {\n highlightedEdgesTransparentBin[highlightedEdgesTransparentBinLen++] = drawable;\n }\n\n if (renderFlags.highlightedEdgesOpaque) {\n highlightedEdgesOpaqueBin[highlightedEdgesOpaqueBinLen++] = drawable;\n }\n }\n }\n }\n }\n\n //------------------------------------------------------------------------------------------------------\n // Render deferred bins\n //------------------------------------------------------------------------------------------------------\n\n // Opaque color with SAO\n\n if (normalDrawSAOBinLen > 0) {\n frameCtx.withSAO = true;\n for (i = 0; i < normalDrawSAOBinLen; i++) {\n normalDrawSAOBin[i].drawColorOpaque(frameCtx);\n }\n }\n\n // Opaque edges\n\n if (normalEdgesOpaqueBinLen > 0) {\n for (i = 0; i < normalEdgesOpaqueBinLen; i++) {\n normalEdgesOpaqueBin[i].drawEdgesColorOpaque(frameCtx);\n }\n }\n\n // Opaque X-ray fill\n\n if (xrayedFillOpaqueBinLen > 0) {\n for (i = 0; i < xrayedFillOpaqueBinLen; i++) {\n xrayedFillOpaqueBin[i].drawSilhouetteXRayed(frameCtx);\n }\n }\n\n // Opaque X-ray edges\n\n if (xrayEdgesOpaqueBinLen > 0) {\n for (i = 0; i < xrayEdgesOpaqueBinLen; i++) {\n xrayEdgesOpaqueBin[i].drawEdgesXRayed(frameCtx);\n }\n }\n\n // Transparent\n\n if (xrayedFillTransparentBinLen > 0 || xrayEdgesTransparentBinLen > 0 || normalFillTransparentBinLen > 0 || normalEdgesTransparentBinLen > 0) {\n gl.enable(gl.CULL_FACE);\n gl.enable(gl.BLEND);\n if (canvasTransparent) {\n gl.blendEquation(gl.FUNC_ADD);\n gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n } else {\n gl.blendEquation(gl.FUNC_ADD);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n }\n frameCtx.backfaces = false;\n if (!alphaDepthMask) {\n gl.depthMask(false);\n }\n\n // Transparent color edges\n\n if (normalFillTransparentBinLen > 0 || normalEdgesTransparentBinLen > 0) {\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n }\n if (normalEdgesTransparentBinLen > 0) {\n for (i = 0; i < normalEdgesTransparentBinLen; i++) {\n drawable = normalEdgesTransparentBin[i];\n drawable.drawEdgesColorTransparent(frameCtx);\n }\n }\n\n // Transparent color fill\n\n if (normalFillTransparentBinLen > 0) {\n for (i = 0; i < normalFillTransparentBinLen; i++) {\n drawable = normalFillTransparentBin[i];\n drawable.drawColorTransparent(frameCtx);\n }\n }\n\n // Transparent X-ray edges\n\n if (xrayEdgesTransparentBinLen > 0) {\n for (i = 0; i < xrayEdgesTransparentBinLen; i++) {\n xrayEdgesTransparentBin[i].drawEdgesXRayed(frameCtx);\n }\n }\n\n // Transparent X-ray fill\n\n if (xrayedFillTransparentBinLen > 0) {\n for (i = 0; i < xrayedFillTransparentBinLen; i++) {\n xrayedFillTransparentBin[i].drawSilhouetteXRayed(frameCtx);\n }\n }\n\n gl.disable(gl.BLEND);\n if (!alphaDepthMask) {\n gl.depthMask(true);\n }\n }\n\n // Opaque highlight\n\n if (highlightedFillOpaqueBinLen > 0 || highlightedEdgesOpaqueBinLen > 0) {\n frameCtx.lastProgramId = null;\n if (scene.highlightMaterial.glowThrough) {\n gl.clear(gl.DEPTH_BUFFER_BIT);\n }\n\n // Opaque highlighted edges\n\n if (highlightedEdgesOpaqueBinLen > 0) {\n for (i = 0; i < highlightedEdgesOpaqueBinLen; i++) {\n highlightedEdgesOpaqueBin[i].drawEdgesHighlighted(frameCtx);\n }\n }\n\n // Opaque highlighted fill\n\n if (highlightedFillOpaqueBinLen > 0) {\n for (i = 0; i < highlightedFillOpaqueBinLen; i++) {\n highlightedFillOpaqueBin[i].drawSilhouetteHighlighted(frameCtx);\n }\n }\n }\n\n // Highlighted transparent\n\n if (highlightedFillTransparentBinLen > 0 || highlightedEdgesTransparentBinLen > 0 || highlightedFillOpaqueBinLen > 0) {\n frameCtx.lastProgramId = null;\n if (scene.selectedMaterial.glowThrough) {\n gl.clear(gl.DEPTH_BUFFER_BIT);\n }\n gl.enable(gl.BLEND);\n if (canvasTransparent) {\n gl.blendEquation(gl.FUNC_ADD);\n gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n } else {\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n }\n gl.enable(gl.CULL_FACE);\n\n // Highlighted transparent edges\n\n if (highlightedEdgesTransparentBinLen > 0) {\n for (i = 0; i < highlightedEdgesTransparentBinLen; i++) {\n highlightedEdgesTransparentBin[i].drawEdgesHighlighted(frameCtx);\n }\n }\n\n // Highlighted transparent fill\n\n if (highlightedFillTransparentBinLen > 0) {\n for (i = 0; i < highlightedFillTransparentBinLen; i++) {\n highlightedFillTransparentBin[i].drawSilhouetteHighlighted(frameCtx);\n }\n }\n gl.disable(gl.BLEND);\n }\n\n // Selected opaque\n\n if (selectedFillOpaqueBinLen > 0 || selectedEdgesOpaqueBinLen > 0) {\n frameCtx.lastProgramId = null;\n if (scene.selectedMaterial.glowThrough) {\n gl.clear(gl.DEPTH_BUFFER_BIT);\n }\n\n // Selected opaque fill\n\n if (selectedEdgesOpaqueBinLen > 0) {\n for (i = 0; i < selectedEdgesOpaqueBinLen; i++) {\n selectedEdgesOpaqueBin[i].drawEdgesSelected(frameCtx);\n }\n }\n\n // Selected opaque edges\n\n if (selectedFillOpaqueBinLen > 0) {\n for (i = 0; i < selectedFillOpaqueBinLen; i++) {\n selectedFillOpaqueBin[i].drawSilhouetteSelected(frameCtx);\n }\n }\n }\n\n // Selected transparent\n\n if (selectedFillTransparentBinLen > 0 || selectedEdgesTransparentBinLen > 0) {\n frameCtx.lastProgramId = null;\n if (scene.selectedMaterial.glowThrough) {\n gl.clear(gl.DEPTH_BUFFER_BIT);\n }\n gl.enable(gl.CULL_FACE);\n gl.enable(gl.BLEND);\n if (canvasTransparent) {\n gl.blendEquation(gl.FUNC_ADD);\n gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n } else {\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n }\n\n // Selected transparent edges\n\n if (selectedEdgesTransparentBinLen > 0) {\n for (i = 0; i < selectedEdgesTransparentBinLen; i++) {\n selectedEdgesTransparentBin[i].drawEdgesSelected(frameCtx);\n }\n }\n\n // Selected transparent fill\n\n if (selectedFillTransparentBinLen > 0) {\n for (i = 0; i < selectedFillTransparentBinLen; i++) {\n selectedFillTransparentBin[i].drawSilhouetteSelected(frameCtx);\n }\n }\n gl.disable(gl.BLEND);\n }\n\n const endTime = Date.now();\n const frameStats = stats.frame;\n\n frameStats.renderTime = (endTime - startTime) / 1000.0;\n frameStats.drawElements = frameCtx.drawElements;\n frameStats.drawArrays = frameCtx.drawArrays;\n frameStats.useProgram = frameCtx.useProgram;\n frameStats.bindTexture = frameCtx.bindTexture;\n frameStats.bindArray = frameCtx.bindArray;\n\n const numTextureUnits = WEBGL_INFO.MAX_TEXTURE_IMAGE_UNITS;\n for (let ii = 0; ii < numTextureUnits; ii++) {\n gl.activeTexture(gl.TEXTURE0 + ii);\n }\n gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);\n gl.bindTexture(gl.TEXTURE_2D, null);\n\n const numVertexAttribs = WEBGL_INFO.MAX_VERTEX_ATTRIBS; // Fixes https://github.com/xeokit/xeokit-sdk/issues/174\n for (let ii = 0; ii < numVertexAttribs; ii++) {\n gl.disableVertexAttribArray(ii);\n }\n }\n\n /**\n * Picks an Entity.\n * @private\n */\n this.pick = (function () {\n\n const tempVec3a = math.vec3();\n math.mat4();\n const tempMat4b = math.mat4();\n\n const randomVec3 = math.vec3();\n const up = math.vec3([0, 1, 0]);\n const _pickResult = new PickResult();\n\n const nearAndFar = math.vec2();\n\n const canvasPos = math.vec3();\n\n const worldRayOrigin = math.vec3();\n const worldRayDir = math.vec3();\n math.vec3();\n math.vec3();\n\n return function (params, pickResult = _pickResult) {\n\n pickResult.reset();\n\n updateDrawlist();\n\n let look;\n let pickViewMatrix = null;\n let pickProjMatrix = null;\n\n pickResult.pickSurface = params.pickSurface;\n\n if (params.canvasPos) {\n\n canvasPos[0] = params.canvasPos[0];\n canvasPos[1] = params.canvasPos[1];\n\n pickViewMatrix = scene.camera.viewMatrix;\n pickProjMatrix = scene.camera.projMatrix;\n\n pickResult.canvasPos = params.canvasPos;\n\n } else {\n\n // Picking with arbitrary World-space ray\n // Align camera along ray and fire ray through center of canvas\n\n if (params.matrix) {\n\n pickViewMatrix = params.matrix;\n pickProjMatrix = scene.camera.projMatrix;\n\n } else {\n\n worldRayOrigin.set(params.origin || [0, 0, 0]);\n worldRayDir.set(params.direction || [0, 0, 1]);\n\n look = math.addVec3(worldRayOrigin, worldRayDir, tempVec3a);\n\n randomVec3[0] = Math.random();\n randomVec3[1] = Math.random();\n randomVec3[2] = Math.random();\n\n math.normalizeVec3(randomVec3);\n math.cross3Vec3(worldRayDir, randomVec3, up);\n\n pickViewMatrix = math.lookAtMat4v(worldRayOrigin, look, up, tempMat4b);\n // pickProjMatrix = scene.camera.projMatrix;\n pickProjMatrix = scene.camera.ortho.matrix;\n\n pickResult.origin = worldRayOrigin;\n pickResult.direction = worldRayDir;\n }\n\n canvasPos[0] = canvas.clientWidth * 0.5;\n canvasPos[1] = canvas.clientHeight * 0.5;\n }\n\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n const drawableList = drawableTypeInfo[type].drawableList;\n for (let i = 0, len = drawableList.length; i < len; i++) {\n const drawable = drawableList[i];\n if (drawable.setPickMatrices) { // Eg. SceneModel, which needs pre-loading into texture\n drawable.setPickMatrices(pickViewMatrix, pickProjMatrix);\n }\n }\n }\n }\n\n const pickBuffer = renderBufferManager.getRenderBuffer(\"pick\", {size: [1, 1]});\n\n pickBuffer.bind();\n\n const pickable = gpuPickPickable(pickBuffer, canvasPos, pickViewMatrix, pickProjMatrix, params, pickResult);\n\n if (!pickable) {\n pickBuffer.unbind();\n return null;\n }\n\n const pickedEntity = (pickable.delegatePickedEntity) ? pickable.delegatePickedEntity() : pickable;\n\n if (!pickedEntity) {\n pickBuffer.unbind();\n return null;\n }\n\n if (params.pickSurface) {\n\n // GPU-based ray-picking\n\n if (pickable.canPickTriangle && pickable.canPickTriangle()) {\n\n gpuPickTriangle(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);\n\n pickable.pickTriangleSurface(pickViewMatrix, pickProjMatrix, pickResult);\n\n pickResult.pickSurfacePrecision = false;\n\n } else {\n\n if (pickable.canPickWorldPos && pickable.canPickWorldPos()) {\n\n nearAndFar[0] = scene.camera.project.near;\n nearAndFar[1] = scene.camera.project.far;\n\n gpuPickWorldPos(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult);\n\n if (params.pickSurfaceNormal !== false) {\n gpuPickWorldNormal(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);\n }\n\n pickResult.pickSurfacePrecision = false;\n }\n }\n }\n pickBuffer.unbind();\n pickResult.entity = pickedEntity;\n return pickResult;\n };\n })();\n\n function gpuPickPickable(pickBuffer, canvasPos, pickViewMatrix, pickProjMatrix, params, pickResult) {\n\n const resolutionScale = scene.canvas.resolutionScale;\n\n frameCtx.reset();\n frameCtx.backfaces = true;\n frameCtx.frontface = true; // \"ccw\"\n frameCtx.pickOrigin = pickResult.origin;\n frameCtx.pickViewMatrix = pickViewMatrix;\n frameCtx.pickProjMatrix = pickProjMatrix;\n frameCtx.pickInvisible = !!params.pickInvisible;\n frameCtx.pickClipPos = [\n getClipPosX(canvasPos[0] * resolutionScale, gl.drawingBufferWidth),\n getClipPosY(canvasPos[1] * resolutionScale, gl.drawingBufferHeight),\n ];\n\n gl.viewport(0, 0, 1, 1);\n gl.depthMask(true);\n gl.enable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.BLEND);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n const includeEntityIds = params.includeEntityIds;\n const excludeEntityIds = params.excludeEntityIds;\n\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n\n const drawableInfo = drawableTypeInfo[type];\n const drawableList = drawableInfo.drawableList;\n\n for (let i = 0, len = drawableList.length; i < len; i++) {\n\n const drawable = drawableList[i];\n\n if (!drawable.drawPickMesh || (params.pickInvisible !== true && drawable.visible === false) || drawable.pickable === false) {\n continue;\n }\n if (includeEntityIds && !includeEntityIds[drawable.id]) { // TODO: push this logic into drawable\n continue;\n }\n if (excludeEntityIds && excludeEntityIds[drawable.id]) {\n continue;\n }\n\n drawable.drawPickMesh(frameCtx);\n }\n }\n }\n const pix = pickBuffer.read(0, 0);\n const pickID = pix[0] + (pix[1] << 8) + (pix[2] << 16) + (pix[3] << 24);\n\n if (pickID < 0) {\n return;\n }\n\n const pickable = pickIDs.items[pickID];\n\n return pickable;\n }\n\n function gpuPickTriangle(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult) {\n\n if (!pickable.drawPickTriangles) {\n return;\n }\n\n const resolutionScale = scene.canvas.resolutionScale;\n\n frameCtx.reset();\n frameCtx.backfaces = true;\n frameCtx.frontface = true; // \"ccw\"\n frameCtx.pickOrigin = pickResult.origin;\n frameCtx.pickViewMatrix = pickViewMatrix; // Can be null\n frameCtx.pickProjMatrix = pickProjMatrix; // Can be null\n // frameCtx.pickInvisible = !!params.pickInvisible;\n frameCtx.pickClipPos = [\n getClipPosX(canvasPos[0] * resolutionScale, gl.drawingBufferWidth),\n getClipPosY(canvasPos[1] * resolutionScale, gl.drawingBufferHeight),\n ];\n\n gl.viewport(0, 0, 1, 1);\n\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.BLEND);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n pickable.drawPickTriangles(frameCtx);\n\n const pix = pickBuffer.read(0, 0);\n\n let primIndex = pix[0] + (pix[1] * 256) + (pix[2] * 256 * 256) + (pix[3] * 256 * 256 * 256);\n\n primIndex *= 3; // Convert from triangle number to first vertex in indices\n\n pickResult.primIndex = primIndex;\n }\n\n const gpuPickWorldPos = (function () {\n\n const tempVec4a = math.vec4();\n const tempVec4b = math.vec4();\n const tempVec4c = math.vec4();\n const tempVec4d = math.vec4();\n const tempVec4e = math.vec4();\n const tempMat4a = math.mat4();\n const tempMat4b = math.mat4();\n const tempMat4c = math.mat4();\n\n return function (pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult) {\n\n const resolutionScale = scene.canvas.resolutionScale;\n\n frameCtx.reset();\n frameCtx.backfaces = true;\n frameCtx.frontface = true; // \"ccw\"\n frameCtx.pickOrigin = pickResult.origin;\n frameCtx.pickViewMatrix = pickViewMatrix;\n frameCtx.pickProjMatrix = pickProjMatrix;\n frameCtx.pickZNear = nearAndFar[0];\n frameCtx.pickZFar = nearAndFar[1];\n frameCtx.pickElementsCount = pickable.pickElementsCount;\n frameCtx.pickElementsOffset = pickable.pickElementsOffset;\n frameCtx.pickClipPos = [\n getClipPosX(canvasPos[0] * resolutionScale, gl.drawingBufferWidth),\n getClipPosY(canvasPos[1] * resolutionScale, gl.drawingBufferHeight),\n ];\n\n gl.viewport(0, 0, 1, 1);\n\n gl.clearColor(0, 0, 0, 0);\n gl.depthMask(true);\n gl.enable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.BLEND);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n pickable.drawPickDepths(frameCtx); // Draw color-encoded fragment screen-space depths\n\n const pix = pickBuffer.read(0, 0);\n\n const screenZ = unpackDepth(pix); // Get screen-space Z at the given canvas coords\n\n // Calculate clip space coordinates, which will be in range of x=[-1..1] and y=[-1..1], with y=(+1) at top\n const x = (canvasPos[0] - canvas.clientWidth / 2) / (canvas.clientWidth / 2);\n const y = -(canvasPos[1] - canvas.clientHeight / 2) / (canvas.clientHeight / 2);\n\n const origin = pickable.origin;\n let pvMat;\n\n if (origin) {\n const rtcPickViewMat = createRTCViewMat(pickViewMatrix, origin, tempMat4a);\n pvMat = math.mulMat4(pickProjMatrix, rtcPickViewMat, tempMat4b);\n\n } else {\n pvMat = math.mulMat4(pickProjMatrix, pickViewMatrix, tempMat4b);\n }\n\n const pvMatInverse = math.inverseMat4(pvMat, tempMat4c);\n\n tempVec4a[0] = x;\n tempVec4a[1] = y;\n tempVec4a[2] = -1;\n tempVec4a[3] = 1;\n\n let world1 = math.transformVec4(pvMatInverse, tempVec4a);\n world1 = math.mulVec4Scalar(world1, 1 / world1[3]);\n\n tempVec4b[0] = x;\n tempVec4b[1] = y;\n tempVec4b[2] = 1;\n tempVec4b[3] = 1;\n\n let world2 = math.transformVec4(pvMatInverse, tempVec4b);\n world2 = math.mulVec4Scalar(world2, 1 / world2[3]);\n\n const dir = math.subVec3(world2, world1, tempVec4c);\n const worldPos = math.addVec3(world1, math.mulVec4Scalar(dir, screenZ, tempVec4d), tempVec4e);\n\n if (origin) {\n math.addVec3(worldPos, origin);\n }\n\n pickResult.worldPos = worldPos;\n }\n })();\n\n function drawSnapInit(frameCtx) {\n frameCtx.snapPickLayerParams = [];\n frameCtx.snapPickLayerNumber = 0;\n for (let type in drawableTypeInfo) {\n const drawableInfo = drawableTypeInfo[type];\n const drawableList = drawableInfo.drawableList;\n for (let i = 0, len = drawableList.length; i < len; i++) {\n const drawable = drawableList[i];\n if (drawable.drawSnapInit) {\n if (!drawable.culled && drawable.visible && drawable.pickable) {\n drawable.drawSnapInit(frameCtx);\n }\n }\n }\n }\n return frameCtx.snapPickLayerParams;\n }\n\n function drawSnap(frameCtx) {\n frameCtx.snapPickLayerParams = frameCtx.snapPickLayerParams || [];\n frameCtx.snapPickLayerNumber = frameCtx.snapPickLayerParams.length;\n for (let type in drawableTypeInfo) {\n const drawableInfo = drawableTypeInfo[type];\n const drawableList = drawableInfo.drawableList;\n for (let i = 0, len = drawableList.length; i < len; i++) {\n const drawable = drawableList[i];\n if (drawable.drawSnap) {\n if (!drawable.culled && drawable.visible && drawable.pickable) {\n drawable.drawSnap(frameCtx);\n }\n }\n }\n }\n return frameCtx.snapPickLayerParams;\n }\n\n function getClipPosX(pos, size) {\n return 2 * (pos / size) - 1;\n }\n\n function getClipPosY(pos, size) {\n return 1 - 2 * (pos / size);\n }\n\n /**\n * @param {[number, number]} canvasPos\n * @param {number} [snapRadiusInPixels=30]\n * @param {boolean} [snapToVertex=true]\n * @param {boolean} [snapToEdge=true]\n * @param pickResult\n * @returns {PickResult}\n */\n this.snapPick = (function () {\n\n const _pickResult = new PickResult();\n\n return function (canvasPos, snapRadiusInPixels, snapToVertex, snapToEdge, pickResult = _pickResult) {\n\n if (!snapToVertex && !snapToEdge) {\n return this.pick({canvasPos, pickSurface: true});\n }\n\n const resolutionScale = scene.canvas.resolutionScale;\n\n frameCtx.reset();\n frameCtx.backfaces = true;\n frameCtx.frontface = true; // \"ccw\"\n frameCtx.pickZNear = scene.camera.project.near;\n frameCtx.pickZFar = scene.camera.project.far;\n\n snapRadiusInPixels = snapRadiusInPixels || 30;\n\n const vertexPickBuffer = renderBufferManager.getRenderBuffer(\"uniquePickColors-aabs\", {\n depthTexture: true,\n size: [\n 2 * snapRadiusInPixels + 1,\n 2 * snapRadiusInPixels + 1,\n ]\n });\n\n frameCtx.snapVectorA = [\n getClipPosX(canvasPos[0] * resolutionScale, gl.drawingBufferWidth),\n getClipPosY(canvasPos[1] * resolutionScale, gl.drawingBufferHeight),\n ];\n\n frameCtx.snapInvVectorAB = [\n gl.drawingBufferWidth / (2 * snapRadiusInPixels),\n gl.drawingBufferHeight / (2 * snapRadiusInPixels),\n ];\n\n // Bind and clear the snap render target\n\n vertexPickBuffer.bind(gl.RGBA32I, gl.RGBA32I, gl.RGBA8UI);\n gl.viewport(0, 0, vertexPickBuffer.size[0], vertexPickBuffer.size[1]);\n gl.enable(gl.DEPTH_TEST);\n gl.frontFace(gl.CCW);\n gl.disable(gl.CULL_FACE);\n gl.depthMask(true);\n gl.disable(gl.BLEND);\n gl.depthFunc(gl.LEQUAL);\n gl.clear(gl.DEPTH_BUFFER_BIT);\n gl.clearBufferiv(gl.COLOR, 0, new Int32Array([0, 0, 0, 0]));\n gl.clearBufferiv(gl.COLOR, 1, new Int32Array([0, 0, 0, 0]));\n gl.clearBufferuiv(gl.COLOR, 2, new Uint32Array([0, 0, 0, 0]));\n\n //////////////////////////////////\n // Set view and proj mats for VBO renderers\n ///////////////////////////////////////\n\n const pickViewMatrix = scene.camera.viewMatrix;\n const pickProjMatrix = scene.camera.projMatrix;\n\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n const drawableList = drawableTypeInfo[type].drawableList;\n for (let i = 0, len = drawableList.length; i < len; i++) {\n const drawable = drawableList[i];\n if (drawable.setPickMatrices) { // Eg. SceneModel, which needs pre-loading into texture\n drawable.setPickMatrices(pickViewMatrix, pickProjMatrix);\n }\n }\n }\n }\n\n // a) init z-buffer\n gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);\n const layerParamsSurface = drawSnapInit(frameCtx);\n\n // b) snap-pick\n const layerParamsSnap = [];\n frameCtx.snapPickLayerParams = layerParamsSnap;\n\n gl.depthMask(false);\n gl.drawBuffers([gl.COLOR_ATTACHMENT0]);\n\n if (snapToVertex && snapToEdge) {\n frameCtx.snapMode = \"edge\";\n drawSnap(frameCtx);\n\n frameCtx.snapMode = \"vertex\";\n frameCtx.snapPickLayerNumber++;\n\n drawSnap(frameCtx);\n } else {\n frameCtx.snapMode = snapToVertex ? \"vertex\" : \"edge\";\n\n drawSnap(frameCtx);\n }\n\n gl.depthMask(true);\n\n // Read and decode the snapped coordinates\n\n const snapPickResultArray = vertexPickBuffer.readArray(gl.RGBA_INTEGER, gl.INT, Int32Array, 4);\n const snapPickNormalResultArray = vertexPickBuffer.readArray(gl.RGBA_INTEGER, gl.INT, Int32Array, 4, 1);\n const snapPickIdResultArray = vertexPickBuffer.readArray(gl.RGBA_INTEGER, gl.UNSIGNED_INT, Uint32Array, 4, 2);\n\n vertexPickBuffer.unbind();\n\n // result 1) regular hi-precision world position\n\n let worldPos = null;\n\n const middleX = snapRadiusInPixels;\n const middleY = snapRadiusInPixels;\n const middleIndex = (middleX * 4) + (middleY * vertexPickBuffer.size[0] * 4);\n const pickResultMiddleXY = snapPickResultArray.slice(middleIndex, middleIndex + 4);\n const pickNormalResultMiddleXY = snapPickNormalResultArray.slice(middleIndex, middleIndex + 4);\n const pickPickableResultMiddleXY = snapPickIdResultArray.slice(middleIndex, middleIndex + 4);\n\n if (pickResultMiddleXY[3] !== 0) {\n const pickedLayerParmasSurface = layerParamsSurface[Math.abs(pickResultMiddleXY[3]) % layerParamsSurface.length];\n const origin = pickedLayerParmasSurface.origin;\n const scale = pickedLayerParmasSurface.coordinateScale;\n worldPos = [\n pickResultMiddleXY[0] * scale[0] + origin[0],\n pickResultMiddleXY[1] * scale[1] + origin[1],\n pickResultMiddleXY[2] * scale[2] + origin[2],\n ];\n math.normalizeVec3([\n pickNormalResultMiddleXY[0] / math.MAX_INT,\n pickNormalResultMiddleXY[1] / math.MAX_INT,\n pickNormalResultMiddleXY[2] / math.MAX_INT,\n ]);\n\n const pickID =\n pickPickableResultMiddleXY[0]\n + (pickPickableResultMiddleXY[1] << 8)\n + (pickPickableResultMiddleXY[2] << 16)\n + (pickPickableResultMiddleXY[3] << 24);\n\n pickIDs.items[pickID];\n }\n\n // result 2) hi-precision snapped (to vertex/edge) world position\n\n let snapPickResult = [];\n\n for (let i = 0; i < snapPickResultArray.length; i += 4) {\n if (snapPickResultArray[i + 3] > 0) {\n const pixelNumber = Math.floor(i / 4);\n const w = vertexPickBuffer.size[0];\n const x = pixelNumber % w - Math.floor(w / 2);\n const y = Math.floor(pixelNumber / w) - Math.floor(w / 2);\n const dist = (Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));\n snapPickResult.push({\n x,\n y,\n dist,\n isVertex: snapToVertex && snapToEdge ? snapPickResultArray[i + 3] > layerParamsSnap.length / 2 : snapToVertex,\n result: [\n snapPickResultArray[i + 0],\n snapPickResultArray[i + 1],\n snapPickResultArray[i + 2],\n snapPickResultArray[i + 3],\n ],\n normal: [\n snapPickNormalResultArray[i + 0],\n snapPickNormalResultArray[i + 1],\n snapPickNormalResultArray[i + 2],\n snapPickNormalResultArray[i + 3],\n ],\n id: [\n snapPickIdResultArray[i + 0],\n snapPickIdResultArray[i + 1],\n snapPickIdResultArray[i + 2],\n snapPickIdResultArray[i + 3],\n ]\n });\n }\n }\n\n let snappedWorldPos = null;\n let snappedWorldNormal = null;\n let snappedPickable = null;\n let snapType = null;\n\n if (snapPickResult.length > 0) {\n // vertex snap first, then edge snap\n snapPickResult.sort((a, b) => {\n if (a.isVertex !== b.isVertex) {\n return a.isVertex ? -1 : 1;\n } else {\n return a.dist - b.dist;\n }\n });\n\n snapType = snapPickResult[0].isVertex ? \"vertex\" : \"edge\";\n const snapPick = snapPickResult[0].result;\n const snapPickNormal = snapPickResult[0].normal;\n const snapPickId = snapPickResult[0].id;\n\n const pickedLayerParmas = layerParamsSnap[snapPick[3]];\n\n const origin = pickedLayerParmas.origin;\n const scale = pickedLayerParmas.coordinateScale;\n\n snappedWorldNormal = math.normalizeVec3([\n snapPickNormal[0] / math.MAX_INT,\n snapPickNormal[1] / math.MAX_INT,\n snapPickNormal[2] / math.MAX_INT,\n ]);\n\n snappedWorldPos = [\n snapPick[0] * scale[0] + origin[0],\n snapPick[1] * scale[1] + origin[1],\n snapPick[2] * scale[2] + origin[2],\n ];\n\n snappedPickable = pickIDs.items[\n snapPickId[0]\n + (snapPickId[1] << 8)\n + (snapPickId[2] << 16)\n + (snapPickId[3] << 24)\n ];\n }\n\n if (null === worldPos && null == snappedWorldPos) { // If neither regular pick or snap pick, return null\n return null;\n }\n\n let snappedCanvasPos = null;\n\n if (null !== snappedWorldPos) {\n snappedCanvasPos = scene.camera.projectWorldPos(snappedWorldPos);\n }\n\n const snappedEntity = (snappedPickable && snappedPickable.delegatePickedEntity) ? snappedPickable.delegatePickedEntity() : snappedPickable;\n\n pickResult.reset();\n pickResult.snappedToEdge = (snapType === \"edge\");\n pickResult.snappedToVertex = (snapType === \"vertex\");\n pickResult.worldPos = snappedWorldPos;\n pickResult.worldNormal = snappedWorldNormal;\n pickResult.entity = snappedEntity;\n pickResult.canvasPos = canvasPos;\n pickResult.snappedCanvasPos = snappedCanvasPos || canvasPos;\n\n return pickResult;\n };\n })();\n\n function unpackDepth(depthZ) {\n const vec = [depthZ[0] / 256.0, depthZ[1] / 256.0, depthZ[2] / 256.0, depthZ[3] / 256.0];\n const bitShift = [1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0];\n return math.dotVec4(vec, bitShift);\n }\n\n function gpuPickWorldNormal(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult) {\n\n const resolutionScale = scene.canvas.resolutionScale;\n\n frameCtx.reset();\n frameCtx.backfaces = true;\n frameCtx.frontface = true; // \"ccw\"\n frameCtx.pickOrigin = pickResult.origin;\n frameCtx.pickViewMatrix = pickViewMatrix;\n frameCtx.pickProjMatrix = pickProjMatrix;\n frameCtx.pickClipPos = [\n getClipPosX(canvasPos[0] * resolutionScale, gl.drawingBufferWidth),\n getClipPosY(canvasPos[1] * resolutionScale, gl.drawingBufferHeight),\n ];\n\n const pickNormalBuffer = renderBufferManager.getRenderBuffer(\"pick-normal\", {size: [3, 3]});\n\n pickNormalBuffer.bind(gl.RGBA32I);\n\n gl.viewport(0, 0, pickNormalBuffer.size[0], pickNormalBuffer.size[1]);\n\n gl.enable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.BLEND);\n gl.clear(gl.DEPTH_BUFFER_BIT);\n gl.clearBufferiv(gl.COLOR, 0, new Int32Array([0, 0, 0, 0]));\n\n pickable.drawPickNormals(frameCtx); // Draw color-encoded fragment World-space normals\n\n const pix = pickNormalBuffer.read(1, 1, gl.RGBA_INTEGER, gl.INT, Int32Array, 4);\n\n pickNormalBuffer.unbind();\n\n const worldNormal = [\n pix[0] / math.MAX_INT,\n pix[1] / math.MAX_INT,\n pix[2] / math.MAX_INT,\n ];\n\n math.normalizeVec3(worldNormal);\n\n pickResult.worldNormal = worldNormal;\n }\n\n /**\n * Adds a {@link Marker} for occlusion testing.\n * @param marker\n */\n this.addMarker = function (marker) {\n this._occlusionTester = this._occlusionTester || new OcclusionTester(scene, renderBufferManager);\n this._occlusionTester.addMarker(marker);\n scene.occlusionTestCountdown = 0;\n };\n\n /**\n * Notifies that a {@link Marker#worldPos} has updated.\n * @param marker\n */\n this.markerWorldPosUpdated = function (marker) {\n this._occlusionTester.markerWorldPosUpdated(marker);\n };\n\n /**\n * Removes a {@link Marker} from occlusion testing.\n * @param marker\n */\n this.removeMarker = function (marker) {\n this._occlusionTester.removeMarker(marker);\n };\n\n /**\n * Performs an occlusion test for all added {@link Marker}s, updating\n * their {@link Marker#visible} properties accordingly.\n */\n this.doOcclusionTest = function () {\n\n if (this._occlusionTester && this._occlusionTester.needOcclusionTest) {\n\n updateDrawlist();\n\n this._occlusionTester.bindRenderBuf();\n\n frameCtx.reset();\n frameCtx.backfaces = true;\n frameCtx.frontface = true; // \"ccw\"\n\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.BLEND);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n for (let type in drawableTypeInfo) {\n if (drawableTypeInfo.hasOwnProperty(type)) {\n const drawableInfo = drawableTypeInfo[type];\n const drawableList = drawableInfo.drawableList;\n for (let i = 0, len = drawableList.length; i < len; i++) {\n const drawable = drawableList[i];\n if (!drawable.drawOcclusion || drawable.culled === true || drawable.visible === false || drawable.pickable === false) { // TODO: Option to exclude transparent?\n continue;\n }\n\n drawable.drawOcclusion(frameCtx);\n }\n }\n }\n\n this._occlusionTester.drawMarkers(frameCtx);\n this._occlusionTester.doOcclusionTest(); // Updates Marker \"visible\" properties\n this._occlusionTester.unbindRenderBuf();\n }\n };\n\n /**\n * Read pixels from the renderer's current output. Performs a force-render first.\n * @param pixels\n * @param colors\n * @param len\n * @param opaqueOnly\n * @private\n */\n this.readPixels = function (pixels, colors, len, opaqueOnly) {\n const snapshotBuffer = renderBufferManager.getRenderBuffer(\"snapshot\");\n snapshotBuffer.bind();\n snapshotBuffer.clear();\n this.render({force: true, opaqueOnly: opaqueOnly});\n let color;\n let i;\n let j;\n let k;\n for (i = 0; i < len; i++) {\n j = i * 2;\n k = i * 4;\n color = snapshotBuffer.read(pixels[j], pixels[j + 1]);\n colors[k] = color[0];\n colors[k + 1] = color[1];\n colors[k + 2] = color[2];\n colors[k + 3] = color[3];\n }\n snapshotBuffer.unbind();\n imageDirty = true;\n };\n\n /**\n * Enter snapshot mode.\n *\n * Switches rendering to a hidden snapshot canvas.\n *\n * Exit snapshot mode using endSnapshot().\n */\n this.beginSnapshot = function (params = {}) {\n const snapshotBuffer = renderBufferManager.getRenderBuffer(\"snapshot\");\n if (params.width && params.height) {\n snapshotBuffer.setSize([params.width, params.height]);\n }\n snapshotBuffer.bind();\n snapshotBuffer.clear();\n snapshotBound = true;\n };\n\n /**\n * When in snapshot mode, renders a frame of the current Scene state to the snapshot canvas.\n */\n this.renderSnapshot = function () {\n if (!snapshotBound) {\n return;\n }\n const snapshotBuffer = renderBufferManager.getRenderBuffer(\"snapshot\");\n snapshotBuffer.clear();\n this.render({force: true, opaqueOnly: false});\n imageDirty = true;\n };\n\n /**\n * When in snapshot mode, gets an image of the snapshot canvas.\n *\n * @private\n * @returns {String} The image data URI.\n */\n this.readSnapshot = function (params) {\n const snapshotBuffer = renderBufferManager.getRenderBuffer(\"snapshot\");\n return snapshotBuffer.readImage(params);\n };\n\n /**\n * Returns an HTMLCanvas containing an image of the snapshot canvas.\n *\n * - The HTMLCanvas has a CanvasRenderingContext2D.\n * - Expects the caller to draw more things on the HTMLCanvas (annotations etc).\n *\n * @returns {HTMLCanvasElement}\n */\n this.readSnapshotAsCanvas = function () {\n const snapshotBuffer = renderBufferManager.getRenderBuffer(\"snapshot\");\n return snapshotBuffer.readImageAsCanvas();\n };\n\n /**\n * Exists snapshot mode.\n *\n * Switches rendering back to the main canvas.\n */\n this.endSnapshot = function () {\n if (!snapshotBound) {\n return;\n }\n const snapshotBuffer = renderBufferManager.getRenderBuffer(\"snapshot\");\n snapshotBuffer.unbind();\n snapshotBound = false;\n };\n\n /**\n * Destroys this renderer.\n * @private\n */\n this.destroy = function () {\n\n drawableTypeInfo = {};\n drawables = {};\n\n renderBufferManager.destroy();\n\n saoOcclusionRenderer.destroy();\n saoDepthLimitedBlurRenderer.destroy();\n\n if (this._occlusionTester) {\n this._occlusionTester.destroy();\n }\n };\n};\n\n/**\n * @desc Meditates mouse, touch and keyboard events for various interaction controls.\n *\n * Ordinarily, you would only use this component as a utility to help manage input events and state for your\n * own custom input handlers.\n *\n * * Located at {@link Scene#input}\n * * Used by (at least) {@link CameraControl}\n *\n * ## Usage\n *\n * Subscribing to mouse events on the canvas:\n *\n * ````javascript\n * import {Viewer} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * const input = viewer.scene.input;\n *\n * const onMouseDown = input.on(\"mousedown\", (canvasCoords) => {\n * console.log(\"Mouse down at: x=\" + canvasCoords[0] + \", y=\" + coords[1]);\n * });\n *\n * const onMouseUp = input.on(\"mouseup\", (canvasCoords) => {\n * console.log(\"Mouse up at: x=\" + canvasCoords[0] + \", y=\" + canvasCoords[1]);\n * });\n *\n * const onMouseClicked = input.on(\"mouseclicked\", (canvasCoords) => {\n * console.log(\"Mouse clicked at: x=\" + canvasCoords[0] + \", y=\" + canvasCoords[1]);\n * });\n *\n * const onDblClick = input.on(\"dblclick\", (canvasCoords) => {\n * console.log(\"Double-click at: x=\" + canvasCoords[0] + \", y=\" + canvasCoords[1]);\n * });\n * ````\n *\n * Subscribing to keyboard events on the canvas:\n *\n * ````javascript\n * const onKeyDown = input.on(\"keydown\", (keyCode) => {\n * switch (keyCode) {\n * case this.KEY_A:\n * console.log(\"The 'A' key is down\");\n * break;\n *\n * case this.KEY_B:\n * console.log(\"The 'B' key is down\");\n * break;\n *\n * case this.KEY_C:\n * console.log(\"The 'C' key is down\");\n * break;\n *\n * default:\n * console.log(\"Some other key is down\");\n * }\n * });\n *\n * const onKeyUp = input.on(\"keyup\", (keyCode) => {\n * switch (keyCode) {\n * case this.KEY_A:\n * console.log(\"The 'A' key is up\");\n * break;\n *\n * case this.KEY_B:\n * console.log(\"The 'B' key is up\");\n * break;\n *\n * case this.KEY_C:\n * console.log(\"The 'C' key is up\");\n * break;\n *\n * default:\n * console.log(\"Some other key is up\");\n * }\n * });\n * ````\n *\n * Checking if keys are down:\n *\n * ````javascript\n * const isCtrlDown = input.ctrlDown;\n * const isAltDown = input.altDown;\n * const shiftDown = input.shiftDown;\n * //...\n *\n * const isAKeyDown = input.keyDown[input.KEY_A];\n * const isBKeyDown = input.keyDown[input.KEY_B];\n * const isShiftKeyDown = input.keyDown[input.KEY_SHIFT];\n * //...\n *\n * ````\n * Unsubscribing from events:\n *\n * ````javascript\n * input.off(onMouseDown);\n * input.off(onMouseUp);\n * //...\n * ````\n *\n * ## Disabling all events\n *\n * Event handling is enabled by default.\n *\n * To disable all events:\n *\n * ````javascript\n * myViewer.scene.input.setEnabled(false);\n * ````\n * To enable all events again:\n *\n * ````javascript\n * myViewer.scene.input.setEnabled(true);\n * ````\n *\n * ## Disabling keyboard input\n *\n * When the mouse is over the canvas, the canvas will consume keyboard events. Therefore, sometimes we need\n * to disable keyboard control, so that other UI elements can get those events.\n *\n * To disable keyboard events:\n *\n * ````javascript\n * myViewer.scene.input.setKeyboardEnabled(false);\n * ````\n *\n * To enable keyboard events again:\n *\n * ````javascript\n * myViewer.scene.input.setKeyboardEnabled(true)\n * ````\n */\nclass Input extends Component {\n\n /**\n * @private\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n /**\n * Code for the BACKSPACE key.\n * @property KEY_BACKSPACE\n * @final\n * @type {Number}\n */\n this.KEY_BACKSPACE = 8;\n\n /**\n * Code for the TAB key.\n * @property KEY_TAB\n * @final\n * @type {Number}\n */\n this.KEY_TAB = 9;\n\n /**\n * Code for the ENTER key.\n * @property KEY_ENTER\n * @final\n * @type {Number}\n */\n this.KEY_ENTER = 13;\n\n /**\n * Code for the SHIFT key.\n * @property KEY_SHIFT\n * @final\n * @type {Number}\n */\n this.KEY_SHIFT = 16;\n\n /**\n * Code for the CTRL key.\n * @property KEY_CTRL\n * @final\n * @type {Number}\n */\n this.KEY_CTRL = 17;\n\n /**\n * Code for the ALT key.\n * @property KEY_ALT\n * @final\n * @type {Number}\n */\n this.KEY_ALT = 18;\n\n /**\n * Code for the PAUSE_BREAK key.\n * @property KEY_PAUSE_BREAK\n * @final\n * @type {Number}\n */\n this.KEY_PAUSE_BREAK = 19;\n\n /**\n * Code for the CAPS_LOCK key.\n * @property KEY_CAPS_LOCK\n * @final\n * @type {Number}\n */\n this.KEY_CAPS_LOCK = 20;\n\n /**\n * Code for the ESCAPE key.\n * @property KEY_ESCAPE\n * @final\n * @type {Number}\n */\n this.KEY_ESCAPE = 27;\n\n /**\n * Code for the PAGE_UP key.\n * @property KEY_PAGE_UP\n * @final\n * @type {Number}\n */\n this.KEY_PAGE_UP = 33;\n\n /**\n * Code for the PAGE_DOWN key.\n * @property KEY_PAGE_DOWN\n * @final\n * @type {Number}\n */\n this.KEY_PAGE_DOWN = 34;\n\n /**\n * Code for the END key.\n * @property KEY_END\n * @final\n * @type {Number}\n */\n this.KEY_END = 35;\n\n /**\n * Code for the HOME key.\n * @property KEY_HOME\n * @final\n * @type {Number}\n */\n this.KEY_HOME = 36;\n\n /**\n * Code for the LEFT_ARROW key.\n * @property KEY_LEFT_ARROW\n * @final\n * @type {Number}\n */\n this.KEY_LEFT_ARROW = 37;\n\n /**\n * Code for the UP_ARROW key.\n * @property KEY_UP_ARROW\n * @final\n * @type {Number}\n */\n this.KEY_UP_ARROW = 38;\n\n /**\n * Code for the RIGHT_ARROW key.\n * @property KEY_RIGHT_ARROW\n * @final\n * @type {Number}\n */\n this.KEY_RIGHT_ARROW = 39;\n\n /**\n * Code for the DOWN_ARROW key.\n * @property KEY_DOWN_ARROW\n * @final\n * @type {Number}\n */\n this.KEY_DOWN_ARROW = 40;\n\n /**\n * Code for the INSERT key.\n * @property KEY_INSERT\n * @final\n * @type {Number}\n */\n this.KEY_INSERT = 45;\n\n /**\n * Code for the DELETE key.\n * @property KEY_DELETE\n * @final\n * @type {Number}\n */\n this.KEY_DELETE = 46;\n\n /**\n * Code for the 0 key.\n * @property KEY_NUM_0\n * @final\n * @type {Number}\n */\n this.KEY_NUM_0 = 48;\n\n /**\n * Code for the 1 key.\n * @property KEY_NUM_1\n * @final\n * @type {Number}\n */\n this.KEY_NUM_1 = 49;\n\n /**\n * Code for the 2 key.\n * @property KEY_NUM_2\n * @final\n * @type {Number}\n */\n this.KEY_NUM_2 = 50;\n\n /**\n * Code for the 3 key.\n * @property KEY_NUM_3\n * @final\n * @type {Number}\n */\n this.KEY_NUM_3 = 51;\n\n /**\n * Code for the 4 key.\n * @property KEY_NUM_4\n * @final\n * @type {Number}\n */\n this.KEY_NUM_4 = 52;\n\n /**\n * Code for the 5 key.\n * @property KEY_NUM_5\n * @final\n * @type {Number}\n */\n this.KEY_NUM_5 = 53;\n\n /**\n * Code for the 6 key.\n * @property KEY_NUM_6\n * @final\n * @type {Number}\n */\n this.KEY_NUM_6 = 54;\n\n /**\n * Code for the 7 key.\n * @property KEY_NUM_7\n * @final\n * @type {Number}\n */\n this.KEY_NUM_7 = 55;\n\n /**\n * Code for the 8 key.\n * @property KEY_NUM_8\n * @final\n * @type {Number}\n */\n this.KEY_NUM_8 = 56;\n\n /**\n * Code for the 9 key.\n * @property KEY_NUM_9\n * @final\n * @type {Number}\n */\n this.KEY_NUM_9 = 57;\n\n /**\n * Code for the A key.\n * @property KEY_A\n * @final\n * @type {Number}\n */\n this.KEY_A = 65;\n\n /**\n * Code for the B key.\n * @property KEY_B\n * @final\n * @type {Number}\n */\n this.KEY_B = 66;\n\n /**\n * Code for the C key.\n * @property KEY_C\n * @final\n * @type {Number}\n */\n this.KEY_C = 67;\n\n /**\n * Code for the D key.\n * @property KEY_D\n * @final\n * @type {Number}\n */\n this.KEY_D = 68;\n\n /**\n * Code for the E key.\n * @property KEY_E\n * @final\n * @type {Number}\n */\n this.KEY_E = 69;\n\n /**\n * Code for the F key.\n * @property KEY_F\n * @final\n * @type {Number}\n */\n this.KEY_F = 70;\n\n /**\n * Code for the G key.\n * @property KEY_G\n * @final\n * @type {Number}\n */\n this.KEY_G = 71;\n\n /**\n * Code for the H key.\n * @property KEY_H\n * @final\n * @type {Number}\n */\n this.KEY_H = 72;\n\n /**\n * Code for the I key.\n * @property KEY_I\n * @final\n * @type {Number}\n */\n this.KEY_I = 73;\n\n /**\n * Code for the J key.\n * @property KEY_J\n * @final\n * @type {Number}\n */\n this.KEY_J = 74;\n\n /**\n * Code for the K key.\n * @property KEY_K\n * @final\n * @type {Number}\n */\n this.KEY_K = 75;\n\n /**\n * Code for the L key.\n * @property KEY_L\n * @final\n * @type {Number}\n */\n this.KEY_L = 76;\n\n /**\n * Code for the M key.\n * @property KEY_M\n * @final\n * @type {Number}\n */\n this.KEY_M = 77;\n\n /**\n * Code for the N key.\n * @property KEY_N\n * @final\n * @type {Number}\n */\n this.KEY_N = 78;\n\n /**\n * Code for the O key.\n * @property KEY_O\n * @final\n * @type {Number}\n */\n this.KEY_O = 79;\n\n /**\n * Code for the P key.\n * @property KEY_P\n * @final\n * @type {Number}\n */\n this.KEY_P = 80;\n\n /**\n * Code for the Q key.\n * @property KEY_Q\n * @final\n * @type {Number}\n */\n this.KEY_Q = 81;\n\n /**\n * Code for the R key.\n * @property KEY_R\n * @final\n * @type {Number}\n */\n this.KEY_R = 82;\n\n /**\n * Code for the S key.\n * @property KEY_S\n * @final\n * @type {Number}\n */\n this.KEY_S = 83;\n\n /**\n * Code for the T key.\n * @property KEY_T\n * @final\n * @type {Number}\n */\n this.KEY_T = 84;\n\n /**\n * Code for the U key.\n * @property KEY_U\n * @final\n * @type {Number}\n */\n this.KEY_U = 85;\n\n /**\n * Code for the V key.\n * @property KEY_V\n * @final\n * @type {Number}\n */\n this.KEY_V = 86;\n\n /**\n * Code for the W key.\n * @property KEY_W\n * @final\n * @type {Number}\n */\n this.KEY_W = 87;\n\n /**\n * Code for the X key.\n * @property KEY_X\n * @final\n * @type {Number}\n */\n this.KEY_X = 88;\n\n /**\n * Code for the Y key.\n * @property KEY_Y\n * @final\n * @type {Number}\n */\n this.KEY_Y = 89;\n\n /**\n * Code for the Z key.\n * @property KEY_Z\n * @final\n * @type {Number}\n */\n this.KEY_Z = 90;\n\n /**\n * Code for the LEFT_WINDOW key.\n * @property KEY_LEFT_WINDOW\n * @final\n * @type {Number}\n */\n this.KEY_LEFT_WINDOW = 91;\n\n /**\n * Code for the RIGHT_WINDOW key.\n * @property KEY_RIGHT_WINDOW\n * @final\n * @type {Number}\n */\n this.KEY_RIGHT_WINDOW = 92;\n\n /**\n * Code for the SELECT key.\n * @property KEY_SELECT\n * @final\n * @type {Number}\n */\n this.KEY_SELECT_KEY = 93;\n\n /**\n * Code for the number pad 0 key.\n * @property KEY_NUMPAD_0\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_0 = 96;\n\n /**\n * Code for the number pad 1 key.\n * @property KEY_NUMPAD_1\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_1 = 97;\n\n /**\n * Code for the number pad 2 key.\n * @property KEY_NUMPAD 2\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_2 = 98;\n\n /**\n * Code for the number pad 3 key.\n * @property KEY_NUMPAD_3\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_3 = 99;\n\n /**\n * Code for the number pad 4 key.\n * @property KEY_NUMPAD_4\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_4 = 100;\n\n /**\n * Code for the number pad 5 key.\n * @property KEY_NUMPAD_5\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_5 = 101;\n\n /**\n * Code for the number pad 6 key.\n * @property KEY_NUMPAD_6\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_6 = 102;\n\n /**\n * Code for the number pad 7 key.\n * @property KEY_NUMPAD_7\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_7 = 103;\n\n /**\n * Code for the number pad 8 key.\n * @property KEY_NUMPAD_8\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_8 = 104;\n\n /**\n * Code for the number pad 9 key.\n * @property KEY_NUMPAD_9\n * @final\n * @type {Number}\n */\n this.KEY_NUMPAD_9 = 105;\n\n /**\n * Code for the MULTIPLY key.\n * @property KEY_MULTIPLY\n * @final\n * @type {Number}\n */\n this.KEY_MULTIPLY = 106;\n\n /**\n * Code for the ADD key.\n * @property KEY_ADD\n * @final\n * @type {Number}\n */\n this.KEY_ADD = 107;\n\n /**\n * Code for the SUBTRACT key.\n * @property KEY_SUBTRACT\n * @final\n * @type {Number}\n */\n this.KEY_SUBTRACT = 109;\n\n /**\n * Code for the DECIMAL POINT key.\n * @property KEY_DECIMAL_POINT\n * @final\n * @type {Number}\n */\n this.KEY_DECIMAL_POINT = 110;\n\n /**\n * Code for the DIVIDE key.\n * @property KEY_DIVIDE\n * @final\n * @type {Number}\n */\n this.KEY_DIVIDE = 111;\n\n /**\n * Code for the F1 key.\n * @property KEY_F1\n * @final\n * @type {Number}\n */\n this.KEY_F1 = 112;\n\n /**\n * Code for the F2 key.\n * @property KEY_F2\n * @final\n * @type {Number}\n */\n this.KEY_F2 = 113;\n\n /**\n * Code for the F3 key.\n * @property KEY_F3\n * @final\n * @type {Number}\n */\n this.KEY_F3 = 114;\n\n /**\n * Code for the F4 key.\n * @property KEY_F4\n * @final\n * @type {Number}\n */\n this.KEY_F4 = 115;\n\n /**\n * Code for the F5 key.\n * @property KEY_F5\n * @final\n * @type {Number}\n */\n this.KEY_F5 = 116;\n\n /**\n * Code for the F6 key.\n * @property KEY_F6\n * @final\n * @type {Number}\n */\n this.KEY_F6 = 117;\n\n /**\n * Code for the F7 key.\n * @property KEY_F7\n * @final\n * @type {Number}\n */\n this.KEY_F7 = 118;\n\n /**\n * Code for the F8 key.\n * @property KEY_F8\n * @final\n * @type {Number}\n */\n this.KEY_F8 = 119;\n\n /**\n * Code for the F9 key.\n * @property KEY_F9\n * @final\n * @type {Number}\n */\n this.KEY_F9 = 120;\n\n /**\n * Code for the F10 key.\n * @property KEY_F10\n * @final\n * @type {Number}\n */\n this.KEY_F10 = 121;\n\n /**\n * Code for the F11 key.\n * @property KEY_F11\n * @final\n * @type {Number}\n */\n this.KEY_F11 = 122;\n\n /**\n * Code for the F12 key.\n * @property KEY_F12\n * @final\n * @type {Number}\n */\n this.KEY_F12 = 123;\n\n /**\n * Code for the NUM_LOCK key.\n * @property KEY_NUM_LOCK\n * @final\n * @type {Number}\n */\n this.KEY_NUM_LOCK = 144;\n\n /**\n * Code for the SCROLL_LOCK key.\n * @property KEY_SCROLL_LOCK\n * @final\n * @type {Number}\n */\n this.KEY_SCROLL_LOCK = 145;\n\n /**\n * Code for the SEMI_COLON key.\n * @property KEY_SEMI_COLON\n * @final\n * @type {Number}\n */\n this.KEY_SEMI_COLON = 186;\n\n /**\n * Code for the EQUAL_SIGN key.\n * @property KEY_EQUAL_SIGN\n * @final\n * @type {Number}\n */\n this.KEY_EQUAL_SIGN = 187;\n\n /**\n * Code for the COMMA key.\n * @property KEY_COMMA\n * @final\n * @type {Number}\n */\n this.KEY_COMMA = 188;\n\n /**\n * Code for the DASH key.\n * @property KEY_DASH\n * @final\n * @type {Number}\n */\n this.KEY_DASH = 189;\n\n /**\n * Code for the PERIOD key.\n * @property KEY_PERIOD\n * @final\n * @type {Number}\n */\n this.KEY_PERIOD = 190;\n\n /**\n * Code for the FORWARD_SLASH key.\n * @property KEY_FORWARD_SLASH\n * @final\n * @type {Number}\n */\n this.KEY_FORWARD_SLASH = 191;\n\n /**\n * Code for the GRAVE_ACCENT key.\n * @property KEY_GRAVE_ACCENT\n * @final\n * @type {Number}\n */\n this.KEY_GRAVE_ACCENT = 192;\n\n /**\n * Code for the OPEN_BRACKET key.\n * @property KEY_OPEN_BRACKET\n * @final\n * @type {Number}\n */\n this.KEY_OPEN_BRACKET = 219;\n\n /**\n * Code for the BACK_SLASH key.\n * @property KEY_BACK_SLASH\n * @final\n * @type {Number}\n */\n this.KEY_BACK_SLASH = 220;\n\n /**\n * Code for the CLOSE_BRACKET key.\n * @property KEY_CLOSE_BRACKET\n * @final\n * @type {Number}\n */\n this.KEY_CLOSE_BRACKET = 221;\n\n /**\n * Code for the SINGLE_QUOTE key.\n * @property KEY_SINGLE_QUOTE\n * @final\n * @type {Number}\n */\n this.KEY_SINGLE_QUOTE = 222;\n\n /**\n * Code for the SPACE key.\n * @property KEY_SPACE\n * @final\n * @type {Number}\n */\n this.KEY_SPACE = 32;\n\n /**\n * The canvas element that mouse and keyboards are bound to.\n *\n * @final\n * @type {HTMLCanvasElement}\n */\n this.element = cfg.element;\n\n /** True whenever ALT key is down.\n *\n * @type {boolean}\n */\n this.altDown = false;\n\n /** True whenever CTRL key is down.\n *\n * @type {boolean}\n */\n this.ctrlDown = false;\n\n /** True whenever left mouse button is down.\n *\n * @type {boolean}\n */\n this.mouseDownLeft = false;\n\n /**\n * True whenever middle mouse button is down.\n *\n * @type {boolean}\n */\n this.mouseDownMiddle = false;\n\n /**\n * True whenever the right mouse button is down.\n *\n * @type {boolean}\n */\n this.mouseDownRight = false;\n\n /**\n * Flag for each key that's down.\n *\n * @type {boolean[]}\n */\n this.keyDown = [];\n\n /** True while input enabled\n *\n * @type {boolean}\n */\n this.enabled = true;\n\n /** True while keyboard input is enabled.\n *\n * Default value is ````true````.\n *\n * {@link CameraControl} will not respond to keyboard events while this is ````false````.\n *\n * @type {boolean}\n */\n this.keyboardEnabled = true;\n\n /** True while the mouse is over the canvas.\n *\n * @type {boolean}\n */\n this.mouseover = false;\n\n /**\n * Current mouse position within the canvas.\n * @type {Number[]}\n */\n this.mouseCanvasPos = math.vec2();\n\n this._keyboardEventsElement = cfg.keyboardEventsElement || document;\n\n this._bindEvents();\n }\n\n _bindEvents() {\n\n if (this._eventsBound) {\n return;\n }\n\n this._keyboardEventsElement.addEventListener(\"keydown\", this._keyDownListener = (e) => {\n if (!this.enabled || (!this.keyboardEnabled)) {\n return;\n }\n if (e.target.tagName !== \"INPUT\" && e.target.tagName !== \"TEXTAREA\") {\n if (e.keyCode === this.KEY_CTRL) {\n this.ctrlDown = true;\n } else if (e.keyCode === this.KEY_ALT) {\n this.altDown = true;\n } else if (e.keyCode === this.KEY_SHIFT) {\n this.shiftDown = true;\n }\n this.keyDown[e.keyCode] = true;\n this.fire(\"keydown\", e.keyCode, true);\n }\n }, false);\n\n this._keyboardEventsElement.addEventListener(\"keyup\", this._keyUpListener = (e) => {\n if (!this.enabled || (!this.keyboardEnabled)) {\n return;\n }\n if (e.target.tagName !== \"INPUT\" && e.target.tagName !== \"TEXTAREA\") {\n if (e.keyCode === this.KEY_CTRL) {\n this.ctrlDown = false;\n } else if (e.keyCode === this.KEY_ALT) {\n this.altDown = false;\n } else if (e.keyCode === this.KEY_SHIFT) {\n this.shiftDown = false;\n }\n this.keyDown[e.keyCode] = false;\n this.fire(\"keyup\", e.keyCode, true);\n }\n });\n\n this.element.addEventListener(\"mouseenter\", this._mouseEnterListener = (e) => {\n if (!this.enabled) {\n return;\n }\n this.mouseover = true;\n this._getMouseCanvasPos(e);\n this.fire(\"mouseenter\", this.mouseCanvasPos, true);\n });\n\n this.element.addEventListener(\"mouseleave\", this._mouseLeaveListener = (e) => {\n if (!this.enabled) {\n return;\n }\n this.mouseover = false;\n this._getMouseCanvasPos(e);\n this.fire(\"mouseleave\", this.mouseCanvasPos, true);\n });\n\n this.element.addEventListener(\"mousedown\", this._mouseDownListener = (e) => {\n if (!this.enabled) {\n return;\n }\n switch (e.which) {\n case 1:// Left button\n this.mouseDownLeft = true;\n break;\n case 2:// Middle/both buttons\n this.mouseDownMiddle = true;\n break;\n case 3:// Right button\n this.mouseDownRight = true;\n break;\n }\n this._getMouseCanvasPos(e);\n this.element.focus();\n this.fire(\"mousedown\", this.mouseCanvasPos, true);\n if (this.mouseover) {\n e.preventDefault();\n }\n });\n\n document.addEventListener(\"mouseup\", this._mouseUpListener = (e) => {\n if (!this.enabled) {\n return;\n }\n switch (e.which) {\n case 1:// Left button\n this.mouseDownLeft = false;\n break;\n case 2:// Middle/both buttons\n this.mouseDownMiddle = false;\n break;\n case 3:// Right button\n this.mouseDownRight = false;\n break;\n }\n this.fire(\"mouseup\", this.mouseCanvasPos, true);\n // if (this.mouseover) {\n // e.preventDefault();\n // }\n }, true);\n\n document.addEventListener(\"click\", this._clickListener = (e) => {\n if (!this.enabled) {\n return;\n }\n switch (e.which) {\n case 1:// Left button\n this.mouseDownLeft = false;\n this.mouseDownRight = false;\n break;\n case 2:// Middle/both buttons\n this.mouseDownMiddle = false;\n break;\n case 3:// Right button\n this.mouseDownLeft = false;\n this.mouseDownRight = false;\n break;\n }\n this._getMouseCanvasPos(e);\n this.fire(\"click\", this.mouseCanvasPos, true);\n if (this.mouseover) {\n e.preventDefault();\n }\n });\n\n document.addEventListener(\"dblclick\", this._dblClickListener = (e) => {\n if (!this.enabled) {\n return;\n }\n switch (e.which) {\n case 1:// Left button\n this.mouseDownLeft = false;\n this.mouseDownRight = false;\n break;\n case 2:// Middle/both buttons\n this.mouseDownMiddle = false;\n break;\n case 3:// Right button\n this.mouseDownLeft = false;\n this.mouseDownRight = false;\n break;\n }\n this._getMouseCanvasPos(e);\n this.fire(\"dblclick\", this.mouseCanvasPos, true);\n if (this.mouseover) {\n e.preventDefault();\n }\n });\n\n const tickifedMouseMoveFn = this.scene.tickify(\n () => this.fire(\"mousemove\", this.mouseCanvasPos, true)\n );\n\n this.element.addEventListener(\"mousemove\", this._mouseMoveListener = (e) => {\n if (!this.enabled) {\n return;\n }\n this._getMouseCanvasPos(e);\n tickifedMouseMoveFn(); \n if (this.mouseover) {\n e.preventDefault();\n }\n });\n\n const tickifiedMouseWheelFn = this.scene.tickify(\n (delta) => { this.fire(\"mousewheel\", delta, true); }\n );\n\n this.element.addEventListener(\"wheel\", this._mouseWheelListener = (e, d) => {\n if (!this.enabled) {\n return;\n }\n const delta = Math.max(-1, Math.min(1, -e.deltaY * 40));\n tickifiedMouseWheelFn(delta);\n }, {passive: true});\n\n // mouseclicked\n\n {\n let downX;\n let downY;\n // Tolerance between down and up positions for a mouse click\n const tolerance = 2;\n this.on(\"mousedown\", (params) => {\n downX = params[0];\n downY = params[1];\n });\n this.on(\"mouseup\", (params) => {\n if (downX >= (params[0] - tolerance) &&\n downX <= (params[0] + tolerance) &&\n downY >= (params[1] - tolerance) &&\n downY <= (params[1] + tolerance)) {\n this.fire(\"mouseclicked\", params, true);\n }\n });\n }\n\n this._eventsBound = true;\n }\n\n _unbindEvents() {\n if (!this._eventsBound) {\n return;\n }\n this._keyboardEventsElement.removeEventListener(\"keydown\", this._keyDownListener);\n this._keyboardEventsElement.removeEventListener(\"keyup\", this._keyUpListener);\n this.element.removeEventListener(\"mouseenter\", this._mouseEnterListener);\n this.element.removeEventListener(\"mouseleave\", this._mouseLeaveListener);\n this.element.removeEventListener(\"mousedown\", this._mouseDownListener);\n document.removeEventListener(\"mouseup\", this._mouseDownListener);\n document.removeEventListener(\"click\", this._clickListener);\n document.removeEventListener(\"dblclick\", this._dblClickListener);\n this.element.removeEventListener(\"mousemove\", this._mouseMoveListener);\n this.element.removeEventListener(\"wheel\", this._mouseWheelListener);\n if (window.OrientationChangeEvent) {\n window.removeEventListener('orientationchange', this._orientationchangedListener);\n }\n if (window.DeviceMotionEvent) {\n window.removeEventListener('devicemotion', this._deviceMotionListener);\n }\n if (window.DeviceOrientationEvent) {\n window.removeEventListener(\"deviceorientation\", this._deviceOrientListener);\n }\n this._eventsBound = false;\n }\n\n _getMouseCanvasPos(event) {\n if (!event) {\n event = window.event;\n this.mouseCanvasPos[0] = event.x;\n this.mouseCanvasPos[1] = event.y;\n } else {\n let element = event.target;\n let totalOffsetLeft = 0;\n let totalOffsetTop = 0;\n while (element.offsetParent) {\n totalOffsetLeft += element.offsetLeft;\n totalOffsetTop += element.offsetTop;\n element = element.offsetParent;\n }\n this.mouseCanvasPos[0] = event.pageX - totalOffsetLeft;\n this.mouseCanvasPos[1] = event.pageY - totalOffsetTop;\n }\n }\n\n /**\n * Sets whether input handlers are enabled.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} enable Indicates if input handlers are enabled.\n */\n setEnabled(enable) {\n if (this.enabled !== enable) {\n this.fire(\"enabled\", this.enabled = enable);\n }\n }\n\n /**\n * Gets whether input handlers are enabled.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} Indicates if input handlers are enabled.\n */\n getEnabled() {\n return this.enabled;\n }\n\n /**\n * Sets whether or not keyboard input is enabled.\n *\n * Default value is ````true````.\n *\n * {@link CameraControl} will not respond to keyboard events while this is set ````false````.\n *\n * @param {Boolean} value Indicates whether keyboard input is enabled.\n */\n setKeyboardEnabled(value) {\n this.keyboardEnabled = value;\n }\n\n /**\n * Gets whether keyboard input is enabled.\n *\n * Default value is ````true````.\n *\n * {@link CameraControl} will not respond to keyboard events while this is set ````false````.\n *\n * @returns {Boolean} Returns whether keyboard input is enabled.\n */\n getKeyboardEnabled() {\n return this.keyboardEnabled;\n }\n\n /**\n * @private\n */\n destroy() {\n super.destroy();\n this._unbindEvents();\n }\n}\n\nconst ids$3 = new Map$1({});\n\n/**\n * @desc Represents a chunk of state changes applied by the {@link Scene}'s renderer while it renders a frame.\n *\n * * Contains properties that represent the state changes.\n * * Has a unique automatically-generated numeric ID, which the renderer can use to sort these, in order to avoid applying redundant state changes for each frame.\n * * Initialize your own properties on a RenderState via its constructor.\n *\n * @private\n */\nclass RenderState {\n\n constructor(cfg) {\n\n /**\n The RenderState's ID, unique within the renderer.\n @property id\n @type {Number}\n @final\n */\n this.id = ids$3.addItem({});\n for (const key in cfg) {\n if (cfg.hasOwnProperty(key)) {\n this[key] = cfg[key];\n }\n }\n }\n\n /**\n Destroys this RenderState.\n */\n destroy() {\n ids$3.removeItem(this.id);\n }\n}\n\n/**\n * @desc controls the canvas viewport for a {@link Scene}.\n *\n * * One Viewport per scene.\n * * You can configure a Scene to render multiple times per frame, while setting the Viewport to different extents on each render.\n * * Make a Viewport automatically size to its {@link Scene} {@link Canvas} by setting its {@link Viewport#autoBoundary} ````true````.\n *\n *\n * Configuring the Scene to render twice on each frame, each time to a separate viewport:\n *\n * ````Javascript\n * // Load glTF model\n * var model = new xeokit.GLTFModel({\n src: \"models/gltf/GearboxAssy/glTF-MaterialsCommon/GearboxAssy.gltf\"\n });\n\n var scene = model.scene;\n var viewport = scene.viewport;\n\n // Configure Scene to render twice for each frame\n scene.passes = 2; // Default is 1\n scene.clearEachPass = false; // Default is false\n\n // Render to a separate viewport on each render\n\n var viewport = scene.viewport;\n viewport.autoBoundary = false;\n\n scene.on(\"rendering\", function (e) {\n switch (e.pass) {\n case 0:\n viewport.boundary = [0, 0, 200, 200]; // xmin, ymin, width, height\n break;\n\n case 1:\n viewport.boundary = [200, 0, 200, 200];\n break;\n }\n });\n ````\n\n @class Viewport\n @module xeokit\n @submodule rendering\n @constructor\n @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n @param {*} [cfg] Viewport configuration\n @param {String} [cfg.id] Optional ID, unique among all components in the parent\n {@link Scene}, generated automatically when omitted.\n @param {String:Object} [cfg.meta] Optional map of user-defined metadata to attach to this Viewport.\n @param [cfg.boundary] {Number[]} Canvas-space Viewport boundary, given as\n (min, max, width, height). Defaults to the size of the parent\n {@link Scene} {@link Canvas}.\n @param [cfg.autoBoundary=false] {Boolean} Indicates if this Viewport's {@link Viewport#boundary}\n automatically synchronizes with the size of the parent {@link Scene} {@link Canvas}.\n\n @extends Component\n */\n\nclass Viewport extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Viewport\";\n }\n\n /**\n @private\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n boundary: [0, 0, 100, 100]\n });\n\n this.boundary = cfg.boundary;\n this.autoBoundary = cfg.autoBoundary;\n }\n\n\n /**\n * Sets the canvas-space boundary of this Viewport, indicated as ````[min, max, width, height]````.\n *\n * When {@link Viewport#autoBoundary} is ````true````, ignores calls to this method and automatically synchronizes with {@link Canvas#boundary}.\n *\n * Fires a \"boundary\"\" event on change.\n *\n * Defaults to the {@link Canvas} extents.\n *\n * @param {Number[]} value New Viewport extents.\n */\n set boundary(value) {\n\n if (this._autoBoundary) {\n return;\n }\n\n if (!value) {\n\n const canvasBoundary = this.scene.canvas.boundary;\n\n const width = canvasBoundary[2];\n const height = canvasBoundary[3];\n\n value = [0, 0, width, height];\n }\n\n this._state.boundary = value;\n\n this.glRedraw();\n\n /**\n Fired whenever this Viewport's {@link Viewport#boundary} property changes.\n\n @event boundary\n @param value {Boolean} The property's new value\n */\n this.fire(\"boundary\", this._state.boundary);\n }\n\n /**\n * Gets the canvas-space boundary of this Viewport, indicated as ````[min, max, width, height]````.\n *\n * @returns {Number[]} The Viewport extents.\n */\n get boundary() {\n return this._state.boundary;\n }\n\n /**\n * Sets if {@link Viewport#boundary} automatically synchronizes with {@link Canvas#boundary}.\n *\n * Default is ````false````.\n *\n * @param {Boolean} value Set true to automatically sycnhronize.\n */\n set autoBoundary(value) {\n\n value = !!value;\n\n if (value === this._autoBoundary) {\n return;\n }\n\n this._autoBoundary = value;\n\n if (this._autoBoundary) {\n this._onCanvasSize = this.scene.canvas.on(\"boundary\",\n function (boundary) {\n\n const width = boundary[2];\n const height = boundary[3];\n\n this._state.boundary = [0, 0, width, height];\n\n this.glRedraw();\n\n /**\n Fired whenever this Viewport's {@link Viewport#boundary} property changes.\n\n @event boundary\n @param value {Boolean} The property's new value\n */\n this.fire(\"boundary\", this._state.boundary);\n\n }, this);\n\n } else if (this._onCanvasSize) {\n this.scene.canvas.off(this._onCanvasSize);\n this._onCanvasSize = null;\n }\n\n /**\n Fired whenever this Viewport's {@link autoBoundary/autoBoundary} property changes.\n\n @event autoBoundary\n @param value The property's new value\n */\n this.fire(\"autoBoundary\", this._autoBoundary);\n }\n\n /**\n * Gets if {@link Viewport#boundary} automatically synchronizes with {@link Canvas#boundary}.\n *\n * Default is ````false````.\n *\n * @returns {Boolean} Returns ````true```` when automatically sycnhronizing.\n */\n get autoBoundary() {\n return this._autoBoundary;\n }\n\n _getState() {\n return this._state;\n }\n\n /**\n * @private\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\n/**\n * @desc Defines its {@link Camera}'s perspective projection using a field-of-view angle.\n *\n * * Located at {@link Camera#perspective}.\n * * Implicitly sets the left, right, top, bottom frustum planes using {@link Perspective#fov}.\n * * {@link Perspective#near} and {@link Perspective#far} specify the distances to the WebGL clipping planes.\n */\nclass Perspective extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Perspective\";\n }\n\n /**\n * @constructor\n * @private\n */\n constructor(camera, cfg = {}) {\n\n super(camera, cfg);\n\n /**\n * The Camera this Perspective belongs to.\n *\n * @property camera\n * @type {Camera}\n * @final\n */\n this.camera = camera;\n\n this._state = new RenderState({\n matrix: math.mat4(),\n inverseMatrix: math.mat4(),\n transposedMatrix: math.mat4(),\n near: 0.1,\n far: 10000.0\n });\n\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = true;\n\n this._fov = 60.0;\n\n // Recompute aspect from change in canvas size\n this._canvasResized = this.scene.canvas.on(\"boundary\", this._needUpdate, this);\n\n this.fov = cfg.fov;\n this.fovAxis = cfg.fovAxis;\n this.near = cfg.near;\n this.far = cfg.far;\n }\n\n _update() {\n\n const WIDTH_INDEX = 2;\n const HEIGHT_INDEX = 3;\n const boundary = this.scene.canvas.boundary;\n const aspect = boundary[WIDTH_INDEX] / boundary[HEIGHT_INDEX];\n const fovAxis = this._fovAxis;\n\n let fov = this._fov;\n if (fovAxis === \"x\" || (fovAxis === \"min\" && aspect < 1) || (fovAxis === \"max\" && aspect > 1)) {\n fov = fov / aspect;\n }\n fov = Math.min(fov, 120);\n\n math.perspectiveMat4(fov * (Math.PI / 180.0), aspect, this._state.near, this._state.far, this._state.matrix);\n\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = true;\n\n this.glRedraw();\n\n this.camera._updateScheduled = true;\n\n this.fire(\"matrix\", this._state.matrix);\n }\n\n /**\n * Sets the Perspective's field-of-view angle (FOV).\n *\n * Fires an \"fov\" event on change.\n\n * Default value is ````60.0````.\n *\n * @param {Number} value New field-of-view.\n */\n set fov(value) {\n value = (value !== undefined && value !== null) ? value : 60.0;\n if (value === this._fov) {\n return;\n }\n this._fov = value;\n this._needUpdate(0); // Ensure matrix built on next \"tick\"\n this.fire(\"fov\", this._fov);\n }\n\n /**\n * Gets the Perspective's field-of-view angle (FOV).\n *\n * Default value is ````60.0````.\n *\n * @returns {Number} Current field-of-view.\n */\n get fov() {\n return this._fov;\n }\n\n /**\n * Sets the Perspective's FOV axis.\n *\n * Options are ````\"x\"````, ````\"y\"```` or ````\"min\"````, to use the minimum axis.\n *\n * Fires an \"fovAxis\" event on change.\n\n * Default value ````\"min\"````.\n *\n * @param {String} value New FOV axis value.\n */\n set fovAxis(value) {\n value = value || \"min\";\n if (this._fovAxis === value) {\n return;\n }\n if (value !== \"x\" && value !== \"y\" && value !== \"min\") {\n this.error(\"Unsupported value for 'fovAxis': \" + value + \" - defaulting to 'min'\");\n value = \"min\";\n }\n this._fovAxis = value;\n this._needUpdate(0); // Ensure matrix built on next \"tick\"\n this.fire(\"fovAxis\", this._fovAxis);\n }\n\n /**\n * Gets the Perspective's FOV axis.\n *\n * Options are ````\"x\"````, ````\"y\"```` or ````\"min\"````, to use the minimum axis.\n *\n * Fires an \"fovAxis\" event on change.\n\n * Default value is ````\"min\"````.\n *\n * @returns {String} The current FOV axis value.\n */\n get fovAxis() {\n return this._fovAxis;\n }\n\n /**\n * Sets the position of the Perspective's near plane on the positive View-space Z-axis.\n *\n * Fires a \"near\" event on change.\n *\n * Default value is ````0.1````.\n *\n * @param {Number} value New Perspective near plane position.\n */\n set near(value) {\n const near = (value !== undefined && value !== null) ? value : 0.1;\n if (this._state.near === near) {\n return;\n }\n this._state.near = near;\n this._needUpdate(0); // Ensure matrix built on next \"tick\"\n this.fire(\"near\", this._state.near);\n }\n\n /**\n * Gets the position of the Perspective's near plane on the positive View-space Z-axis.\n *\n * Fires an \"emits\" emits on change.\n *\n * Default value is ````0.1````.\n *\n * @returns The Perspective's near plane position.\n */\n get near() {\n return this._state.near;\n }\n\n /**\n * Sets the position of this Perspective's far plane on the positive View-space Z-axis.\n *\n * Fires a \"far\" event on change.\n *\n * Default value is ````10000.0````.\n *\n * @param {Number} value New Perspective far plane position.\n */\n set far(value) {\n const far = (value !== undefined && value !== null) ? value : 10000.0;\n if (this._state.far === far) {\n return;\n }\n this._state.far = far;\n this._needUpdate(0); // Ensure matrix built on next \"tick\"\n this.fire(\"far\", this._state.far);\n }\n\n /**\n * Gets the position of this Perspective's far plane on the positive View-space Z-axis.\n *\n * Default value is ````10000.0````.\n *\n * @return {Number} The Perspective's far plane position.\n */\n get far() {\n return this._state.far;\n }\n\n /**\n * Gets the Perspective's projection transform matrix.\n *\n * Fires a \"matrix\" event on change.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @returns {Number[]} The Perspective's projection matrix.\n */\n get matrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.matrix;\n }\n\n /**\n * Gets the inverse of {@link Perspective#matrix}.\n *\n * @returns {Number[]} The inverse of {@link Perspective#matrix}.\n */\n get inverseMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._inverseMatrixDirty) {\n math.inverseMat4(this._state.matrix, this._state.inverseMatrix);\n this._inverseMatrixDirty = false;\n }\n return this._state.inverseMatrix;\n }\n\n /**\n * Gets the transpose of {@link Perspective#matrix}.\n *\n * @returns {Number[]} The transpose of {@link Perspective#matrix}.\n */\n get transposedMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._transposedMatrixDirty) {\n math.transposeMat4(this._state.matrix, this._state.transposedMatrix);\n this._transposedMatrixDirty = false;\n }\n return this._state.transposedMatrix;\n }\n\n /**\n * Un-projects the given Canvas-space coordinates and Screen-space depth, using this Perspective projection.\n *\n * @param {Number[]} canvasPos Inputs 2D Canvas-space coordinates.\n * @param {Number} screenZ Inputs Screen-space Z coordinate.\n * @param {Number[]} screenPos Outputs 3D Screen/Clip-space coordinates.\n * @param {Number[]} viewPos Outputs un-projected 3D View-space coordinates.\n * @param {Number[]} worldPos Outputs un-projected 3D World-space coordinates.\n */\n unproject(canvasPos, screenZ, screenPos, viewPos, worldPos) {\n\n const canvas = this.scene.canvas.canvas;\n\n const halfCanvasWidth = canvas.offsetWidth / 2.0;\n const halfCanvasHeight = canvas.offsetHeight / 2.0;\n\n screenPos[0] = (canvasPos[0] - halfCanvasWidth) / halfCanvasWidth;\n screenPos[1] = (canvasPos[1] - halfCanvasHeight) / halfCanvasHeight;\n screenPos[2] = screenZ;\n screenPos[3] = 1.0;\n\n math.mulMat4v4(this.inverseMatrix, screenPos, viewPos);\n math.mulVec3Scalar(viewPos, 1.0 / viewPos[3]);\n\n viewPos[3] = 1.0;\n viewPos[1] *= -1;\n\n math.mulMat4v4(this.camera.inverseViewMatrix, viewPos, worldPos);\n\n return worldPos;\n }\n\n /** @private\n *\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n this.scene.canvas.off(this._canvasResized);\n }\n}\n\n/**\n * @desc Defines its {@link Camera}'s orthographic projection as a box-shaped view volume.\n *\n * * Located at {@link Camera#ortho}.\n * * Works like Blender's orthographic projection, where the positions of the left, right, top and bottom planes are implicitly\n * indicated with a single {@link Ortho#scale} property, which causes the frustum to be symmetrical on X and Y axis, large enough to\n * contain the number of units given by {@link Ortho#scale}.\n * * {@link Ortho#near} and {@link Ortho#far} indicated the distances to the WebGL clipping planes.\n */\nclass Ortho extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Ortho\";\n }\n\n /**\n * @constructor\n * @private\n */\n constructor(camera, cfg = {}) {\n\n super(camera, cfg);\n\n /**\n * The Camera this Ortho belongs to.\n *\n * @property camera\n * @type {Camera}\n * @final\n */\n this.camera = camera;\n\n this._state = new RenderState({\n matrix: math.mat4(),\n inverseMatrix: math.mat4(),\n transposedMatrix: math.mat4(),\n near: 0.1,\n far: 10000.0\n });\n\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = true;\n\n this.scale = cfg.scale;\n this.near = cfg.near;\n this.far = cfg.far;\n\n this._onCanvasBoundary = this.scene.canvas.on(\"boundary\", this._needUpdate, this);\n }\n\n _update() {\n\n const WIDTH_INDEX = 2;\n const HEIGHT_INDEX = 3;\n\n const scene = this.scene;\n const scale = this._scale;\n const halfSize = 0.5 * scale;\n\n const boundary = scene.canvas.boundary;\n const boundaryWidth = boundary[WIDTH_INDEX];\n const boundaryHeight = boundary[HEIGHT_INDEX];\n const aspect = boundaryWidth / boundaryHeight;\n\n let left;\n let right;\n let top;\n let bottom;\n\n if (boundaryWidth > boundaryHeight) {\n left = -halfSize;\n right = halfSize;\n top = halfSize / aspect;\n bottom = -halfSize / aspect;\n\n } else {\n left = -halfSize * aspect;\n right = halfSize * aspect;\n top = halfSize;\n bottom = -halfSize;\n }\n\n math.orthoMat4c(left, right, bottom, top, this._state.near, this._state.far, this._state.matrix);\n\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = true;\n\n this.glRedraw();\n\n this.fire(\"matrix\", this._state.matrix);\n }\n\n\n /**\n * Sets scale factor for this Ortho's extents on X and Y axis.\n *\n * Clamps to minimum value of ````0.01```.\n *\n * Fires a \"scale\" event on change.\n *\n * Default value is ````1.0````\n * @param {Number} value New scale value.\n */\n set scale(value) {\n if (value === undefined || value === null) {\n value = 1.0;\n }\n if (value <= 0) {\n value = 0.01;\n }\n this._scale = value;\n this._needUpdate(0);\n this.fire(\"scale\", this._scale);\n }\n\n /**\n * Gets scale factor for this Ortho's extents on X and Y axis.\n *\n * Clamps to minimum value of ````0.01```.\n *\n * Default value is ````1.0````\n *\n * @returns {Number} New Ortho scale value.\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the position of the Ortho's near plane on the positive View-space Z-axis.\n *\n * Fires a \"near\" emits on change.\n *\n * Default value is ````0.1````.\n *\n * @param {Number} value New Ortho near plane position.\n */\n set near(value) {\n const near = (value !== undefined && value !== null) ? value : 0.1;\n if (this._state.near === near) {\n return;\n }\n this._state.near = near;\n this._needUpdate(0);\n this.fire(\"near\", this._state.near);\n }\n\n /**\n * Gets the position of the Ortho's near plane on the positive View-space Z-axis.\n *\n * Default value is ````0.1````.\n *\n * @returns {Number} New Ortho near plane position.\n */\n get near() {\n return this._state.near;\n }\n\n /**\n * Sets the position of the Ortho's far plane on the positive View-space Z-axis.\n *\n * Fires a \"far\" event on change.\n *\n * Default value is ````10000.0````.\n *\n * @param {Number} value New far ortho plane position.\n */\n set far(value) {\n const far = (value !== undefined && value !== null) ? value : 10000.0;\n if (this._state.far === far) {\n return;\n }\n this._state.far = far;\n this._needUpdate(0);\n this.fire(\"far\", this._state.far);\n }\n\n /**\n * Gets the position of the Ortho's far plane on the positive View-space Z-axis.\n *\n * Default value is ````10000.0````.\n *\n * @returns {Number} New far ortho plane position.\n */\n get far() {\n return this._state.far;\n }\n\n /**\n * Gets the Ortho's projection transform matrix.\n *\n * Fires a \"matrix\" event on change.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @returns {Number[]} The Ortho's projection matrix.\n */\n get matrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.matrix;\n }\n\n /**\n * Gets the inverse of {@link Ortho#matrix}.\n *\n * @returns {Number[]} The inverse of {@link Ortho#matrix}.\n */\n get inverseMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._inverseMatrixDirty) {\n math.inverseMat4(this._state.matrix, this._state.inverseMatrix);\n this._inverseMatrixDirty = false;\n }\n return this._state.inverseMatrix;\n }\n\n /**\n * Gets the transpose of {@link Ortho#matrix}.\n *\n * @returns {Number[]} The transpose of {@link Ortho#matrix}.\n */\n get transposedMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._transposedMatrixDirty) {\n math.transposeMat4(this._state.matrix, this._state.transposedMatrix);\n this._transposedMatrixDirty = false;\n }\n return this._state.transposedMatrix;\n }\n\n /**\n * Un-projects the given Canvas-space coordinates, using this Ortho projection.\n *\n * @param {Number[]} canvasPos Inputs 2D Canvas-space coordinates.\n * @param {Number} screenZ Inputs Screen-space Z coordinate.\n * @param {Number[]} screenPos Outputs 3D Screen/Clip-space coordinates.\n * @param {Number[]} viewPos Outputs un-projected 3D View-space coordinates.\n * @param {Number[]} worldPos Outputs un-projected 3D World-space coordinates.\n */\n unproject(canvasPos, screenZ, screenPos, viewPos, worldPos) {\n\n const canvas = this.scene.canvas.canvas;\n\n const halfCanvasWidth = canvas.offsetWidth / 2.0;\n const halfCanvasHeight = canvas.offsetHeight / 2.0;\n\n screenPos[0] = (canvasPos[0] - halfCanvasWidth) / halfCanvasWidth;\n screenPos[1] = (canvasPos[1] - halfCanvasHeight) / halfCanvasHeight;\n screenPos[2] = screenZ;\n screenPos[3] = 1.0;\n\n math.mulMat4v4(this.inverseMatrix, screenPos, viewPos);\n math.mulVec3Scalar(viewPos, 1.0 / viewPos[3]);\n\n viewPos[3] = 1.0;\n viewPos[1] *= -1;\n\n math.mulMat4v4(this.camera.inverseViewMatrix, viewPos, worldPos);\n\n return worldPos;\n }\n\n /** @private\n *\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n this.scene.canvas.off(this._onCanvasBoundary);\n }\n}\n\n/**\n * @desc Defines its {@link Camera}'s perspective projection as a frustum-shaped view volume.\n *\n * * Located at {@link Camera#frustum}.\n * * Allows to explicitly set the positions of the left, right, top, bottom, near and far planes, which is useful for asymmetrical view volumes, such as for stereo viewing.\n * * {@link Frustum#near} and {@link Frustum#far} specify the distances to the WebGL clipping planes.\n */\nclass Frustum extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Frustum\";\n }\n\n /**\n * @constructor\n * @private\n */\n constructor(camera, cfg = {}) {\n\n super(camera, cfg);\n\n /**\n * The Camera this Frustum belongs to.\n *\n * @property camera\n * @type {Camera}\n * @final\n */\n this.camera = camera;\n\n this._state = new RenderState({\n matrix: math.mat4(),\n inverseMatrix: math.mat4(),\n transposedMatrix: math.mat4(),\n near: 0.1,\n far: 10000.0\n });\n\n this._left = -1.0;\n this._right = 1.0;\n this._bottom = -1.0;\n this._top = 1.0;\n\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = true;\n\n // Set component properties\n\n this.left = cfg.left;\n this.right = cfg.right;\n this.bottom = cfg.bottom;\n this.top = cfg.top;\n this.near = cfg.near;\n this.far = cfg.far;\n }\n\n _update() {\n\n math.frustumMat4(this._left, this._right, this._bottom, this._top, this._state.near, this._state.far, this._state.matrix);\n\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = true;\n\n this.glRedraw();\n\n this.fire(\"matrix\", this._state.matrix);\n }\n\n /**\n * Sets the position of the Frustum's left plane on the View-space X-axis.\n *\n * Fires a {@link Frustum#left:emits} emits on change.\n *\n * @param {Number} value New left frustum plane position.\n */\n set left(value) {\n this._left = (value !== undefined && value !== null) ? value : -1.0;\n this._needUpdate(0);\n this.fire(\"left\", this._left);\n }\n\n /**\n * Gets the position of the Frustum's left plane on the View-space X-axis.\n *\n * @return {Number} Left frustum plane position.\n */\n get left() {\n return this._left;\n }\n\n /**\n * Sets the position of the Frustum's right plane on the View-space X-axis.\n *\n * Fires a {@link Frustum#right:emits} emits on change.\n *\n * @param {Number} value New right frustum plane position.\n */\n set right(value) {\n this._right = (value !== undefined && value !== null) ? value : 1.0;\n this._needUpdate(0);\n this.fire(\"right\", this._right);\n }\n\n /**\n * Gets the position of the Frustum's right plane on the View-space X-axis.\n *\n * Fires a {@link Frustum#right:emits} emits on change.\n *\n * @return {Number} Right frustum plane position.\n */\n get right() {\n return this._right;\n }\n\n /**\n * Sets the position of the Frustum's top plane on the View-space Y-axis.\n *\n * Fires a {@link Frustum#top:emits} emits on change.\n *\n * @param {Number} value New top frustum plane position.\n */\n set top(value) {\n this._top = (value !== undefined && value !== null) ? value : 1.0;\n this._needUpdate(0);\n this.fire(\"top\", this._top);\n }\n\n /**\n * Gets the position of the Frustum's top plane on the View-space Y-axis.\n *\n * Fires a {@link Frustum#top:emits} emits on change.\n *\n * @return {Number} Top frustum plane position.\n */\n get top() {\n return this._top;\n }\n\n /**\n * Sets the position of the Frustum's bottom plane on the View-space Y-axis.\n *\n * Fires a {@link Frustum#bottom:emits} emits on change.\n *\n * @emits {\"bottom\"} event with the value of this property whenever it changes.\n *\n * @param {Number} value New bottom frustum plane position.\n */\n set bottom(value) {\n this._bottom = (value !== undefined && value !== null) ? value : -1.0;\n this._needUpdate(0);\n this.fire(\"bottom\", this._bottom);\n }\n\n /**\n * Gets the position of the Frustum's bottom plane on the View-space Y-axis.\n *\n * Fires a {@link Frustum#bottom:emits} emits on change.\n *\n * @return {Number} Bottom frustum plane position.\n */\n get bottom() {\n return this._bottom;\n }\n\n /**\n * Sets the position of the Frustum's near plane on the positive View-space Z-axis.\n *\n * Fires a {@link Frustum#near:emits} emits on change.\n *\n * Default value is ````0.1````.\n *\n * @param {Number} value New Frustum near plane position.\n */\n set near(value) {\n this._state.near = (value !== undefined && value !== null) ? value : 0.1;\n this._needUpdate(0);\n this.fire(\"near\", this._state.near);\n }\n\n /**\n * Gets the position of the Frustum's near plane on the positive View-space Z-axis.\n *\n * Fires a {@link Frustum#near:emits} emits on change.\n *\n * Default value is ````0.1````.\n *\n * @return {Number} Near frustum plane position.\n */\n get near() {\n return this._state.near;\n }\n\n /**\n * Sets the position of the Frustum's far plane on the positive View-space Z-axis.\n *\n * Fires a {@link Frustum#far:emits} emits on change.\n *\n * Default value is ````10000.0````.\n *\n * @param {Number} value New far frustum plane position.\n */\n set far(value) {\n this._state.far = (value !== undefined && value !== null) ? value : 10000.0;\n this._needUpdate(0);\n this.fire(\"far\", this._state.far);\n }\n\n /**\n * Gets the position of the Frustum's far plane on the positive View-space Z-axis.\n *\n * Default value is ````10000.0````.\n *\n * @return {Number} Far frustum plane position.\n */\n get far() {\n return this._state.far;\n }\n\n /**\n * Gets the Frustum's projection transform matrix.\n *\n * Fires a {@link Frustum#matrix:emits} emits on change.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @returns {Number[]} The Frustum's projection matrix matrix.\n */\n get matrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.matrix;\n }\n\n /**\n * Gets the inverse of {@link Frustum#matrix}.\n *\n * @returns {Number[]} The inverse orthographic projection matrix.\n */\n get inverseMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._inverseMatrixDirty) {\n math.inverseMat4(this._state.matrix, this._state.inverseMatrix);\n this._inverseMatrixDirty = false;\n }\n return this._state.inverseMatrix;\n }\n\n /**\n * Gets the transpose of {@link Frustum#matrix}.\n *\n * @returns {Number[]} The transpose of {@link Frustum#matrix}.\n */\n get transposedMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._transposedMatrixDirty) {\n math.transposeMat4(this._state.matrix, this._state.transposedMatrix);\n this._transposedMatrixDirty = false;\n }\n return this._state.transposedMatrix;\n }\n\n /**\n * Un-projects the given Canvas-space coordinates, using this Frustum projection.\n *\n * @param {Number[]} canvasPos Inputs 2D Canvas-space coordinates.\n * @param {Number} screenZ Inputs Screen-space Z coordinate.\n * @param {Number[]} screenPos Outputs 3D Screen/Clip-space coordinates.\n * @param {Number[]} viewPos Outputs un-projected 3D View-space coordinates.\n * @param {Number[]} worldPos Outputs un-projected 3D World-space coordinates.\n */\n unproject(canvasPos, screenZ, screenPos, viewPos, worldPos) {\n\n const canvas = this.scene.canvas.canvas;\n\n const halfCanvasWidth = canvas.offsetWidth / 2.0;\n const halfCanvasHeight = canvas.offsetHeight / 2.0;\n\n screenPos[0] = (canvasPos[0] - halfCanvasWidth) / halfCanvasWidth;\n screenPos[1] = (canvasPos[1] - halfCanvasHeight) / halfCanvasHeight;\n screenPos[2] = screenZ;\n screenPos[3] = 1.0;\n\n math.mulMat4v4(this.inverseMatrix, screenPos, viewPos);\n math.mulVec3Scalar(viewPos, 1.0 / viewPos[3]);\n\n viewPos[3] = 1.0;\n viewPos[1] *= -1;\n\n math.mulMat4v4(this.camera.inverseViewMatrix, viewPos, worldPos);\n\n return worldPos;\n }\n\n /** @private\n *\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n super.destroy();\n }\n}\n\n/**\n * @desc Defines a custom projection for a {@link Camera} as a custom 4x4 matrix..\n *\n * Located at {@link Camera#customProjection}.\n */\nclass CustomProjection extends Component {\n\n /**\n * @private\n */\n get type() {\n return \"CustomProjection\";\n }\n\n /**\n * @constructor\n * @private\n */\n constructor(camera, cfg = {}) {\n\n super(camera, cfg);\n\n /**\n * The Camera this CustomProjection belongs to.\n *\n * @property camera\n * @type {Camera}\n * @final\n */\n this.camera = camera;\n\n this._state = new RenderState({\n matrix: math.mat4(),\n inverseMatrix: math.mat4(),\n transposedMatrix: math.mat4()\n });\n\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = false;\n\n this.matrix = cfg.matrix;\n }\n\n /**\n * Sets the CustomProjection's projection transform matrix.\n *\n * Fires a \"matrix\" event on change.\n\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @param {Number[]} matrix New value for the CustomProjection's matrix.\n */\n set matrix(matrix) {\n this._state.matrix.set(matrix || [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);\n this._inverseMatrixDirty = true;\n this._transposedMatrixDirty = true;\n this.glRedraw();\n this.fire(\"matrix\", this._state.matrix);\n }\n\n /**\n * Gets the CustomProjection's projection transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @return {Number[]} New value for the CustomProjection's matrix.\n */\n get matrix() {\n return this._state.matrix;\n }\n\n /**\n * Gets the inverse of {@link CustomProjection#matrix}.\n *\n * @returns {Number[]} The inverse of {@link CustomProjection#matrix}.\n */\n get inverseMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._inverseMatrixDirty) {\n math.inverseMat4(this._state.matrix, this._state.inverseMatrix);\n this._inverseMatrixDirty = false;\n }\n return this._state.inverseMatrix;\n }\n\n /**\n * Gets the transpose of {@link CustomProjection#matrix}.\n *\n * @returns {Number[]} The transpose of {@link CustomProjection#matrix}.\n */\n get transposedMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n if (this._transposedMatrixDirty) {\n math.transposeMat4(this._state.matrix, this._state.transposedMatrix);\n this._transposedMatrixDirty = false;\n }\n return this._state.transposedMatrix;\n }\n\n /**\n * Un-projects the given Canvas-space coordinates, using this CustomProjection.\n *\n * @param {Number[]} canvasPos Inputs 2D Canvas-space coordinates.\n * @param {Number} screenZ Inputs Screen-space Z coordinate.\n * @param {Number[]} screenPos Outputs 3D Screen/Clip-space coordinates.\n * @param {Number[]} viewPos Outputs un-projected 3D View-space coordinates.\n * @param {Number[]} worldPos Outputs un-projected 3D World-space coordinates.\n */\n unproject(canvasPos, screenZ, screenPos, viewPos, worldPos) {\n\n const canvas = this.scene.canvas.canvas;\n\n const halfCanvasWidth = canvas.offsetWidth / 2.0;\n const halfCanvasHeight = canvas.offsetHeight / 2.0;\n\n screenPos[0] = (canvasPos[0] - halfCanvasWidth) / halfCanvasWidth;\n screenPos[1] = (canvasPos[1] - halfCanvasHeight) / halfCanvasHeight;\n screenPos[2] = screenZ;\n screenPos[3] = 1.0;\n\n math.mulMat4v4(this.inverseMatrix, screenPos, viewPos);\n math.mulVec3Scalar(viewPos, 1.0 / viewPos[3]);\n\n viewPos[3] = 1.0;\n viewPos[1] *= -1;\n\n math.mulMat4v4(this.camera.inverseViewMatrix, viewPos, worldPos);\n\n return worldPos;\n }\n\n /** @private\n *\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\nconst tempVec3$6 = math.vec3();\nconst tempVec3b$y = math.vec3();\nconst tempVec3c$u = math.vec3();\nconst tempVec3d$d = math.vec3();\nconst tempVec3e$2 = math.vec3();\nconst tempVec3f$2 = math.vec3();\nconst tempVec4a$8 = math.vec4();\nconst tempVec4b$5 = math.vec4();\nconst tempVec4c$2 = math.vec4();\nconst tempMat = math.mat4();\nconst tempMatb = math.mat4();\nconst eyeLookVec = math.vec3();\nconst eyeLookVecNorm = math.vec3();\nconst eyeLookOffset = math.vec3();\nconst offsetEye = math.vec3();\n\n/**\n * @desc Manages viewing and projection transforms for its {@link Scene}.\n *\n * * One Camera per {@link Scene}\n * * Scene is located at {@link Viewer#scene} and Camera is located at {@link Scene#camera}\n * * Controls viewing and projection transforms\n * * Has methods to pan, zoom and orbit (or first-person rotation)\n * * Dynamically configurable World-space axis\n * * Has {@link Perspective}, {@link Ortho} and {@link Frustum} and {@link CustomProjection}, which you can dynamically switch it between\n * * Switchable gimbal lock\n * * Can be \"flown\" to look at targets using a {@link CameraFlightAnimation}\n * * Can be animated along a path using a {@link CameraPathAnimation}\n *\n * ## Getting the Camera\n *\n * There is exactly one Camera per {@link Scene}:\n *\n * ````javascript\n * import {Viewer} from \"xeokit-sdk.es.js\";\n *\n * var camera = viewer.scene.camera;\n *\n * ````\n *\n * ## Setting the Camera Position\n *\n * Get and set the Camera's absolute position via {@link Camera#eye}, {@link Camera#look} and {@link Camera#up}:\n *\n * ````javascript\n * camera.eye = [-10,0,0];\n * camera.look = [-10,0,0];\n * camera.up = [0,1,0];\n * ````\n *\n * ## Camera View and Projection Matrices\n *\n * The Camera's view matrix transforms coordinates from World-space to View-space.\n *\n * Getting the view matrix:\n *\n * ````javascript\n * var viewMatrix = camera.viewMatrix;\n * var viewNormalMatrix = camera.normalMatrix;\n * ````\n *\n * The Camera's view normal matrix transforms normal vectors from World-space to View-space.\n *\n * Getting the view normal matrix:\n *\n * ````javascript\n * var viewNormalMatrix = camera.normalMatrix;\n * ````\n *\n * The Camera fires a ````\"viewMatrix\"```` event whenever the {@link Camera#viewMatrix} and {@link Camera#viewNormalMatrix} updates.\n *\n * Listen for view matrix updates:\n *\n * ````javascript\n * camera.on(\"viewMatrix\", function(matrix) { ... });\n * ````\n *\n * ## Rotating the Camera\n *\n * Orbiting the {@link Camera#look} position:\n *\n * ````javascript\n * camera.orbitYaw(20.0);\n * camera.orbitPitch(10.0);\n * ````\n *\n * First-person rotation, rotates {@link Camera#look} and {@link Camera#up} about {@link Camera#eye}:\n *\n * ````javascript\n * camera.yaw(5.0);\n * camera.pitch(-10.0);\n * ````\n *\n * ## Panning the Camera\n *\n * Panning along the Camera's local axis (ie. left/right, up/down, forward/backward):\n *\n * ````javascript\n * camera.pan([-20, 0, 10]);\n * ````\n *\n * ## Zooming the Camera\n *\n * Zoom to vary distance between {@link Camera#eye} and {@link Camera#look}:\n *\n * ````javascript\n * camera.zoom(-5); // Move five units closer\n * ````\n *\n * Get the current distance between {@link Camera#eye} and {@link Camera#look}:\n *\n * ````javascript\n * var distance = camera.eyeLookDist;\n * ````\n *\n * ## Projection\n *\n * The Camera has a Component to manage each projection type, which are: {@link Perspective}, {@link Ortho}\n * and {@link Frustum} and {@link CustomProjection}.\n *\n * You can configure those components at any time, regardless of which is currently active:\n *\n * The Camera has a {@link Perspective} to manage perspective\n * ````javascript\n *\n * // Set some properties on Perspective\n * camera.perspective.near = 0.4;\n * camera.perspective.fov = 45;\n *\n * // Set some properties on Ortho\n * camera.ortho.near = 0.8;\n * camera.ortho.far = 1000;\n *\n * // Set some properties on Frustum\n * camera.frustum.left = -1.0;\n * camera.frustum.right = 1.0;\n * camera.frustum.far = 1000.0;\n *\n * // Set the matrix property on CustomProjection\n * camera.customProjection.matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];\n *\n * // Switch between the projection types\n * camera.projection = \"perspective\"; // Switch to perspective\n * camera.projection = \"frustum\"; // Switch to frustum\n * camera.projection = \"ortho\"; // Switch to ortho\n * camera.projection = \"customProjection\"; // Switch to custom\n * ````\n *\n * Camera provides the projection matrix for the currently active projection in {@link Camera#projMatrix}.\n *\n * Get the projection matrix:\n *\n * ````javascript\n * var projMatrix = camera.projMatrix;\n * ````\n *\n * Listen for projection matrix updates:\n *\n * ````javascript\n * camera.on(\"projMatrix\", function(matrix) { ... });\n * ````\n *\n * ## Configuring World up direction\n *\n * We can dynamically configure the directions of the World-space coordinate system.\n *\n * Setting the +Y axis as World \"up\", +X as right and -Z as forwards (convention in some modeling software):\n *\n * ````javascript\n * camera.worldAxis = [\n * 1, 0, 0, // Right\n * 0, 1, 0, // Up\n * 0, 0,-1 // Forward\n * ];\n * ````\n *\n * Setting the +Z axis as World \"up\", +X as right and -Y as \"up\" (convention in most CAD and BIM viewers):\n *\n * ````javascript\n * camera.worldAxis = [\n * 1, 0, 0, // Right\n * 0, 0, 1, // Up\n * 0,-1, 0 // Forward\n * ];\n * ````\n *\n * The Camera has read-only convenience properties that provide each axis individually:\n *\n * ````javascript\n * var worldRight = camera.worldRight;\n * var worldForward = camera.worldForward;\n * var worldUp = camera.worldUp;\n * ````\n *\n * ### Gimbal locking\n *\n * By default, the Camera locks yaw rotation to pivot about the World-space \"up\" axis. We can dynamically lock and unlock that at any time:\n *\n * ````javascript\n * camera.gimbalLock = false; // Yaw rotation now happens about Camera's local Y-axis\n * camera.gimbalLock = true; // Yaw rotation now happens about World's \"up\" axis\n * ````\n *\n * See: https://en.wikipedia.org/wiki/Gimbal_lock\n */\nclass Camera extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Camera\";\n }\n\n /**\n * @constructor\n * @private\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n deviceMatrix: math.mat4(),\n hasDeviceMatrix: false, // True when deviceMatrix set to other than identity\n matrix: math.mat4(),\n normalMatrix: math.mat4(),\n inverseMatrix: math.mat4()\n });\n\n this._perspective = new Perspective(this);\n this._ortho = new Ortho(this);\n this._frustum = new Frustum(this);\n this._customProjection = new CustomProjection(this);\n this._project = this._perspective;\n\n this._eye = math.vec3([0, 0, 10.0]);\n this._look = math.vec3([0, 0, 0]);\n this._up = math.vec3([0, 1, 0]);\n\n this._worldUp = math.vec3([0, 1, 0]);\n this._worldRight = math.vec3([1, 0, 0]);\n this._worldForward = math.vec3([0, 0, -1]);\n\n this.deviceMatrix = cfg.deviceMatrix;\n this.eye = cfg.eye;\n this.look = cfg.look;\n this.up = cfg.up;\n this.worldAxis = cfg.worldAxis;\n this.gimbalLock = cfg.gimbalLock;\n this.constrainPitch = cfg.constrainPitch;\n\n this.projection = cfg.projection;\n\n this._perspective.on(\"matrix\", () => {\n if (this._projectionType === \"perspective\") {\n this.fire(\"projMatrix\", this._perspective.matrix);\n }\n });\n this._ortho.on(\"matrix\", () => {\n if (this._projectionType === \"ortho\") {\n this.fire(\"projMatrix\", this._ortho.matrix);\n }\n });\n this._frustum.on(\"matrix\", () => {\n if (this._projectionType === \"frustum\") {\n this.fire(\"projMatrix\", this._frustum.matrix);\n }\n });\n this._customProjection.on(\"matrix\", () => {\n if (this._projectionType === \"customProjection\") {\n this.fire(\"projMatrix\", this._customProjection.matrix);\n }\n });\n }\n\n _update() {\n const state = this._state;\n // In ortho mode, build the view matrix with an eye position that's translated\n // well back from look, so that the front sectionPlane plane doesn't unexpectedly cut\n // the front off the view (not a problem with perspective, since objects close enough\n // to be clipped by the front plane are usually too big to see anything of their cross-sections).\n let eye;\n if (this.projection === \"ortho\") {\n math.subVec3(this._eye, this._look, eyeLookVec);\n math.normalizeVec3(eyeLookVec, eyeLookVecNorm);\n math.mulVec3Scalar(eyeLookVecNorm, 1000.0, eyeLookOffset);\n math.addVec3(this._look, eyeLookOffset, offsetEye);\n eye = offsetEye;\n } else {\n eye = this._eye;\n }\n if (state.hasDeviceMatrix) {\n math.lookAtMat4v(eye, this._look, this._up, tempMatb);\n math.mulMat4(state.deviceMatrix, tempMatb, state.matrix);\n //state.matrix.set(state.deviceMatrix);\n } else {\n math.lookAtMat4v(eye, this._look, this._up, state.matrix);\n }\n math.inverseMat4(this._state.matrix, this._state.inverseMatrix);\n math.transposeMat4(this._state.inverseMatrix, this._state.normalMatrix);\n this.glRedraw();\n this.fire(\"matrix\", this._state.matrix);\n this.fire(\"viewMatrix\", this._state.matrix);\n }\n\n /**\n * Rotates {@link Camera#eye} about {@link Camera#look}, around the {@link Camera#up} vector\n *\n * @param {Number} angleInc Angle of rotation in degrees\n */\n orbitYaw(angleInc) {\n let lookEyeVec = math.subVec3(this._eye, this._look, tempVec3$6);\n math.rotationMat4v(angleInc * 0.0174532925, this._gimbalLock ? this._worldUp : this._up, tempMat);\n lookEyeVec = math.transformPoint3(tempMat, lookEyeVec, tempVec3b$y);\n this.eye = math.addVec3(this._look, lookEyeVec, tempVec3c$u); // Set eye position as 'look' plus 'eye' vector\n this.up = math.transformPoint3(tempMat, this._up, tempVec3d$d); // Rotate 'up' vector\n }\n\n /**\n * Rotates {@link Camera#eye} about {@link Camera#look} around the right axis (orthogonal to {@link Camera#up} and \"look\").\n *\n * @param {Number} angleInc Angle of rotation in degrees\n */\n orbitPitch(angleInc) {\n if (this._constrainPitch) {\n angleInc = math.dotVec3(this._up, this._worldUp) / math.DEGTORAD;\n if (angleInc < 1) {\n return;\n }\n }\n let eye2 = math.subVec3(this._eye, this._look, tempVec3$6);\n const left = math.cross3Vec3(math.normalizeVec3(eye2, tempVec3b$y), math.normalizeVec3(this._up, tempVec3c$u));\n math.rotationMat4v(angleInc * 0.0174532925, left, tempMat);\n eye2 = math.transformPoint3(tempMat, eye2, tempVec3d$d);\n this.up = math.transformPoint3(tempMat, this._up, tempVec3e$2);\n this.eye = math.addVec3(eye2, this._look, tempVec3f$2);\n }\n\n /**\n * Rotates {@link Camera#look} about {@link Camera#eye}, around the {@link Camera#up} vector.\n *\n * @param {Number} angleInc Angle of rotation in degrees\n */\n yaw(angleInc) {\n let look2 = math.subVec3(this._look, this._eye, tempVec3$6);\n math.rotationMat4v(angleInc * 0.0174532925, this._gimbalLock ? this._worldUp : this._up, tempMat);\n look2 = math.transformPoint3(tempMat, look2, tempVec3b$y);\n this.look = math.addVec3(look2, this._eye, tempVec3c$u);\n if (this._gimbalLock) {\n this.up = math.transformPoint3(tempMat, this._up, tempVec3d$d);\n }\n }\n\n /**\n * Rotates {@link Camera#look} about {@link Camera#eye}, around the right axis (orthogonal to {@link Camera#up} and \"look\").\n\n * @param {Number} angleInc Angle of rotation in degrees\n */\n pitch(angleInc) {\n if (this._constrainPitch) {\n angleInc = math.dotVec3(this._up, this._worldUp) / math.DEGTORAD;\n if (angleInc < 1) {\n return;\n }\n }\n let look2 = math.subVec3(this._look, this._eye, tempVec3$6);\n const left = math.cross3Vec3(math.normalizeVec3(look2, tempVec3b$y), math.normalizeVec3(this._up, tempVec3c$u));\n math.rotationMat4v(angleInc * 0.0174532925, left, tempMat);\n this.up = math.transformPoint3(tempMat, this._up, tempVec3f$2);\n look2 = math.transformPoint3(tempMat, look2, tempVec3d$d);\n this.look = math.addVec3(look2, this._eye, tempVec3e$2);\n }\n\n /**\n * Pans the Camera along its local X, Y and Z axis.\n *\n * @param pan The pan vector\n */\n pan(pan) {\n const eye2 = math.subVec3(this._eye, this._look, tempVec3$6);\n const vec = [0, 0, 0];\n let v;\n if (pan[0] !== 0) {\n const left = math.cross3Vec3(math.normalizeVec3(eye2, []), math.normalizeVec3(this._up, tempVec3b$y));\n v = math.mulVec3Scalar(left, pan[0]);\n vec[0] += v[0];\n vec[1] += v[1];\n vec[2] += v[2];\n }\n if (pan[1] !== 0) {\n v = math.mulVec3Scalar(math.normalizeVec3(this._up, tempVec3c$u), pan[1]);\n vec[0] += v[0];\n vec[1] += v[1];\n vec[2] += v[2];\n }\n if (pan[2] !== 0) {\n v = math.mulVec3Scalar(math.normalizeVec3(eye2, tempVec3d$d), pan[2]);\n vec[0] += v[0];\n vec[1] += v[1];\n vec[2] += v[2];\n }\n this.eye = math.addVec3(this._eye, vec, tempVec3e$2);\n this.look = math.addVec3(this._look, vec, tempVec3f$2);\n }\n\n /**\n * Increments/decrements the Camera's zoom factor, which is the distance between {@link Camera#eye} and {@link Camera#look}.\n *\n * @param {Number} delta Zoom factor increment.\n */\n zoom(delta) {\n const vec = math.subVec3(this._eye, this._look, tempVec3$6);\n const lenLook = Math.abs(math.lenVec3(vec, tempVec3b$y));\n const newLenLook = Math.abs(lenLook + delta);\n if (newLenLook < 0.5) {\n return;\n }\n const dir = math.normalizeVec3(vec, tempVec3c$u);\n this.eye = math.addVec3(this._look, math.mulVec3Scalar(dir, newLenLook), tempVec3d$d);\n }\n\n /**\n * Sets the position of the Camera's eye.\n *\n * Default value is ````[0,0,10]````.\n *\n * @emits \"eye\" event on change, with the value of this property.\n * @type {Number[]} New eye position.\n */\n set eye(eye) {\n this._eye.set(eye || [0, 0, 10]);\n this._needUpdate(0); // Ensure matrix built on next \"tick\"\n this.fire(\"eye\", this._eye);\n }\n\n /**\n * Gets the position of the Camera's eye.\n *\n * Default vale is ````[0,0,10]````.\n *\n * @type {Number[]} New eye position.\n */\n get eye() {\n return this._eye;\n }\n\n /**\n * Sets the position of this Camera's point-of-interest.\n *\n * Default value is ````[0,0,0]````.\n *\n * @emits \"look\" event on change, with the value of this property.\n *\n * @param {Number[]} look Camera look position.\n */\n set look(look) {\n this._look.set(look || [0, 0, 0]);\n this._needUpdate(0); // Ensure matrix built on next \"tick\"\n this.fire(\"look\", this._look);\n }\n\n /**\n * Gets the position of this Camera's point-of-interest.\n *\n * Default value is ````[0,0,0]````.\n *\n * @returns {Number[]} Camera look position.\n */\n get look() {\n return this._look;\n }\n\n /**\n * Sets the direction of this Camera's {@link Camera#up} vector.\n *\n * @emits \"up\" event on change, with the value of this property.\n *\n * @param {Number[]} up Direction of \"up\".\n */\n set up(up) {\n this._up.set(up || [0, 1, 0]);\n this._needUpdate(0);\n this.fire(\"up\", this._up);\n }\n\n /**\n * Gets the direction of this Camera's {@link Camera#up} vector.\n *\n * @returns {Number[]} Direction of \"up\".\n */\n get up() {\n return this._up;\n }\n\n /**\n * Sets an optional matrix to premultiply into {@link Camera#matrix} matrix.\n *\n * This is intended to be used for stereo rendering with WebVR etc.\n *\n * @param {Number[]} matrix The matrix.\n */\n set deviceMatrix(matrix) {\n this._state.deviceMatrix.set(matrix || [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);\n this._state.hasDeviceMatrix = !!matrix;\n this._needUpdate(0);\n this.fire(\"deviceMatrix\", this._state.deviceMatrix);\n }\n\n /**\n * Gets an optional matrix to premultiply into {@link Camera#matrix} matrix.\n *\n * @returns {Number[]} The matrix.\n */\n get deviceMatrix() {\n return this._state.deviceMatrix;\n }\n\n /**\n * Sets the up, right and forward axis of the World coordinate system.\n *\n * Has format: ````[rightX, rightY, rightZ, upX, upY, upZ, forwardX, forwardY, forwardZ]````\n *\n * Default axis is ````[1, 0, 0, 0, 1, 0, 0, 0, 1]````\n *\n * @param {Number[]} axis The new Wworld coordinate axis.\n */\n set worldAxis(axis) {\n axis = axis || [1, 0, 0, 0, 1, 0, 0, 0, 1];\n if (!this._worldAxis) {\n this._worldAxis = math.vec3(axis);\n } else {\n this._worldAxis.set(axis);\n }\n this._worldRight[0] = this._worldAxis[0];\n this._worldRight[1] = this._worldAxis[1];\n this._worldRight[2] = this._worldAxis[2];\n this._worldUp[0] = this._worldAxis[3];\n this._worldUp[1] = this._worldAxis[4];\n this._worldUp[2] = this._worldAxis[5];\n this._worldForward[0] = this._worldAxis[6];\n this._worldForward[1] = this._worldAxis[7];\n this._worldForward[2] = this._worldAxis[8];\n this.fire(\"worldAxis\", this._worldAxis);\n }\n\n /**\n * Gets the up, right and forward axis of the World coordinate system.\n *\n * Has format: ````[rightX, rightY, rightZ, upX, upY, upZ, forwardX, forwardY, forwardZ]````\n *\n * Default axis is ````[1, 0, 0, 0, 1, 0, 0, 0, 1]````\n *\n * @returns {Number[]} The current World coordinate axis.\n */\n get worldAxis() {\n return this._worldAxis;\n }\n\n /**\n * Gets the direction of World-space \"up\".\n *\n * This is set by {@link Camera#worldAxis}.\n *\n * Default value is ````[0,1,0]````.\n *\n * @returns {Number[]} The \"up\" vector.\n */\n get worldUp() {\n return this._worldUp;\n }\n\n /**\n * Gets if the World-space X-axis is \"up\".\n * @returns {Boolean}\n */\n get xUp() {\n return this._worldUp[0] > this._worldUp[1] && this._worldUp[0] > this._worldUp[2];\n }\n\n /**\n * Gets if the World-space Y-axis is \"up\".\n * @returns {Boolean}\n */\n get yUp() {\n return this._worldUp[1] > this._worldUp[0] && this._worldUp[1] > this._worldUp[2];\n }\n\n /**\n * Gets if the World-space Z-axis is \"up\".\n * @returns {Boolean}\n */\n get zUp() {\n return this._worldUp[2] > this._worldUp[0] && this._worldUp[2] > this._worldUp[1];\n }\n\n /**\n * Gets the direction of World-space \"right\".\n *\n * This is set by {@link Camera#worldAxis}.\n *\n * Default value is ````[1,0,0]````.\n *\n * @returns {Number[]} The \"up\" vector.\n */\n get worldRight() {\n return this._worldRight;\n }\n\n /**\n * Gets the direction of World-space \"forwards\".\n *\n * This is set by {@link Camera#worldAxis}.\n *\n * Default value is ````[0,0,1]````.\n *\n * @returns {Number[]} The \"up\" vector.\n */\n get worldForward() {\n return this._worldForward;\n }\n\n /**\n * Sets whether to lock yaw rotation to pivot about the World-space \"up\" axis.\n *\n * Fires a {@link Camera#gimbalLock:event} event on change.\n *\n * @params {Boolean} gimbalLock Set true to lock gimbal.\n */\n set gimbalLock(value) {\n this._gimbalLock = value !== false;\n this.fire(\"gimbalLock\", this._gimbalLock);\n }\n\n /**\n * Gets whether to lock yaw rotation to pivot about the World-space \"up\" axis.\n *\n * @returns {Boolean} Returns ````true```` if gimbal is locked.\n */\n get gimbalLock() {\n return this._gimbalLock;\n }\n\n /**\n * Sets whether to prevent camera from being pitched upside down.\n *\n * The camera is upside down when the angle between {@link Camera#up} and {@link Camera#worldUp} is less than one degree.\n *\n * Fires a {@link Camera#constrainPitch:event} event on change.\n *\n * Default value is ````false````.\n *\n * @param {Boolean} value Set ````true```` to contrain pitch rotation.\n */\n set constrainPitch(value) {\n this._constrainPitch = !!value;\n this.fire(\"constrainPitch\", this._constrainPitch);\n }\n\n /**\n * Gets whether to prevent camera from being pitched upside down.\n *\n * The camera is upside down when the angle between {@link Camera#up} and {@link Camera#worldUp} is less than one degree.\n *\n * Default value is ````false````.\n *\n * @returns {Boolean} ````true```` if pitch rotation is currently constrained.\n get constrainPitch() {\n return this._constrainPitch;\n }\n\n /**\n * Gets distance from {@link Camera#look} to {@link Camera#eye}.\n *\n * @returns {Number} The distance.\n */\n get eyeLookDist() {\n return math.lenVec3(math.subVec3(this._look, this._eye, tempVec3$6));\n }\n\n /**\n * Gets the Camera's viewing transformation matrix.\n *\n * Fires a {@link Camera#matrix:event} event on change.\n *\n * @returns {Number[]} The viewing transform matrix.\n */\n get matrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.matrix;\n }\n\n /**\n * Gets the Camera's viewing transformation matrix.\n *\n * Fires a {@link Camera#matrix:event} event on change.\n *\n * @returns {Number[]} The viewing transform matrix.\n */\n get viewMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.matrix;\n }\n\n /**\n * The Camera's viewing normal transformation matrix.\n *\n * Fires a {@link Camera#matrix:event} event on change.\n *\n * @returns {Number[]} The viewing normal transform matrix.\n */\n get normalMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.normalMatrix;\n }\n\n /**\n * The Camera's viewing normal transformation matrix.\n *\n * Fires a {@link Camera#matrix:event} event on change.\n *\n * @returns {Number[]} The viewing normal transform matrix.\n */\n get viewNormalMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.normalMatrix;\n }\n\n /**\n * Gets the inverse of the Camera's viewing transform matrix.\n *\n * This has the same value as {@link Camera#normalMatrix}.\n *\n * @returns {Number[]} The inverse viewing transform matrix.\n */\n get inverseViewMatrix() {\n if (this._updateScheduled) {\n this._doUpdate();\n }\n return this._state.inverseMatrix;\n }\n\n /**\n * Gets the Camera's projection transformation projMatrix.\n *\n * Fires a {@link Camera#projMatrix:event} event on change.\n *\n * @returns {Number[]} The projection matrix.\n */\n get projMatrix() {\n return this[this.projection].matrix;\n }\n\n /**\n * Gets the Camera's perspective projection.\n *\n * The Camera uses this while {@link Camera#projection} equals ````perspective````.\n *\n * @returns {Perspective} The Perspective component.\n */\n get perspective() {\n return this._perspective;\n }\n\n /**\n * Gets the Camera's orthographic projection.\n *\n * The Camera uses this while {@link Camera#projection} equals ````ortho````.\n *\n * @returns {Ortho} The Ortho component.\n */\n get ortho() {\n return this._ortho;\n }\n\n /**\n * Gets the Camera's frustum projection.\n *\n * The Camera uses this while {@link Camera#projection} equals ````frustum````.\n *\n * @returns {Frustum} The Ortho component.\n */\n get frustum() {\n return this._frustum;\n }\n\n /**\n * Gets the Camera's custom projection.\n *\n * This is used while {@link Camera#projection} equals \"customProjection\".\n *\n * @returns {CustomProjection} The custom projection.\n */\n get customProjection() {\n return this._customProjection;\n }\n\n /**\n * Sets the active projection type.\n *\n * Accepted values are ````\"perspective\"````, ````\"ortho\"````, ````\"frustum\"```` and ````\"customProjection\"````.\n *\n * Default value is ````\"perspective\"````.\n *\n * @param {String} value Identifies the active projection type.\n */\n set projection(value) {\n value = value || \"perspective\";\n if (this._projectionType === value) {\n return;\n }\n if (value === \"perspective\") {\n this._project = this._perspective;\n } else if (value === \"ortho\") {\n this._project = this._ortho;\n } else if (value === \"frustum\") {\n this._project = this._frustum;\n } else if (value === \"customProjection\") {\n this._project = this._customProjection;\n } else {\n this.error(\"Unsupported value for 'projection': \" + value + \" defaulting to 'perspective'\");\n this._project = this._perspective;\n value = \"perspective\";\n }\n this._project._update();\n this._projectionType = value;\n this.glRedraw();\n this._update(); // Need to rebuild lookat matrix with full eye, look & up\n this.fire(\"dirty\");\n this.fire(\"projection\", this._projectionType);\n this.fire(\"projMatrix\", this._project.matrix);\n }\n\n /**\n * Gets the active projection type.\n *\n * Possible values are ````\"perspective\"````, ````\"ortho\"````, ````\"frustum\"```` and ````\"customProjection\"````.\n *\n * Default value is ````\"perspective\"````.\n *\n * @returns {String} Identifies the active projection type.\n */\n get projection() {\n return this._projectionType;\n }\n\n /**\n * Gets the currently active projection for this Camera.\n *\n * The currently active project is selected with {@link Camera#projection}.\n *\n * @returns {Perspective|Ortho|Frustum|CustomProjection} The currently active projection is active.\n */\n get project() {\n return this._project;\n }\n\n /**\n * Get the 2D canvas position of a 3D world position.\n *\n * @param {[number, number, number]} worldPos\n * @returns {[number, number]} the canvas position\n */\n projectWorldPos(worldPos) {\n const _worldPos = tempVec4a$8;\n const viewPos = tempVec4b$5;\n const screenPos = tempVec4c$2;\n _worldPos[0] = worldPos[0];\n _worldPos[1] = worldPos[1];\n _worldPos[2] = worldPos[2];\n _worldPos[3] = 1;\n math.mulMat4v4(this.viewMatrix, _worldPos, viewPos);\n math.mulMat4v4(this.projMatrix, viewPos, screenPos);\n math.mulVec3Scalar(screenPos, 1.0 / screenPos[3]);\n screenPos[3] = 1.0;\n screenPos[1] *= -1;\n const canvas = this.scene.canvas.canvas;\n const halfCanvasWidth = canvas.offsetWidth / 2.0;\n const halfCanvasHeight = canvas.offsetHeight / 2.0;\n const canvasPos = [\n screenPos[0] * halfCanvasWidth + halfCanvasWidth,\n screenPos[1] * halfCanvasHeight + halfCanvasHeight,\n ];\n return canvasPos;\n }\n\n /**\n * Destroys this Camera.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\n/**\n * @desc A dynamic light source within a {@link Scene}.\n *\n * These are registered by {@link Light#id} in {@link Scene#lights}.\n */\nclass Light extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Light\";\n }\n\n /**\n * @private\n */\n get isLight() {\n return true;\n }\n\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n }\n}\n\n/**\n * @desc A directional light source that illuminates all {@link Mesh}es equally from a given direction.\n *\n * * Has an emission direction vector in {@link DirLight#dir}, but no position.\n * * Defined in either *World* or *View* coordinate space. When in World-space, {@link DirLight#dir} is relative to the\n * World coordinate system, and will appear to move as the {@link Camera} moves. When in View-space, {@link DirLight#dir} is\n * relative to the View coordinate system, and will behave as if fixed to the viewer's head.\n * * {@link AmbientLight}s, {@link DirLight}s and {@link PointLight}s are registered by their {@link Component#id} on {@link Scene#lights}.\n *\n * ## Usage\n *\n * In the example below we'll replace the {@link Scene}'s default light sources with three View-space DirLights.\n *\n * [[Run this example](/examples/index.html#lights_DirLight_view)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildSphereGeometry,\n * buildPlaneGeometry, ReadableGeometry,\n * PhongMaterial, Texture, DirLight} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer and arrange the camera\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * // Replace the Scene's default lights with three custom view-space DirLights\n *\n * viewer.scene.clearLights();\n *\n * new DirLight(viewer.scene, {\n * id: \"keyLight\",\n * dir: [0.8, -0.6, -0.8],\n * color: [1.0, 0.3, 0.3],\n * intensity: 1.0,\n * space: \"view\"\n * });\n *\n * new DirLight(viewer.scene, {\n * id: \"fillLight\",\n * dir: [-0.8, -0.4, -0.4],\n * color: [0.3, 1.0, 0.3],\n * intensity: 1.0,\n * space: \"view\"\n * });\n *\n * new DirLight(viewer.scene, {\n * id: \"rimLight\",\n * dir: [0.2, -0.8, 0.8],\n * color: [0.6, 0.6, 0.6],\n * intensity: 1.0,\n * space: \"view\"\n * });\n *\n *\n * // Create a sphere and ground plane\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * radius: 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.7, 0.7, 0.7],\n * specular: [1.0, 1.0, 1.0],\n * emissive: [0, 0, 0],\n * alpha: 1.0,\n * ambient: [1, 1, 0],\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n *\n * new Mesh(viewer.scene, {\n * geometry: buildPlaneGeometry(ReadableGeometry, viewer.scene, {\n * xSize: 30,\n * zSize: 30\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * backfaces: true\n * }),\n * position: [0, -2.1, 0]\n * });\n * ````\n */\nclass DirLight extends Light {\n\n /**\n @private\n */\n get type() {\n return \"DirLight\";\n }\n\n /**\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this DirLight as well.\n * @param {*} [cfg] The DirLight configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.dir=[1.0, 1.0, 1.0]] A unit vector indicating the direction that the light is shining, given in either World or View space, depending on the value of the ````space```` parameter.\n * @param {Number[]} [cfg.color=[0.7, 0.7, 0.8 ]] The color of this DirLight.\n * @param {Number} [cfg.intensity=1.0] The intensity of this DirLight, as a factor in range ````[0..1]````.\n * @param {String} [cfg.space=\"view\"] The coordinate system the DirLight is defined in - ````\"view\"```` or ````\"space\"````.\n * @param {Boolean} [cfg.castsShadow=false] Flag which indicates if this DirLight casts a castsShadow.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._shadowRenderBuf = null;\n this._shadowViewMatrix = null;\n this._shadowProjMatrix = null;\n this._shadowViewMatrixDirty = true;\n this._shadowProjMatrixDirty = true;\n\n const camera = this.scene.camera;\n const canvas = this.scene.canvas;\n\n this._onCameraViewMatrix = camera.on(\"viewMatrix\", () => {\n this._shadowViewMatrixDirty = true;\n });\n\n this._onCameraProjMatrix = camera.on(\"projMatrix\", () => {\n this._shadowProjMatrixDirty = true;\n });\n\n this._onCanvasBoundary = canvas.on(\"boundary\", () => {\n this._shadowProjMatrixDirty = true;\n });\n\n this._state = new RenderState({\n\n type: \"dir\",\n dir: math.vec3([1.0, 1.0, 1.0]),\n color: math.vec3([0.7, 0.7, 0.8]),\n intensity: 1.0,\n space: cfg.space || \"view\",\n castsShadow: false,\n\n getShadowViewMatrix: () => {\n if (this._shadowViewMatrixDirty) {\n if (!this._shadowViewMatrix) {\n this._shadowViewMatrix = math.identityMat4();\n }\n const camera = this.scene.camera;\n const dir = this._state.dir;\n const look = camera.look;\n const eye = [look[0] - dir[0], look[1] - dir[1], look[2] - dir[2]];\n const up = [0, 1, 0];\n math.lookAtMat4v(eye, look, up, this._shadowViewMatrix);\n this._shadowViewMatrixDirty = false;\n }\n return this._shadowViewMatrix;\n },\n\n getShadowProjMatrix: () => {\n if (this._shadowProjMatrixDirty) { // TODO: Set when canvas resizes\n if (!this._shadowProjMatrix) {\n this._shadowProjMatrix = math.identityMat4();\n }\n math.orthoMat4c(-40, 40, -40, 40, -40.0, 80, this._shadowProjMatrix); // left, right, bottom, top, near, far, dest\n this._shadowProjMatrixDirty = false;\n }\n return this._shadowProjMatrix;\n },\n\n getShadowRenderBuf: () => {\n if (!this._shadowRenderBuf) {\n this._shadowRenderBuf = new RenderBuffer(this.scene.canvas.canvas, this.scene.canvas.gl, {size: [1024, 1024]}); // Super old mobile devices have a limit of 1024x1024 textures\n }\n return this._shadowRenderBuf;\n }\n });\n\n this.dir = cfg.dir;\n this.color = cfg.color;\n this.intensity = cfg.intensity;\n this.castsShadow = cfg.castsShadow;\n\n this.scene._lightCreated(this);\n }\n\n /**\n * Sets the direction in which the DirLight is shining.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @param {Number[]} value The direction vector.\n */\n set dir(value) {\n this._state.dir.set(value || [1.0, 1.0, 1.0]);\n this._shadowViewMatrixDirty = true;\n this.glRedraw();\n }\n\n /**\n * Gets the direction in which the DirLight is shining.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @returns {Number[]} The direction vector.\n */\n get dir() {\n return this._state.dir;\n }\n\n /**\n * Sets the RGB color of this DirLight.\n *\n * Default value is ````[0.7, 0.7, 0.8]````.\n *\n * @param {Number[]} color The DirLight's RGB color.\n */\n set color(color) {\n this._state.color.set(color || [0.7, 0.7, 0.8]);\n this.glRedraw();\n }\n\n /**\n * Gets the RGB color of this DirLight.\n *\n * Default value is ````[0.7, 0.7, 0.8]````.\n *\n * @returns {Number[]} The DirLight's RGB color.\n */\n get color() {\n return this._state.color;\n }\n\n /**\n * Sets the intensity of this DirLight.\n *\n * Default intensity is ````1.0```` for maximum intensity.\n *\n * @param {Number} intensity The DirLight's intensity\n */\n set intensity(intensity) {\n intensity = intensity !== undefined ? intensity : 1.0;\n this._state.intensity = intensity;\n this.glRedraw();\n }\n\n /**\n * Gets the intensity of this DirLight.\n *\n * Default value is ````1.0```` for maximum intensity.\n *\n * @returns {Number} The DirLight's intensity.\n */\n get intensity() {\n return this._state.intensity;\n }\n\n /**\n * Sets if this DirLight casts a shadow.\n *\n * Default value is ````false````.\n *\n * @param {Boolean} castsShadow Set ````true```` to cast shadows.\n */\n set castsShadow(castsShadow) {\n castsShadow = !!castsShadow;\n if (this._state.castsShadow === castsShadow) {\n return;\n }\n this._state.castsShadow = castsShadow;\n this._shadowViewMatrixDirty = true;\n this.glRedraw();\n }\n\n /**\n * Gets if this DirLight casts a shadow.\n *\n * Default value is ````false````.\n *\n * @returns {Boolean} ````true```` if this DirLight casts shadows.\n */\n get castsShadow() {\n return this._state.castsShadow;\n }\n\n /**\n * Destroys this DirLight.\n */\n destroy() {\n\n const camera = this.scene.camera;\n const canvas = this.scene.canvas;\n camera.off(this._onCameraViewMatrix);\n camera.off(this._onCameraProjMatrix);\n canvas.off(this._onCanvasBoundary);\n\n super.destroy();\n this._state.destroy();\n if (this._shadowRenderBuf) {\n this._shadowRenderBuf.destroy();\n }\n this.scene._lightDestroyed(this);\n this.glRedraw();\n }\n}\n\n/**\n * @desc An ambient light source of fixed color and intensity that illuminates all {@link Mesh}es equally.\n *\n * * {@link AmbientLight#color} multiplies by {@link PhongMaterial#ambient} at each position of each {@link ReadableGeometry} surface.\n * * {@link AmbientLight#color} multiplies by {@link LambertMaterial#color} uniformly across each triangle of each {@link ReadableGeometry} (ie. flat shaded).\n * * {@link AmbientLight}s, {@link DirLight}s and {@link PointLight}s are registered by their {@link Component#id} on {@link Scene#lights}.\n *\n * ## Usage\n *\n * In the example below we'll destroy the {@link Scene}'s default light sources then create an AmbientLight and a couple of {@link @DirLight}s:\n *\n * [[Run this example](/examples/index.html#lights_AmbientLight)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildTorusGeometry,\n * ReadableGeometry, PhongMaterial, Texture, AmbientLight} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer and arrange the camera\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * // Replace the Scene's default lights with a single custom AmbientLight\n *\n * viewer.scene.clearLights();\n *\n * new AmbientLight(viewer.scene, {\n * color: [0.0, 0.3, 0.7],\n * intensity: 1.0\n * });\n *\n * new DirLight(viewer.scene, {\n * id: \"keyLight\",\n * dir: [0.8, -0.6, -0.8],\n * color: [1.0, 0.3, 0.3],\n * intensity: 1.0,\n * space: \"view\"\n * });\n *\n * new DirLight(viewer.scene, {\n * id: \"fillLight\",\n * dir: [-0.8, -0.4, -0.4],\n * color: [0.3, 1.0, 0.3],\n * intensity: 1.0,\n * space: \"view\"\n * });\n *\n * new DirLight(viewer.scene, {\n * id: \"rimLight\",\n * dir: [0.2, -0.8, 0.8],\n * color: [0.6, 0.6, 0.6],\n * intensity: 1.0,\n * space: \"view\"\n * });\n *\n * // Create a mesh with torus shape and PhongMaterial\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * center: [0, 0, 0],\n * radius: 1.5,\n * tube: 0.5,\n * radialSegments: 32,\n * tubeSegments: 24,\n * arc: Math.PI * 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * ambient: [1.0, 1.0, 1.0],\n * shininess: 30,\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n *\n * // Adjust the color of our AmbientLight\n *\n * var ambientLight = viewer.scene.lights[\"myAmbientLight\"];\n * ambientLight.color = [1.0, 0.8, 0.8];\n *````\n */\nclass AmbientLight extends Light {\n\n /**\n @private\n */\n get type() {\n return \"AmbientLight\";\n }\n\n /**\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this AmbientLight as well.\n * @param {*} [cfg] AmbientLight configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.color=[0.7, 0.7, 0.8]] The color of this AmbientLight.\n * @param {Number} [cfg.intensity=[1.0]] The intensity of this AmbientLight, as a factor in range ````[0..1]````.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this._state = {\n type: \"ambient\",\n color: math.vec3([0.7, 0.7, 0.7]),\n intensity: 1.0\n };\n this.color = cfg.color;\n this.intensity = cfg.intensity;\n this.scene._lightCreated(this);\n }\n\n /**\n * Sets the RGB color of this AmbientLight.\n *\n * Default value is ````[0.7, 0.7, 0.8]````.\n *\n * @param {Number[]} color The AmbientLight's RGB color.\n */\n set color(color) {\n this._state.color.set(color || [0.7, 0.7, 0.8]);\n this.glRedraw();\n }\n\n /**\n * Gets the RGB color of this AmbientLight.\n *\n * Default value is ````[0.7, 0.7, 0.8]````.\n *\n * @returns {Number[]} The AmbientLight's RGB color.\n */\n get color() {\n return this._state.color;\n }\n\n /**\n * Sets the intensity of this AmbientLight.\n *\n * Default value is ````1.0```` for maximum intensity.\n *\n * @param {Number} intensity The AmbientLight's intensity.\n */\n set intensity(intensity) {\n this._state.intensity = intensity !== undefined ? intensity : 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the intensity of this AmbientLight.\n *\n * Default value is ````1.0```` for maximum intensity.\n *\n * @returns {Number} The AmbientLight's intensity.\n */\n get intensity() {\n return this._state.intensity;\n }\n\n /**\n * Destroys this AmbientLight.\n */\n destroy() {\n\n super.destroy();\n\n this.scene._lightDestroyed(this);\n }\n}\n\n/**\n * @desc Defines a shape for one or more {@link Mesh}es.\n *\n * * {@link ReadableGeometry} is a subclass that stores its data in both browser and GPU memory. Use ReadableGeometry when you need to keep the geometry arrays in browser memory.\n * * {@link VBOGeometry} is a subclass that stores its data solely in GPU memory. Use VBOGeometry when you need a lower memory footprint and don't need to keep the geometry data in browser memory.\n */\nclass Geometry extends Component {\n\n /** @private */\n get type() {\n return \"Geometry\";\n }\n\n /** @private */\n get isGeometry() {\n return true;\n }\n\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n stats.memory.meshes++;\n }\n\n destroy() {\n super.destroy();\n stats.memory.meshes--;\n }\n}\n\n/**\n * @private\n */\nvar buildEdgeIndices = (function () {\n\n const uniquePositions = [];\n const indicesLookup = [];\n const indicesReverseLookup = [];\n const weldedIndices = [];\n\n// TODO: Optimize with caching, but need to cater to both compressed and uncompressed positions\n\n const faces = [];\n let numFaces = 0;\n const compa = new Uint16Array(3);\n const compb = new Uint16Array(3);\n const compc = new Uint16Array(3);\n const a = math.vec3();\n const b = math.vec3();\n const c = math.vec3();\n const cb = math.vec3();\n const ab = math.vec3();\n const cross = math.vec3();\n const normal = math.vec3();\n\n function weldVertices(positions, indices) {\n const positionsMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)\n let vx;\n let vy;\n let vz;\n let key;\n const precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001\n const precision = Math.pow(10, precisionPoints);\n let i;\n let len;\n let lenUniquePositions = 0;\n for (i = 0, len = positions.length; i < len; i += 3) {\n vx = positions[i];\n vy = positions[i + 1];\n vz = positions[i + 2];\n key = Math.round(vx * precision) + '_' + Math.round(vy * precision) + '_' + Math.round(vz * precision);\n if (positionsMap[key] === undefined) {\n positionsMap[key] = lenUniquePositions / 3;\n uniquePositions[lenUniquePositions++] = vx;\n uniquePositions[lenUniquePositions++] = vy;\n uniquePositions[lenUniquePositions++] = vz;\n }\n indicesLookup[i / 3] = positionsMap[key];\n }\n for (i = 0, len = indices.length; i < len; i++) {\n weldedIndices[i] = indicesLookup[indices[i]];\n indicesReverseLookup[weldedIndices[i]] = indices[i];\n }\n }\n\n function buildFaces(numIndices, positionsDecodeMatrix) {\n numFaces = 0;\n for (let i = 0, len = numIndices; i < len; i += 3) {\n const ia = ((weldedIndices[i]) * 3);\n const ib = ((weldedIndices[i + 1]) * 3);\n const ic = ((weldedIndices[i + 2]) * 3);\n if (positionsDecodeMatrix) {\n compa[0] = uniquePositions[ia];\n compa[1] = uniquePositions[ia + 1];\n compa[2] = uniquePositions[ia + 2];\n compb[0] = uniquePositions[ib];\n compb[1] = uniquePositions[ib + 1];\n compb[2] = uniquePositions[ib + 2];\n compc[0] = uniquePositions[ic];\n compc[1] = uniquePositions[ic + 1];\n compc[2] = uniquePositions[ic + 2];\n // Decode\n math.decompressPosition(compa, positionsDecodeMatrix, a);\n math.decompressPosition(compb, positionsDecodeMatrix, b);\n math.decompressPosition(compc, positionsDecodeMatrix, c);\n } else {\n a[0] = uniquePositions[ia];\n a[1] = uniquePositions[ia + 1];\n a[2] = uniquePositions[ia + 2];\n b[0] = uniquePositions[ib];\n b[1] = uniquePositions[ib + 1];\n b[2] = uniquePositions[ib + 2];\n c[0] = uniquePositions[ic];\n c[1] = uniquePositions[ic + 1];\n c[2] = uniquePositions[ic + 2];\n }\n math.subVec3(c, b, cb);\n math.subVec3(a, b, ab);\n math.cross3Vec3(cb, ab, cross);\n math.normalizeVec3(cross, normal);\n const face = faces[numFaces] || (faces[numFaces] = {normal: math.vec3()});\n face.normal[0] = normal[0];\n face.normal[1] = normal[1];\n face.normal[2] = normal[2];\n numFaces++;\n }\n }\n\n return function (positions, indices, positionsDecodeMatrix, edgeThreshold) {\n weldVertices(positions, indices);\n buildFaces(indices.length, positionsDecodeMatrix);\n const edgeIndices = [];\n const thresholdDot = Math.cos(math.DEGTORAD * edgeThreshold);\n const edges = {};\n let edge1;\n let edge2;\n let index1;\n let index2;\n let key;\n let largeIndex = false;\n let edge;\n let normal1;\n let normal2;\n let dot;\n let ia;\n let ib;\n for (let i = 0, len = indices.length; i < len; i += 3) {\n const faceIndex = i / 3;\n for (let j = 0; j < 3; j++) {\n edge1 = weldedIndices[i + j];\n edge2 = weldedIndices[i + ((j + 1) % 3)];\n index1 = Math.min(edge1, edge2);\n index2 = Math.max(edge1, edge2);\n key = index1 + \",\" + index2;\n if (edges[key] === undefined) {\n edges[key] = {\n index1: index1,\n index2: index2,\n face1: faceIndex,\n face2: undefined\n };\n } else {\n edges[key].face2 = faceIndex;\n }\n }\n }\n for (key in edges) {\n edge = edges[key];\n // an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.\n if (edge.face2 !== undefined) {\n normal1 = faces[edge.face1].normal;\n normal2 = faces[edge.face2].normal;\n dot = math.dotVec3(normal1, normal2);\n if (dot > thresholdDot) {\n continue;\n }\n }\n ia = indicesReverseLookup[edge.index1];\n ib = indicesReverseLookup[edge.index2];\n if (!largeIndex && ia > 65535 || ib > 65535) {\n largeIndex = true;\n }\n edgeIndices.push(ia);\n edgeIndices.push(ib);\n }\n return (largeIndex) ? new Uint32Array(edgeIndices) : new Uint16Array(edgeIndices);\n };\n})();\n\n/**\n * Private geometry compression and decompression utilities.\n */\n\n/**\n * @private\n * @param array\n * @returns {{min: Float32Array, max: Float32Array}}\n */\nfunction getPositionsBounds(array) {\n const min = new Float32Array(3);\n const max = new Float32Array(3);\n let i, j;\n for (i = 0; i < 3; i++) {\n min[i] = Number.MAX_VALUE;\n max[i] = -Number.MAX_VALUE;\n }\n for (i = 0; i < array.length; i += 3) {\n for (j = 0; j < 3; j++) {\n min[j] = Math.min(min[j], array[i + j]);\n max[j] = Math.max(max[j], array[i + j]);\n }\n }\n return {\n min: min,\n max: max\n };\n}\n\nconst createPositionsDecodeMatrix$1 = (function () {\n const translate = math.mat4();\n const scale = math.mat4();\n return function (aabb, positionsDecodeMatrix) {\n positionsDecodeMatrix = positionsDecodeMatrix || math.mat4();\n const xmin = aabb[0];\n const ymin = aabb[1];\n const zmin = aabb[2];\n const xwid = aabb[3] - xmin;\n const ywid = aabb[4] - ymin;\n const zwid = aabb[5] - zmin;\n const maxInt = 65535;\n math.identityMat4(translate);\n math.translationMat4v(aabb, translate);\n math.identityMat4(scale);\n math.scalingMat4v([xwid / maxInt, ywid / maxInt, zwid / maxInt], scale);\n math.mulMat4(translate, scale, positionsDecodeMatrix);\n return positionsDecodeMatrix;\n };\n})();\n\n/**\n * @private\n */\nvar compressPositions = (function () { // http://cg.postech.ac.kr/research/mesh_comp_mobile/mesh_comp_mobile_conference.pdf\n const translate = math.mat4();\n const scale = math.mat4();\n return function (array, min, max) {\n const quantized = new Uint16Array(array.length);\n const multiplier = new Float32Array([\n max[0] !== min[0] ? 65535 / (max[0] - min[0]) : 0,\n max[1] !== min[1] ? 65535 / (max[1] - min[1]) : 0,\n max[2] !== min[2] ? 65535 / (max[2] - min[2]) : 0\n ]);\n let i;\n for (i = 0; i < array.length; i += 3) {\n quantized[i + 0] = Math.max(0, Math.min(65535, Math.floor((array[i + 0] - min[0]) * multiplier[0])));\n quantized[i + 1] = Math.max(0, Math.min(65535, Math.floor((array[i + 1] - min[1]) * multiplier[1])));\n quantized[i + 2] = Math.max(0, Math.min(65535, Math.floor((array[i + 2] - min[2]) * multiplier[2])));\n }\n math.identityMat4(translate);\n math.translationMat4v(min, translate);\n math.identityMat4(scale);\n math.scalingMat4v([\n (max[0] - min[0]) / 65535,\n (max[1] - min[1]) / 65535,\n (max[2] - min[2]) / 65535\n ], scale);\n const decodeMat = math.mulMat4(translate, scale, math.identityMat4());\n return {\n quantized: quantized,\n decodeMatrix: decodeMat\n };\n };\n})();\n\nfunction compressPosition(p, aabb, q) {\n const multiplier = new Float32Array([\n aabb[3] !== aabb[0] ? 65535 / (aabb[3] - aabb[0]) : 0,\n aabb[4] !== aabb[1] ? 65535 / (aabb[4] - aabb[1]) : 0,\n aabb[5] !== aabb[2] ? 65535 / (aabb[5] - aabb[2]) : 0\n ]);\n q[0] = Math.max(0, Math.min(65535, Math.floor((p[0] - aabb[0]) * multiplier[0])));\n q[1] = Math.max(0, Math.min(65535, Math.floor((p[1] - aabb[1]) * multiplier[1])));\n q[2] = Math.max(0, Math.min(65535, Math.floor((p[2] - aabb[2]) * multiplier[2])));\n}\n\nfunction decompressPosition(position, decodeMatrix, dest) {\n dest[0] = position[0] * decodeMatrix[0] + decodeMatrix[12];\n dest[1] = position[1] * decodeMatrix[5] + decodeMatrix[13];\n dest[2] = position[2] * decodeMatrix[10] + decodeMatrix[14];\n return dest;\n}\n\nfunction decompressAABB(aabb, decodeMatrix, dest = aabb) {\n dest[0] = aabb[0] * decodeMatrix[0] + decodeMatrix[12];\n dest[1] = aabb[1] * decodeMatrix[5] + decodeMatrix[13];\n dest[2] = aabb[2] * decodeMatrix[10] + decodeMatrix[14];\n dest[3] = aabb[3] * decodeMatrix[0] + decodeMatrix[12];\n dest[4] = aabb[4] * decodeMatrix[5] + decodeMatrix[13];\n dest[5] = aabb[5] * decodeMatrix[10] + decodeMatrix[14];\n return dest;\n}\n\n/**\n * @private\n */\nfunction decompressPositions(positions, decodeMatrix, dest = new Float32Array(positions.length)) {\n for (let i = 0, len = positions.length; i < len; i += 3) {\n dest[i + 0] = positions[i + 0] * decodeMatrix[0] + decodeMatrix[12];\n dest[i + 1] = positions[i + 1] * decodeMatrix[5] + decodeMatrix[13];\n dest[i + 2] = positions[i + 2] * decodeMatrix[10] + decodeMatrix[14];\n }\n return dest;\n}\n\n//--------------- UVs --------------------------------------------------------------------------------------------------\n\n/**\n * @private\n * @param array\n * @returns {{min: Float32Array, max: Float32Array}}\n */\nfunction getUVBounds(array) {\n const min = new Float32Array(2);\n const max = new Float32Array(2);\n let i, j;\n for (i = 0; i < 2; i++) {\n min[i] = Number.MAX_VALUE;\n max[i] = -Number.MAX_VALUE;\n }\n for (i = 0; i < array.length; i += 2) {\n for (j = 0; j < 2; j++) {\n min[j] = Math.min(min[j], array[i + j]);\n max[j] = Math.max(max[j], array[i + j]);\n }\n }\n return {\n min: min,\n max: max\n };\n}\n\n/**\n * @private\n */\nvar compressUVs = (function () {\n const translate = math.mat3();\n const scale = math.mat3();\n return function (array, min, max) {\n const quantized = new Uint16Array(array.length);\n const multiplier = new Float32Array([\n 65535 / (max[0] - min[0]),\n 65535 / (max[1] - min[1])\n ]);\n let i;\n for (i = 0; i < array.length; i += 2) {\n quantized[i + 0] = Math.max(0, Math.min(65535, Math.floor((array[i + 0] - min[0]) * multiplier[0])));\n quantized[i + 1] = Math.max(0, Math.min(65535, Math.floor((array[i + 1] - min[1]) * multiplier[1])));\n }\n math.identityMat3(translate);\n math.translationMat3v(min, translate);\n math.identityMat3(scale);\n math.scalingMat3v([\n (max[0] - min[0]) / 65535,\n (max[1] - min[1]) / 65535\n ], scale);\n const decodeMat = math.mulMat3(translate, scale, math.identityMat3());\n return {\n quantized: quantized,\n decodeMatrix: decodeMat\n };\n };\n})();\n\n\n//--------------- Normals ----------------------------------------------------------------------------------------------\n\n/**\n * @private\n */\nfunction compressNormals(array) { // http://jcgt.org/published/0003/02/01/\n\n // Note: three elements for each encoded normal, in which the last element in each triplet is redundant.\n // This is to work around a mysterious WebGL issue where 2-element normals just wouldn't work in the shader :/\n\n const encoded = new Int8Array(array.length);\n let oct, dec, best, currentCos, bestCos;\n for (let i = 0; i < array.length; i += 3) {\n // Test various combinations of ceil and floor\n // to minimize rounding errors\n best = oct = octEncodeVec3$1(array, i, \"floor\", \"floor\");\n dec = octDecodeVec2$1(oct);\n currentCos = bestCos = dot(array, i, dec);\n oct = octEncodeVec3$1(array, i, \"ceil\", \"floor\");\n dec = octDecodeVec2$1(oct);\n currentCos = dot(array, i, dec);\n if (currentCos > bestCos) {\n best = oct;\n bestCos = currentCos;\n }\n oct = octEncodeVec3$1(array, i, \"floor\", \"ceil\");\n dec = octDecodeVec2$1(oct);\n currentCos = dot(array, i, dec);\n if (currentCos > bestCos) {\n best = oct;\n bestCos = currentCos;\n }\n oct = octEncodeVec3$1(array, i, \"ceil\", \"ceil\");\n dec = octDecodeVec2$1(oct);\n currentCos = dot(array, i, dec);\n if (currentCos > bestCos) {\n best = oct;\n bestCos = currentCos;\n }\n encoded[i] = best[0];\n encoded[i + 1] = best[1];\n }\n return encoded;\n}\n\n/**\n * @private\n */\nfunction octEncodeVec3$1(array, i, xfunc, yfunc) { // Oct-encode single normal vector in 2 bytes\n let x = array[i] / (Math.abs(array[i]) + Math.abs(array[i + 1]) + Math.abs(array[i + 2]));\n let y = array[i + 1] / (Math.abs(array[i]) + Math.abs(array[i + 1]) + Math.abs(array[i + 2]));\n if (array[i + 2] < 0) {\n let tempx = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n let tempy = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n x = tempx;\n y = tempy;\n }\n return new Int8Array([\n Math[xfunc](x * 127.5 + (x < 0 ? -1 : 0)),\n Math[yfunc](y * 127.5 + (y < 0 ? -1 : 0))\n ]);\n}\n\n/**\n * Decode an oct-encoded normal\n */\nfunction octDecodeVec2$1(oct) {\n let x = oct[0];\n let y = oct[1];\n x /= x < 0 ? 127 : 128;\n y /= y < 0 ? 127 : 128;\n const z = 1 - Math.abs(x) - Math.abs(y);\n if (z < 0) {\n x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n }\n const length = Math.sqrt(x * x + y * y + z * z);\n return [\n x / length,\n y / length,\n z / length\n ];\n}\n\n/**\n * Dot product of a normal in an array against a candidate decoding\n * @private\n */\nfunction dot(array, i, vec3) {\n return array[i] * vec3[0] + array[i + 1] * vec3[1] + array[i + 2] * vec3[2];\n}\n\n/**\n * @private\n */\nfunction decompressUV(uv, decodeMatrix, dest) {\n dest[0] = uv[0] * decodeMatrix[0] + decodeMatrix[6];\n dest[1] = uv[1] * decodeMatrix[4] + decodeMatrix[7];\n}\n\n/**\n * @private\n */\nfunction decompressUVs(uvs, decodeMatrix, dest = new Float32Array(uvs.length)) {\n for (let i = 0, len = uvs.length; i < len; i += 3) {\n dest[i + 0] = uvs[i + 0] * decodeMatrix[0] + decodeMatrix[6];\n dest[i + 1] = uvs[i + 1] * decodeMatrix[4] + decodeMatrix[7];\n }\n return dest;\n}\n\n/**\n * @private\n */\nfunction decompressNormal(oct, result) {\n let x = oct[0];\n let y = oct[1];\n x = (2 * x + 1) / 255;\n y = (2 * y + 1) / 255;\n const z = 1 - Math.abs(x) - Math.abs(y);\n if (z < 0) {\n x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n }\n const length = Math.sqrt(x * x + y * y + z * z);\n result[0] = x / length;\n result[1] = y / length;\n result[2] = z / length;\n return result;\n}\n\n/**\n * @private\n */\nfunction decompressNormals(octs, result) {\n for (let i = 0, j = 0, len = octs.length; i < len; i += 2) {\n let x = octs[i + 0];\n let y = octs[i + 1];\n x = (2 * x + 1) / 255;\n y = (2 * y + 1) / 255;\n const z = 1 - Math.abs(x) - Math.abs(y);\n if (z < 0) {\n x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n }\n const length = Math.sqrt(x * x + y * y + z * z);\n result[j + 0] = x / length;\n result[j + 1] = y / length;\n result[j + 2] = z / length;\n j += 3;\n }\n return result;\n}\n\n/**\n * @private\n */\nconst geometryCompressionUtils = {\n\n getPositionsBounds: getPositionsBounds,\n createPositionsDecodeMatrix: createPositionsDecodeMatrix$1,\n compressPositions: compressPositions,\n compressPosition:compressPosition,\n decompressPositions: decompressPositions,\n decompressPosition: decompressPosition,\n decompressAABB: decompressAABB,\n\n getUVBounds: getUVBounds,\n compressUVs: compressUVs,\n decompressUVs: decompressUVs,\n decompressUV: decompressUV,\n\n compressNormals: compressNormals,\n decompressNormals: decompressNormals,\n decompressNormal: decompressNormal\n};\n\nconst memoryStats$1 = stats.memory;\nconst tempAABB$3 = math.AABB3();\n\n/**\n * @desc A {@link Geometry} that keeps its geometry data in both browser and GPU memory.\n *\n * ReadableGeometry uses more memory than {@link VBOGeometry}, which only stores its geometry data in GPU memory.\n *\n * ## Usage\n *\n * Creating a {@link Mesh} with a ReadableGeometry that defines a single triangle, plus a {@link PhongMaterial} with diffuse {@link Texture}:\n *\n * [[Run this example](/examples/index.html#geometry_ReadableGeometry)]\n *\n * ````javascript\n * import {Viewer, Mesh, ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * const myMesh = new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, {\n * primitive: \"triangles\",\n * positions: [0.0, 3, 0.0, -3, -3, 0.0, 3, -3, 0.0],\n * normals: [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0],\n * uv: [0.0, 0.0, 0.5, 1.0, 1.0, 0.0],\n * indices: [0, 1, 2]\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * backfaces: true\n * })\n * });\n *\n * // Get geometry data from browser memory:\n *\n * const positions = myMesh.geometry.positions; // Flat arrays\n * const normals = myMesh.geometry.normals;\n * const uv = myMesh.geometry.uv;\n * const indices = myMesh.geometry.indices;\n *\n * ````\n */\nclass ReadableGeometry extends Geometry {\n\n /**\n @private\n */\n get type() {\n return \"ReadableGeometry\";\n }\n\n /**\n * @private\n * @returns {Boolean}\n */\n get isReadableGeometry() {\n return true;\n }\n\n /**\n *\n @class ReadableGeometry\n @module xeokit\n @submodule geometry\n @constructor\n @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n @param {*} [cfg] Configs\n @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene},\n generated automatically when omitted.\n @param {String:Object} [cfg.meta] Optional map of user-defined metadata to attach to this Geometry.\n @param [cfg.primitive=\"triangles\"] {String} The primitive type. Accepted values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'.\n @param [cfg.positions] {Number[]} Positions array.\n @param [cfg.normals] {Number[]} Vertex normal vectors array.\n @param [cfg.uv] {Number[]} UVs array.\n @param [cfg.colors] {Number[]} Vertex colors.\n @param [cfg.indices] {Number[]} Indices array.\n @param [cfg.autoVertexNormals=false] {Boolean} Set true to automatically generate normal vectors from the positions and\n indices, if those are supplied.\n @param [cfg.compressGeometry=false] {Boolean} Stores positions, colors, normals and UVs in compressGeometry and oct-encoded formats\n for reduced memory footprint and GPU bus usage.\n @param [cfg.edgeThreshold=10] {Number} When a {@link Mesh} renders this Geometry as wireframe,\n this indicates the threshold angle (in degrees) between the face normals of adjacent triangles below which the edge is discarded.\n @extends Component\n * @param owner\n * @param cfg\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({ // Arrays for emphasis effects are got from xeokit.Geometry friend methods\n compressGeometry: !!cfg.compressGeometry,\n primitive: null, // WebGL enum\n primitiveName: null, // String\n positions: null, // Uint16Array when compressGeometry == true, else Float32Array\n normals: null, // Uint8Array when compressGeometry == true, else Float32Array\n colors: null,\n uv: null, // Uint8Array when compressGeometry == true, else Float32Array\n indices: null,\n positionsDecodeMatrix: null, // Set when compressGeometry == true\n uvDecodeMatrix: null, // Set when compressGeometry == true\n positionsBuf: null,\n normalsBuf: null,\n colorsbuf: null,\n uvBuf: null,\n indicesBuf: null,\n hash: \"\"\n });\n\n this._numTriangles = 0;\n\n this._edgeThreshold = cfg.edgeThreshold || 10.0;\n\n // Lazy-generated VBOs\n\n this._edgeIndicesBuf = null;\n this._pickTrianglePositionsBuf = null;\n this._pickTriangleColorsBuf = null;\n\n // Local-space Boundary3D\n\n this._aabbDirty = true;\n\n this._boundingSphere = true;\n this._aabb = null;\n this._aabbDirty = true;\n\n this._obb = null;\n this._obbDirty = true;\n\n const state = this._state;\n const gl = this.scene.canvas.gl;\n\n // Primitive type\n\n cfg.primitive = cfg.primitive || \"triangles\";\n switch (cfg.primitive) {\n case \"points\":\n state.primitive = gl.POINTS;\n state.primitiveName = cfg.primitive;\n break;\n case \"lines\":\n state.primitive = gl.LINES;\n state.primitiveName = cfg.primitive;\n break;\n case \"line-loop\":\n state.primitive = gl.LINE_LOOP;\n state.primitiveName = cfg.primitive;\n break;\n case \"line-strip\":\n state.primitive = gl.LINE_STRIP;\n state.primitiveName = cfg.primitive;\n break;\n case \"triangles\":\n state.primitive = gl.TRIANGLES;\n state.primitiveName = cfg.primitive;\n break;\n case \"triangle-strip\":\n state.primitive = gl.TRIANGLE_STRIP;\n state.primitiveName = cfg.primitive;\n break;\n case \"triangle-fan\":\n state.primitive = gl.TRIANGLE_FAN;\n state.primitiveName = cfg.primitive;\n break;\n default:\n this.error(\"Unsupported value for 'primitive': '\" + cfg.primitive +\n \"' - supported values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', \" +\n \"'triangle-strip' and 'triangle-fan'. Defaulting to 'triangles'.\");\n state.primitive = gl.TRIANGLES;\n state.primitiveName = cfg.primitive;\n }\n\n if (cfg.positions) {\n if (this._state.compressGeometry) {\n const bounds = geometryCompressionUtils.getPositionsBounds(cfg.positions);\n const result = geometryCompressionUtils.compressPositions(cfg.positions, bounds.min, bounds.max);\n state.positions = result.quantized;\n state.positionsDecodeMatrix = result.decodeMatrix;\n } else {\n state.positions = cfg.positions.constructor === Float32Array ? cfg.positions : new Float32Array(cfg.positions);\n }\n }\n if (cfg.colors) {\n state.colors = cfg.colors.constructor === Float32Array ? cfg.colors : new Float32Array(cfg.colors);\n }\n if (cfg.uv) {\n if (this._state.compressGeometry) {\n const bounds = geometryCompressionUtils.getUVBounds(cfg.uv);\n const result = geometryCompressionUtils.compressUVs(cfg.uv, bounds.min, bounds.max);\n state.uv = result.quantized;\n state.uvDecodeMatrix = result.decodeMatrix;\n } else {\n state.uv = cfg.uv.constructor === Float32Array ? cfg.uv : new Float32Array(cfg.uv);\n }\n }\n if (cfg.normals) {\n if (this._state.compressGeometry) {\n state.normals = geometryCompressionUtils.compressNormals(cfg.normals);\n } else {\n state.normals = cfg.normals.constructor === Float32Array ? cfg.normals : new Float32Array(cfg.normals);\n }\n }\n if (cfg.indices) {\n state.indices = (cfg.indices.constructor === Uint32Array || cfg.indices.constructor === Uint16Array) ? cfg.indices : new Uint32Array(cfg.indices);\n if (this._state.primitiveName === \"triangles\") {\n this._numTriangles = (cfg.indices.length / 3);\n }\n }\n\n this._buildHash();\n\n memoryStats$1.meshes++;\n\n this._buildVBOs();\n }\n\n _buildVBOs() {\n const state = this._state;\n const gl = this.scene.canvas.gl;\n if (state.indices) {\n state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, state.indices, state.indices.length, 1, gl.STATIC_DRAW);\n memoryStats$1.indices += state.indicesBuf.numItems;\n }\n if (state.positions) {\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, state.positions, state.positions.length, 3, gl.STATIC_DRAW);\n memoryStats$1.positions += state.positionsBuf.numItems;\n }\n if (state.normals) {\n let normalized = state.compressGeometry;\n state.normalsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, state.normals, state.normals.length, 3, gl.STATIC_DRAW, normalized);\n memoryStats$1.normals += state.normalsBuf.numItems;\n }\n if (state.colors) {\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, state.colors, state.colors.length, 4, gl.STATIC_DRAW);\n memoryStats$1.colors += state.colorsBuf.numItems;\n }\n if (state.uv) {\n state.uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, state.uv, state.uv.length, 2, gl.STATIC_DRAW);\n memoryStats$1.uvs += state.uvBuf.numItems;\n }\n }\n\n _buildHash() {\n const state = this._state;\n const hash = [\"/g\"];\n hash.push(\"/\" + state.primitive + \";\");\n if (state.positions) {\n hash.push(\"p\");\n }\n if (state.colors) {\n hash.push(\"c\");\n }\n if (state.normals || state.autoVertexNormals) {\n hash.push(\"n\");\n }\n if (state.uv) {\n hash.push(\"u\");\n }\n if (state.compressGeometry) {\n hash.push(\"cp\");\n }\n hash.push(\";\");\n state.hash = hash.join(\"\");\n }\n\n _getEdgeIndices() {\n if (!this._edgeIndicesBuf) {\n this._buildEdgeIndices();\n }\n return this._edgeIndicesBuf;\n }\n\n _getPickTrianglePositions() {\n if (!this._pickTrianglePositionsBuf) {\n this._buildPickTriangleVBOs();\n }\n return this._pickTrianglePositionsBuf;\n }\n\n _getPickTriangleColors() {\n if (!this._pickTriangleColorsBuf) {\n this._buildPickTriangleVBOs();\n }\n return this._pickTriangleColorsBuf;\n }\n\n _buildEdgeIndices() { // FIXME: Does not adjust indices after other objects are deleted from vertex buffer!!\n const state = this._state;\n if (!state.positions || !state.indices) {\n return;\n }\n const gl = this.scene.canvas.gl;\n const edgeIndices = buildEdgeIndices(state.positions, state.indices, state.positionsDecodeMatrix, this._edgeThreshold);\n this._edgeIndicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, edgeIndices, edgeIndices.length, 1, gl.STATIC_DRAW);\n memoryStats$1.indices += this._edgeIndicesBuf.numItems;\n }\n\n _buildPickTriangleVBOs() { // Builds positions and indices arrays that allow each triangle to have a unique color\n const state = this._state;\n if (!state.positions || !state.indices) {\n return;\n }\n const gl = this.scene.canvas.gl;\n const arrays = math.buildPickTriangles(state.positions, state.indices, state.compressGeometry);\n const positions = arrays.positions;\n const colors = arrays.colors;\n this._pickTrianglePositionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, positions.length, 3, gl.STATIC_DRAW);\n this._pickTriangleColorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colors, colors.length, 4, gl.STATIC_DRAW, true);\n memoryStats$1.positions += this._pickTrianglePositionsBuf.numItems;\n memoryStats$1.colors += this._pickTriangleColorsBuf.numItems;\n }\n\n _buildPickVertexVBOs() {\n // var state = this._state;\n // if (!state.positions || !state.indices) {\n // return;\n // }\n // var gl = this.scene.canvas.gl;\n // var arrays = math.buildPickVertices(state.positions, state.indices, state.compressGeometry);\n // var pickVertexPositions = arrays.positions;\n // var pickColors = arrays.colors;\n // this._pickVertexPositionsBuf = new xeokit.renderer.ArrayBuf(gl, gl.ARRAY_BUFFER, pickVertexPositions, pickVertexPositions.length, 3, gl.STATIC_DRAW);\n // this._pickVertexColorsBuf = new xeokit.renderer.ArrayBuf(gl, gl.ARRAY_BUFFER, pickColors, pickColors.length, 4, gl.STATIC_DRAW, true);\n // memoryStats.positions += this._pickVertexPositionsBuf.numItems;\n // memoryStats.colors += this._pickVertexColorsBuf.numItems;\n }\n\n _webglContextLost() {\n if (this._sceneVertexBufs) {\n this._sceneVertexBufs.webglContextLost();\n }\n }\n\n _webglContextRestored() {\n if (this._sceneVertexBufs) {\n this._sceneVertexBufs.webglContextRestored();\n }\n this._buildVBOs();\n this._edgeIndicesBuf = null;\n this._pickVertexPositionsBuf = null;\n this._pickTrianglePositionsBuf = null;\n this._pickTriangleColorsBuf = null;\n this._pickVertexPositionsBuf = null;\n this._pickVertexColorsBuf = null;\n }\n\n /**\n * Gets the Geometry's primitive type.\n\n Valid types are: 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'.\n\n @property primitive\n @default \"triangles\"\n @type {String}\n */\n get primitive() {\n return this._state.primitiveName;\n }\n\n /**\n Indicates if this Geometry is quantized.\n\n Compression is an internally-performed optimization which stores positions, colors, normals and UVs\n in quantized and oct-encoded formats for reduced memory footprint and GPU bus usage.\n\n Quantized geometry may not be updated.\n\n @property compressGeometry\n @default false\n @type {Boolean}\n @final\n */\n get compressGeometry() {\n return this._state.compressGeometry;\n }\n\n /**\n The Geometry's vertex positions.\n\n @property positions\n @default null\n @type {Number[]}\n */\n get positions() {\n if (!this._state.positions) {\n return null;\n }\n if (!this._state.compressGeometry) {\n return this._state.positions;\n }\n if (!this._decompressedPositions) {\n this._decompressedPositions = new Float32Array(this._state.positions.length);\n geometryCompressionUtils.decompressPositions(this._state.positions, this._state.positionsDecodeMatrix, this._decompressedPositions);\n }\n return this._decompressedPositions;\n }\n\n set positions(newPositions) {\n const state = this._state;\n const positions = state.positions;\n if (!positions) {\n this.error(\"can't update geometry positions - geometry has no positions\");\n return;\n }\n if (positions.length !== newPositions.length) {\n this.error(\"can't update geometry positions - new positions are wrong length\");\n return;\n }\n if (this._state.compressGeometry) {\n const bounds = geometryCompressionUtils.getPositionsBounds(newPositions);\n const result = geometryCompressionUtils.compressPositions(newPositions, bounds.min, bounds.max);\n newPositions = result.quantized; // TODO: Copy in-place\n state.positionsDecodeMatrix = result.decodeMatrix;\n }\n positions.set(newPositions);\n if (state.positionsBuf) {\n state.positionsBuf.setData(positions);\n }\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n The Geometry's vertex normals.\n\n @property normals\n @default null\n @type {Number[]}\n */\n get normals() {\n if (!this._state.normals) {\n return;\n }\n if (!this._state.compressGeometry) {\n return this._state.normals;\n }\n if (!this._decompressedNormals) {\n const lenCompressed = this._state.normals.length;\n const lenDecompressed = lenCompressed + (lenCompressed / 2); // 2 -> 3\n this._decompressedNormals = new Float32Array(lenDecompressed);\n geometryCompressionUtils.decompressNormals(this._state.normals, this._decompressedNormals);\n }\n return this._decompressedNormals;\n }\n\n set normals(newNormals) {\n if (this._state.compressGeometry) {\n this.error(\"can't update geometry normals - quantized geometry is immutable\"); // But will be eventually\n return;\n }\n const state = this._state;\n const normals = state.normals;\n if (!normals) {\n this.error(\"can't update geometry normals - geometry has no normals\");\n return;\n }\n if (normals.length !== newNormals.length) {\n this.error(\"can't update geometry normals - new normals are wrong length\");\n return;\n }\n normals.set(newNormals);\n if (state.normalsBuf) {\n state.normalsBuf.setData(normals);\n }\n this.glRedraw();\n }\n\n\n /**\n The Geometry's UV coordinates.\n\n @property uv\n @default null\n @type {Number[]}\n */\n get uv() {\n if (!this._state.uv) {\n return null;\n }\n if (!this._state.compressGeometry) {\n return this._state.uv;\n }\n if (!this._decompressedUV) {\n this._decompressedUV = new Float32Array(this._state.uv.length);\n geometryCompressionUtils.decompressUVs(this._state.uv, this._state.uvDecodeMatrix, this._decompressedUV);\n }\n return this._decompressedUV;\n }\n\n set uv(newUV) {\n if (this._state.compressGeometry) {\n this.error(\"can't update geometry UVs - quantized geometry is immutable\"); // But will be eventually\n return;\n }\n const state = this._state;\n const uv = state.uv;\n if (!uv) {\n this.error(\"can't update geometry UVs - geometry has no UVs\");\n return;\n }\n if (uv.length !== newUV.length) {\n this.error(\"can't update geometry UVs - new UVs are wrong length\");\n return;\n }\n uv.set(newUV);\n if (state.uvBuf) {\n state.uvBuf.setData(uv);\n }\n this.glRedraw();\n }\n\n /**\n The Geometry's vertex colors.\n\n @property colors\n @default null\n @type {Number[]}\n */\n get colors() {\n return this._state.colors;\n }\n\n set colors(newColors) {\n if (this._state.compressGeometry) {\n this.error(\"can't update geometry colors - quantized geometry is immutable\"); // But will be eventually\n return;\n }\n const state = this._state;\n const colors = state.colors;\n if (!colors) {\n this.error(\"can't update geometry colors - geometry has no colors\");\n return;\n }\n if (colors.length !== newColors.length) {\n this.error(\"can't update geometry colors - new colors are wrong length\");\n return;\n }\n colors.set(newColors);\n if (state.colorsBuf) {\n state.colorsBuf.setData(colors);\n }\n this.glRedraw();\n }\n\n /**\n The Geometry's indices.\n\n If ````xeokit.WEBGL_INFO.SUPPORTED_EXTENSIONS[\"OES_element_index_uint\"]```` is true, then this can be\n a ````Uint32Array````, otherwise it needs to be a ````Uint16Array````.\n\n @property indices\n @default null\n @type Uint16Array | Uint32Array\n @final\n */\n get indices() {\n return this._state.indices;\n }\n\n /**\n * Local-space axis-aligned 3D boundary (AABB) of this geometry.\n *\n * The AABB is represented by a six-element Float64Array containing the min/max extents of the\n * axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````.\n *\n * @property aabb\n * @final\n * @type {Number[]}\n */\n get aabb() {\n if (this._aabbDirty) {\n if (!this._aabb) {\n this._aabb = math.AABB3();\n }\n math.positions3ToAABB3(this._state.positions, this._aabb, this._state.positionsDecodeMatrix);\n this._aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Local-space oriented 3D boundary (OBB) of this geometry.\n *\n * The OBB is represented by a 32-element Float64Array containing the eight vertices of the box,\n * where each vertex is a homogeneous coordinate having [x,y,z,w] elements.\n *\n * @property obb\n * @final\n * @type {Number[]}\n */\n get obb() {\n if (this._obbDirty) {\n if (!this._obb) {\n this._obb = math.OBB3();\n }\n math.positions3ToAABB3(this._state.positions, tempAABB$3, this._state.positionsDecodeMatrix);\n math.AABB3ToOBB3(tempAABB$3, this._obb);\n this._obbDirty = false;\n }\n return this._obb;\n }\n\n /**\n * Approximate number of triangles in this ReadableGeometry.\n *\n * Will be zero if {@link ReadableGeometry#primitive} is not 'triangles', 'triangle-strip' or 'triangle-fan'.\n *\n * @type {Number}\n */\n get numTriangles() {\n return this._numTriangles;\n }\n\n _setAABBDirty() {\n if (this._aabbDirty) {\n return;\n }\n this._aabbDirty = true;\n this._aabbDirty = true;\n this._obbDirty = true;\n }\n\n _getState() {\n return this._state;\n }\n\n /**\n * Destroys this ReadableGeometry\n */\n destroy() {\n super.destroy();\n const state = this._state;\n if (state.indicesBuf) {\n state.indicesBuf.destroy();\n }\n if (state.positionsBuf) {\n state.positionsBuf.destroy();\n }\n if (state.normalsBuf) {\n state.normalsBuf.destroy();\n }\n if (state.uvBuf) {\n state.uvBuf.destroy();\n }\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n }\n if (this._edgeIndicesBuf) {\n this._edgeIndicesBuf.destroy();\n }\n if (this._pickTrianglePositionsBuf) {\n this._pickTrianglePositionsBuf.destroy();\n }\n if (this._pickTriangleColorsBuf) {\n this._pickTriangleColorsBuf.destroy();\n }\n if (this._pickVertexPositionsBuf) {\n this._pickVertexPositionsBuf.destroy();\n }\n if (this._pickVertexColorsBuf) {\n this._pickVertexColorsBuf.destroy();\n }\n state.destroy();\n memoryStats$1.meshes--;\n }\n}\n\n/**\n * @desc Creates box-shaped {@link Geometry}.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with a box-shaped {@link ReadableGeometry}.\n *\n * [[Run this example](/examples/index.html#geometry_builders_buildBoxGeometry)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildBoxGeometry, ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildBoxGeometry({\n * center: [0,0,0],\n * xSize: 1, // Half-size on each axis\n * ySize: 1,\n * zSize: 1\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n * ````\n *\n * @function buildBoxGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.center] 3D point indicating the center position.\n * @param {Number} [cfg.xSize=1.0] Half-size on the X-axis.\n * @param {Number} [cfg.ySize=1.0] Half-size on the Y-axis.\n * @param {Number} [cfg.zSize=1.0] Half-size on the Z-axis.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildBoxGeometry(cfg = {}) {\n\n let xSize = cfg.xSize || 1;\n if (xSize < 0) {\n console.error(\"negative xSize not allowed - will invert\");\n xSize *= -1;\n }\n\n let ySize = cfg.ySize || 1;\n if (ySize < 0) {\n console.error(\"negative ySize not allowed - will invert\");\n ySize *= -1;\n }\n\n let zSize = cfg.zSize || 1;\n if (zSize < 0) {\n console.error(\"negative zSize not allowed - will invert\");\n zSize *= -1;\n }\n\n const center = cfg.center;\n const centerX = center ? center[0] : 0;\n const centerY = center ? center[1] : 0;\n const centerZ = center ? center[2] : 0;\n\n const xmin = -xSize + centerX;\n const ymin = -ySize + centerY;\n const zmin = -zSize + centerZ;\n const xmax = xSize + centerX;\n const ymax = ySize + centerY;\n const zmax = zSize + centerZ;\n\n return utils.apply(cfg, {\n\n // The vertices - eight for our cube, each\n // one spanning three array elements for X,Y and Z\n positions: [\n\n // v0-v1-v2-v3 front\n xmax, ymax, zmax,\n xmin, ymax, zmax,\n xmin, ymin, zmax,\n xmax, ymin, zmax,\n\n // v0-v3-v4-v1 right\n xmax, ymax, zmax,\n xmax, ymin, zmax,\n xmax, ymin, zmin,\n xmax, ymax, zmin,\n\n // v0-v1-v6-v1 top\n xmax, ymax, zmax,\n xmax, ymax, zmin,\n xmin, ymax, zmin,\n xmin, ymax, zmax,\n\n // v1-v6-v7-v2 left\n xmin, ymax, zmax,\n xmin, ymax, zmin,\n xmin, ymin, zmin,\n xmin, ymin, zmax,\n\n // v7-v4-v3-v2 bottom\n xmin, ymin, zmin,\n xmax, ymin, zmin,\n xmax, ymin, zmax,\n xmin, ymin, zmax,\n\n // v4-v7-v6-v1 back\n xmax, ymin, zmin,\n xmin, ymin, zmin,\n xmin, ymax, zmin,\n xmax, ymax, zmin\n ],\n\n // Normal vectors, one for each vertex\n normals: [\n\n // v0-v1-v2-v3 front\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n 0, 0, 1,\n\n // v0-v3-v4-v5 right\n 1, 0, 0,\n 1, 0, 0,\n 1, 0, 0,\n 1, 0, 0,\n\n // v0-v5-v6-v1 top\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n 0, 1, 0,\n\n // v1-v6-v7-v2 left\n -1, 0, 0,\n -1, 0, 0,\n -1, 0, 0,\n -1, 0, 0,\n\n // v7-v4-v3-v2 bottom\n 0, -1, 0,\n 0, -1, 0,\n 0, -1, 0,\n 0, -1, 0,\n\n // v4-v7-v6-v5 back\n 0, 0, -1,\n 0, 0, -1,\n 0, 0, -1,\n 0, 0, -1\n ],\n\n // UV coords\n uv: [\n\n // v0-v1-v2-v3 front\n 1, 0,\n 0, 0,\n 0, 1,\n 1, 1,\n\n // v0-v3-v4-v1 right\n 0, 0,\n 0, 1,\n 1, 1,\n 1, 0,\n\n // v0-v1-v6-v1 top\n 1, 1,\n 1, 0,\n 0, 0,\n 0, 1,\n\n // v1-v6-v7-v2 left\n 1, 0,\n 0, 0,\n 0, 1,\n 1, 1,\n\n // v7-v4-v3-v2 bottom\n 0, 1,\n 1, 1,\n 1, 0,\n 0, 0,\n\n // v4-v7-v6-v1 back\n 0, 1,\n 1, 1,\n 1, 0,\n 0, 0\n ],\n\n // Indices - these organise the\n // positions and uv texture coordinates\n // into geometric primitives in accordance\n // with the \"primitive\" parameter,\n // in this case a set of three indices\n // for each triangle.\n //\n // Note that each triangle is specified\n // in counter-clockwise winding order.\n //\n // You can specify them in clockwise\n // order if you configure the Modes\n // node's frontFace flag as \"cw\", instead of\n // the default \"ccw\".\n indices: [\n 0, 1, 2,\n 0, 2, 3,\n // front\n 4, 5, 6,\n 4, 6, 7,\n // right\n 8, 9, 10,\n 8, 10, 11,\n // top\n 12, 13, 14,\n 12, 14, 15,\n // left\n 16, 17, 18,\n 16, 18, 19,\n // bottom\n 20, 21, 22,\n 20, 22, 23\n ]\n });\n}\n\n/**\n * @desc A **Material** defines the surface appearance of attached {@link Mesh}es.\n *\n * Material is the base class for:\n *\n * * {@link MetallicMaterial} - physically-based material for metallic surfaces. Use this one for things made of metal.\n * * {@link SpecularMaterial} - physically-based material for non-metallic (dielectric) surfaces. Use this one for insulators, such as ceramics, plastics, wood etc.\n * * {@link PhongMaterial} - material for classic Blinn-Phong shading. This is less demanding of graphics hardware than the physically-based materials.\n * * {@link LambertMaterial} - material for fast, flat-shaded CAD rendering without textures. Use this for navigating huge CAD or BIM models interactively. This material gives the best rendering performance and uses the least memory.\n * * {@link EmphasisMaterial} - defines the appearance of Meshes when \"xrayed\" or \"highlighted\".\n * * {@link EdgeMaterial} - defines the appearance of Meshes when edges are emphasized.\n *\n * A {@link Scene} is allowed to contain a mixture of these material types.\n *\n */\n\nclass Material extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Material\";\n }\n\n constructor(owner, cfg={}) {\n super(owner, cfg);\n stats.memory.materials++;\n }\n\n destroy() {\n super.destroy();\n stats.memory.materials--;\n }\n}\n\nconst alphaModes$1 = {\"opaque\": 0, \"mask\": 1, \"blend\": 2};\nconst alphaModeNames$1 = [\"opaque\", \"mask\", \"blend\"];\n\n/**\n * @desc Configures the normal rendered appearance of {@link Mesh}es using the non-physically-correct Blinn-Phong shading model.\n *\n * * Useful for non-realistic objects like gizmos.\n * * {@link SpecularMaterial} is best for insulators, such as wood, ceramics and plastic.\n * * {@link MetallicMaterial} is best for conductive materials, such as metal.\n * * {@link LambertMaterial} is appropriate for high-detail models that need to render as efficiently as possible.\n *\n * ## Usage\n *\n * In the example below, we'll create a {@link Mesh} with a PhongMaterial with a diffuse {@link Texture} and a specular {@link Fresnel}, using a {@link buildTorusGeometry} to create the {@link Geometry}.\n *\n * [[Run this example](/examples/index.html#materials_PhongMaterial)]\n *\n * ```` javascript\n * import {Viewer, Mesh, buildTorusGeometry,\n * ReadableGeometry, PhongMaterial, Texture, Fresnel} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({\n * center: [0, 0, 0],\n * radius: 1.5,\n * tube: 0.5,\n * radialSegments: 32,\n * tubeSegments: 24,\n * arc: Math.PI * 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * ambient: [0.9, 0.3, 0.9],\n * shininess: 30,\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * specularFresnel: new Fresnel(viewer.scene, {\n * leftColor: [1.0, 1.0, 1.0],\n * rightColor: [0.0, 0.0, 0.0],\n * power: 4\n * })\n * })\n * });\n * ````\n *\n * ## PhongMaterial Properties\n *\n * The following table summarizes PhongMaterial properties:\n *\n * | Property | Type | Range | Default Value | Space | Description |\n * |:--------:|:----:|:-----:|:-------------:|:-----:|:-----------:|\n * | {@link PhongMaterial#ambient} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the ambient light reflected by the material. |\n * | {@link PhongMaterial#diffuse} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the diffuse light reflected by the material. |\n * | {@link PhongMaterial#specular} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the specular light reflected by the material. |\n * | {@link PhongMaterial#emissive} | Array | [0, 1] for all components | [0,0,0] | linear | The RGB components of the light emitted by the material. |\n * | {@link PhongMaterial#alpha} | Number | [0, 1] | 1 | linear | The transparency of the material surface (0 fully transparent, 1 fully opaque). |\n * | {@link PhongMaterial#shininess} | Number | [0, 128] | 80 | linear | Determines the size and sharpness of specular highlights. |\n * | {@link PhongMaterial#reflectivity} | Number | [0, 1] | 1 | linear | Determines the amount of reflectivity. |\n * | {@link PhongMaterial#diffuseMap} | {@link Texture} | | null | sRGB | Texture RGB components multiplying by {@link PhongMaterial#diffuse}. If the fourth component (A) is present, it multiplies by {@link PhongMaterial#alpha}. |\n * | {@link PhongMaterial#specularMap} | {@link Texture} | | null | sRGB | Texture RGB components multiplying by {@link PhongMaterial#specular}. If the fourth component (A) is present, it multiplies by {@link PhongMaterial#alpha}. |\n * | {@link PhongMaterial#emissiveMap} | {@link Texture} | | null | linear | Texture with RGB components multiplying by {@link PhongMaterial#emissive}. |\n * | {@link PhongMaterial#alphaMap} | {@link Texture} | | null | linear | Texture with first component multiplying by {@link PhongMaterial#alpha}. |\n * | {@link PhongMaterial#occlusionMap} | {@link Texture} | | null | linear | Ambient occlusion texture multiplying by {@link PhongMaterial#ambient}, {@link PhongMaterial#diffuse} and {@link PhongMaterial#specular}. |\n * | {@link PhongMaterial#normalMap} | {@link Texture} | | null | linear | Tangent-space normal map. |\n * | {@link PhongMaterial#diffuseFresnel} | {@link Fresnel} | | null | | Fresnel term applied to {@link PhongMaterial#diffuse}. |\n * | {@link PhongMaterial#specularFresnel} | {@link Fresnel} | | null | | Fresnel term applied to {@link PhongMaterial#specular}. |\n * | {@link PhongMaterial#emissiveFresnel} | {@link Fresnel} | | null | | Fresnel term applied to {@link PhongMaterial#emissive}. |\n * | {@link PhongMaterial#reflectivityFresnel} | {@link Fresnel} | | null | | Fresnel term applied to {@link PhongMaterial#reflectivity}. |\n * | {@link PhongMaterial#alphaFresnel} | {@link Fresnel} | | null | | Fresnel term applied to {@link PhongMaterial#alpha}. |\n * | {@link PhongMaterial#lineWidth} | Number | [0..100] | 1 | | Line width in pixels. |\n * | {@link PhongMaterial#pointSize} | Number | [0..100] | 1 | | Point size in pixels. |\n * | {@link PhongMaterial#alphaMode} | String | \"opaque\", \"blend\", \"mask\" | \"blend\" | | Alpha blend mode. |\n * | {@link PhongMaterial#alphaCutoff} | Number | [0..1] | 0.5 | | Alpha cutoff value. |\n * | {@link PhongMaterial#backfaces} | Boolean | | false | | Whether to render geometry backfaces. |\n * | {@link PhongMaterial#frontface} | String | \"ccw\", \"cw\" | \"ccw\" | | The winding order for geometry frontfaces - \"cw\" for clockwise, or \"ccw\" for counter-clockwise. |\n */\nclass PhongMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"PhongMaterial\";\n }\n\n /**\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] The PhongMaterial configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.ambient=[1.0, 1.0, 1.0 ]] PhongMaterial ambient color.\n * @param {Number[]} [cfg.diffuse=[ 1.0, 1.0, 1.0 ]] PhongMaterial diffuse color.\n * @param {Number[]} [cfg.specular=[ 1.0, 1.0, 1.0 ]] PhongMaterial specular color.\n * @param {Number[]} [cfg.emissive=[ 0.0, 0.0, 0.0 ]] PhongMaterial emissive color.\n * @param {Number} [cfg.alpha=1] Scalar in range 0-1 that controls alpha, where 0 is completely transparent and 1 is completely opaque.\n * @param {Number} [cfg.shininess=80] Scalar in range 0-128 that determines the size and sharpness of specular highlights.\n * @param {Number} [cfg.reflectivity=1] Scalar in range 0-1 that controls how much {@link ReflectionMap} is reflected.\n * @param {Number} [cfg.lineWidth=1] Scalar that controls the width of lines.\n * @param {Number} [cfg.pointSize=1] Scalar that controls the size of points.\n * @param {Texture} [cfg.ambientMap=null] A ambient map {@link Texture}, which will multiply by the diffuse property. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Texture} [cfg.diffuseMap=null] A diffuse map {@link Texture}, which will override the effect of the diffuse property. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Texture} [cfg.specularMap=null] A specular map {@link Texture}, which will override the effect of the specular property. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Texture} [cfg.emissiveMap=undefined] An emissive map {@link Texture}, which will override the effect of the emissive property. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Texture} [cfg.normalMap=undefined] A normal map {@link Texture}. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Texture} [cfg.alphaMap=undefined] An alpha map {@link Texture}, which will override the effect of the alpha property. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Texture} [cfg.reflectivityMap=undefined] A reflectivity control map {@link Texture}, which will override the effect of the reflectivity property. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Texture} [cfg.occlusionMap=null] An occlusion map {@link Texture}. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Fresnel} [cfg.diffuseFresnel=undefined] A diffuse {@link Fresnel\"}}Fresnel{{/crossLink}}. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Fresnel} [cfg.specularFresnel=undefined] A specular {@link Fresnel\"}}Fresnel{{/crossLink}}. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Fresnel} [cfg.emissiveFresnel=undefined] An emissive {@link Fresnel\"}}Fresnel{{/crossLink}}. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Fresnel} [cfg.alphaFresnel=undefined] An alpha {@link Fresnel\"}}Fresnel{{/crossLink}}. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {Fresnel} [cfg.reflectivityFresnel=undefined] A reflectivity {@link Fresnel\"}}Fresnel{{/crossLink}}. Must be within the same {@link Scene} as this PhongMaterial.\n * @param {String} [cfg.alphaMode=\"opaque\"] The alpha blend mode - accepted values are \"opaque\", \"blend\" and \"mask\". See the {@link PhongMaterial#alphaMode} property for more info.\n * @param {Number} [cfg.alphaCutoff=0.5] The alpha cutoff value. See the {@link PhongMaterial#alphaCutoff} property for more info.\n * @param {Boolean} [cfg.backfaces=false] Whether to render geometry backfaces.\n * @param {Boolean} [cfg.frontface=\"ccw\"] The winding order for geometry front faces - \"cw\" for clockwise, or \"ccw\" for counter-clockwise.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"PhongMaterial\",\n ambient: math.vec3([1.0, 1.0, 1.0]),\n diffuse: math.vec3([1.0, 1.0, 1.0]),\n specular: math.vec3([1.0, 1.0, 1.0]),\n emissive: math.vec3([0.0, 0.0, 0.0]),\n alpha: null,\n shininess: null,\n reflectivity: null,\n alphaMode: null,\n alphaCutoff: null,\n lineWidth: null,\n pointSize: null,\n backfaces: null,\n frontface: null, // Boolean for speed; true == \"ccw\", false == \"cw\"\n hash: null\n });\n\n this.ambient = cfg.ambient;\n this.diffuse = cfg.diffuse;\n this.specular = cfg.specular;\n this.emissive = cfg.emissive;\n this.alpha = cfg.alpha;\n this.shininess = cfg.shininess;\n this.reflectivity = cfg.reflectivity;\n this.lineWidth = cfg.lineWidth;\n this.pointSize = cfg.pointSize;\n\n if (cfg.ambientMap) {\n this._ambientMap = this._checkComponent(\"Texture\", cfg.ambientMap);\n }\n if (cfg.diffuseMap) {\n this._diffuseMap = this._checkComponent(\"Texture\", cfg.diffuseMap);\n }\n if (cfg.specularMap) {\n this._specularMap = this._checkComponent(\"Texture\", cfg.specularMap);\n }\n if (cfg.emissiveMap) {\n this._emissiveMap = this._checkComponent(\"Texture\", cfg.emissiveMap);\n }\n if (cfg.alphaMap) {\n this._alphaMap = this._checkComponent(\"Texture\", cfg.alphaMap);\n }\n if (cfg.reflectivityMap) {\n this._reflectivityMap = this._checkComponent(\"Texture\", cfg.reflectivityMap);\n }\n if (cfg.normalMap) {\n this._normalMap = this._checkComponent(\"Texture\", cfg.normalMap);\n }\n if (cfg.occlusionMap) {\n this._occlusionMap = this._checkComponent(\"Texture\", cfg.occlusionMap);\n }\n if (cfg.diffuseFresnel) {\n this._diffuseFresnel = this._checkComponent(\"Fresnel\", cfg.diffuseFresnel);\n }\n if (cfg.specularFresnel) {\n this._specularFresnel = this._checkComponent(\"Fresnel\", cfg.specularFresnel);\n }\n if (cfg.emissiveFresnel) {\n this._emissiveFresnel = this._checkComponent(\"Fresnel\", cfg.emissiveFresnel);\n }\n if (cfg.alphaFresnel) {\n this._alphaFresnel = this._checkComponent(\"Fresnel\", cfg.alphaFresnel);\n }\n if (cfg.reflectivityFresnel) {\n this._reflectivityFresnel = this._checkComponent(\"Fresnel\", cfg.reflectivityFresnel);\n }\n\n this.alphaMode = cfg.alphaMode;\n this.alphaCutoff = cfg.alphaCutoff;\n this.backfaces = cfg.backfaces;\n this.frontface = cfg.frontface;\n\n this._makeHash();\n }\n\n _makeHash() {\n const state = this._state;\n const hash = [\"/p\"]; // 'P' for Phong\n if (this._normalMap) {\n hash.push(\"/nm\");\n if (this._normalMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._ambientMap) {\n hash.push(\"/am\");\n if (this._ambientMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n hash.push(\"/\" + this._ambientMap.encoding);\n }\n if (this._diffuseMap) {\n hash.push(\"/dm\");\n if (this._diffuseMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n hash.push(\"/\" + this._diffuseMap.encoding);\n }\n if (this._specularMap) {\n hash.push(\"/sm\");\n if (this._specularMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._emissiveMap) {\n hash.push(\"/em\");\n if (this._emissiveMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n hash.push(\"/\" + this._emissiveMap.encoding);\n }\n if (this._alphaMap) {\n hash.push(\"/opm\");\n if (this._alphaMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._reflectivityMap) {\n hash.push(\"/rm\");\n if (this._reflectivityMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._occlusionMap) {\n hash.push(\"/ocm\");\n if (this._occlusionMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._diffuseFresnel) {\n hash.push(\"/df\");\n }\n if (this._specularFresnel) {\n hash.push(\"/sf\");\n }\n if (this._emissiveFresnel) {\n hash.push(\"/ef\");\n }\n if (this._alphaFresnel) {\n hash.push(\"/of\");\n }\n if (this._reflectivityFresnel) {\n hash.push(\"/rf\");\n }\n hash.push(\";\");\n state.hash = hash.join(\"\");\n }\n\n /**\n * Sets the PhongMaterial's ambient color.\n *\n * Default value is ````[0.3, 0.3, 0.3]````.\n *\n * @type {Number[]}\n */\n set ambient(value) {\n let ambient = this._state.ambient;\n if (!ambient) {\n ambient = this._state.ambient = new Float32Array(3);\n } else if (value && ambient[0] === value[0] && ambient[1] === value[1] && ambient[2] === value[2]) {\n return;\n }\n if (value) {\n ambient[0] = value[0];\n ambient[1] = value[1];\n ambient[2] = value[2];\n } else {\n ambient[0] = .2;\n ambient[1] = .2;\n ambient[2] = .2;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial's ambient color.\n *\n * Default value is ````[0.3, 0.3, 0.3]````.\n *\n * @type {Number[]}\n */\n get ambient() {\n return this._state.ambient;\n }\n\n /**\n * Sets the PhongMaterial's diffuse color.\n *\n * Multiplies by {@link PhongMaterial#diffuseMap}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @type {Number[]}\n */\n set diffuse(value) {\n let diffuse = this._state.diffuse;\n if (!diffuse) {\n diffuse = this._state.diffuse = new Float32Array(3);\n } else if (value && diffuse[0] === value[0] && diffuse[1] === value[1] && diffuse[2] === value[2]) {\n return;\n }\n if (value) {\n diffuse[0] = value[0];\n diffuse[1] = value[1];\n diffuse[2] = value[2];\n } else {\n diffuse[0] = 1;\n diffuse[1] = 1;\n diffuse[2] = 1;\n }\n this.glRedraw();\n }\n\n /**\n * Sets the PhongMaterial's diffuse color.\n *\n * Multiplies by {@link PhongMaterial#diffuseMap}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @type {Number[]}\n */\n get diffuse() {\n return this._state.diffuse;\n }\n\n /**\n * Sets the PhongMaterial's specular color.\n *\n * Multiplies by {@link PhongMaterial#specularMap}.\n * Default value is ````[1.0, 1.0, 1.0]````.\n * @type {Number[]}\n */\n set specular(value) {\n let specular = this._state.specular;\n if (!specular) {\n specular = this._state.specular = new Float32Array(3);\n } else if (value && specular[0] === value[0] && specular[1] === value[1] && specular[2] === value[2]) {\n return;\n }\n if (value) {\n specular[0] = value[0];\n specular[1] = value[1];\n specular[2] = value[2];\n } else {\n specular[0] = 1;\n specular[1] = 1;\n specular[2] = 1;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial's specular color.\n *\n * Multiplies by {@link PhongMaterial#specularMap}.\n * Default value is ````[1.0, 1.0, 1.0]````.\n * @type {Number[]}\n */\n get specular() {\n return this._state.specular;\n }\n\n /**\n * Sets the PhongMaterial's emissive color.\n *\n * Multiplies by {@link PhongMaterial#emissiveMap}.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n * @type {Number[]}\n */\n set emissive(value) {\n let emissive = this._state.emissive;\n if (!emissive) {\n emissive = this._state.emissive = new Float32Array(3);\n } else if (value && emissive[0] === value[0] && emissive[1] === value[1] && emissive[2] === value[2]) {\n return;\n }\n if (value) {\n emissive[0] = value[0];\n emissive[1] = value[1];\n emissive[2] = value[2];\n } else {\n emissive[0] = 0;\n emissive[1] = 0;\n emissive[2] = 0;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial's emissive color.\n *\n * Multiplies by {@link PhongMaterial#emissiveMap}.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n * @type {Number[]}\n */\n get emissive() {\n return this._state.emissive;\n }\n\n /**\n * Sets the PhongMaterial alpha.\n *\n * This is a factor in the range [0..1] indicating how transparent the PhongMaterial is.\n *\n * A value of 0.0 indicates fully transparent, 1.0 is fully opaque.\n *\n * Multiplies by {@link PhongMaterial#alphaMap}.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set alpha(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.alpha === value) {\n return;\n }\n this._state.alpha = value;\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial alpha.\n *\n * This is a factor in the range [0..1] indicating how transparent the PhongMaterial is.\n *\n * A value of 0.0 indicates fully transparent, 1.0 is fully opaque.\n *\n * Multiplies by {@link PhongMaterial#alphaMap}.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n get alpha() {\n return this._state.alpha;\n }\n\n /**\n * Sets the PhongMaterial shininess.\n *\n * This is a factor in range [0..128] that determines the size and sharpness of the specular highlights create by this PhongMaterial.\n *\n * Larger values produce smaller, sharper highlights. A value of 0.0 gives very large highlights that are almost never\n * desirable. Try values close to 10 for a larger, fuzzier highlight and values of 100 or more for a small, sharp\n * highlight.\n *\n * Default value is ```` 80.0````.\n *\n * @type {Number}\n */\n set shininess(value) {\n this._state.shininess = value !== undefined ? value : 80;\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial shininess.\n *\n * This is a factor in range [0..128] that determines the size and sharpness of the specular highlights create by this PhongMaterial.\n *\n * Larger values produce smaller, sharper highlights. A value of 0.0 gives very large highlights that are almost never\n * desirable. Try values close to 10 for a larger, fuzzier highlight and values of 100 or more for a small, sharp\n * highlight.\n *\n * Default value is ```` 80.0````.\n *\n * @type {Number}\n */\n get shininess() {\n return this._state.shininess;\n }\n\n /**\n * Sets the PhongMaterial's line width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set lineWidth(value) {\n this._state.lineWidth = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial's line width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n get lineWidth() {\n return this._state.lineWidth;\n }\n\n /**\n * Sets the PhongMaterial's point size.\n *\n * Default value is 1.0.\n *\n * @type {Number}\n */\n set pointSize(value) {\n this._state.pointSize = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial's point size.\n *\n * Default value is 1.0.\n *\n * @type {Number}\n */\n get pointSize() {\n return this._state.pointSize;\n }\n\n /**\n * Sets how much {@link ReflectionMap} is reflected by this PhongMaterial.\n *\n * This is a scalar in range ````[0-1]````. Default value is ````1.0````.\n *\n * The surface will be non-reflective when this is ````0````, and completely mirror-like when it is ````1.0````.\n *\n * Multiplies by {@link PhongMaterial#reflectivityMap}.\n *\n * @type {Number}\n */\n set reflectivity(value) {\n this._state.reflectivity = value !== undefined ? value : 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets how much {@link ReflectionMap} is reflected by this PhongMaterial.\n *\n * This is a scalar in range ````[0-1]````. Default value is ````1.0````.\n *\n * The surface will be non-reflective when this is ````0````, and completely mirror-like when it is ````1.0````.\n *\n * Multiplies by {@link PhongMaterial#reflectivityMap}.\n *\n * @type {Number}\n */\n get reflectivity() {\n return this._state.reflectivity;\n }\n\n /**\n * Gets the PhongMaterials's normal map {@link Texture}.\n *\n * @type {Texture}\n */\n get normalMap() {\n return this._normalMap;\n }\n\n /**\n * Gets the PhongMaterials's ambient {@link Texture}.\n *\n * Multiplies by {@link PhongMaterial#ambient}.\n *\n * @type {Texture}\n */\n get ambientMap() {\n return this._ambientMap;\n }\n\n /**\n * Gets the PhongMaterials's diffuse {@link Texture}.\n *\n * Multiplies by {@link PhongMaterial#diffuse}.\n *\n * @type {Texture}\n */\n get diffuseMap() {\n return this._diffuseMap;\n }\n\n /**\n * Gets the PhongMaterials's specular {@link Texture}.\n *\n * Multiplies by {@link PhongMaterial#specular}.\n *\n * @type {Texture}\n */\n get specularMap() {\n return this._specularMap;\n }\n\n /**\n * Gets the PhongMaterials's emissive {@link Texture}.\n *\n * Multiplies by {@link PhongMaterial#emissive}.\n *\n * @type {Texture}\n */\n get emissiveMap() {\n return this._emissiveMap;\n }\n\n /**\n * Gets the PhongMaterials's alpha {@link Texture}.\n *\n * Multiplies by {@link PhongMaterial#alpha}.\n *\n * @type {Texture}\n */\n get alphaMap() {\n return this._alphaMap;\n }\n\n /**\n * Gets the PhongMaterials's reflectivity {@link Texture}.\n *\n * Multiplies by {@link PhongMaterial#reflectivity}.\n *\n * @type {Texture}\n */\n get reflectivityMap() {\n return this._reflectivityMap;\n }\n\n /**\n * Gets the PhongMaterials's ambient occlusion {@link Texture}.\n *\n * @type {Texture}\n */\n get occlusionMap() {\n return this._occlusionMap;\n }\n\n /**\n * Gets the PhongMaterials's diffuse {@link Fresnel}.\n *\n * Applies to {@link PhongMaterial#diffuse}.\n *\n * @type {Fresnel}\n */\n get diffuseFresnel() {\n return this._diffuseFresnel;\n }\n\n /**\n * Gets the PhongMaterials's specular {@link Fresnel}.\n *\n * Applies to {@link PhongMaterial#specular}.\n *\n * @type {Fresnel}\n */\n get specularFresnel() {\n return this._specularFresnel;\n }\n\n /**\n * Gets the PhongMaterials's emissive {@link Fresnel}.\n *\n * Applies to {@link PhongMaterial#emissive}.\n *\n * @type {Fresnel}\n */\n get emissiveFresnel() {\n return this._emissiveFresnel;\n }\n\n /**\n * Gets the PhongMaterials's alpha {@link Fresnel}.\n *\n * Applies to {@link PhongMaterial#alpha}.\n *\n * @type {Fresnel}\n */\n get alphaFresnel() {\n return this._alphaFresnel;\n }\n\n /**\n * Gets the PhongMaterials's reflectivity {@link Fresnel}.\n *\n * Applies to {@link PhongMaterial#reflectivity}.\n *\n * @type {Fresnel}\n */\n get reflectivityFresnel() {\n return this._reflectivityFresnel;\n }\n\n /**\n * Sets the PhongMaterial's alpha rendering mode.\n *\n * This governs how alpha is treated. Alpha is the combined result of {@link PhongMaterial#alpha} and {@link PhongMaterial#alphaMap}.\n *\n * Supported values are:\n *\n * * \"opaque\" - The alpha value is ignored and the rendered output is fully opaque (default).\n * * \"mask\" - The rendered output is either fully opaque or fully transparent depending on the alpha value and the specified alpha cutoff value.\n * * \"blend\" - The alpha value is used to composite the source and destination areas. The rendered output is combined with the background using the normal painting operation (i.e. the Porter and Duff over operator).\n *\n *@type {String}\n */\n set alphaMode(alphaMode) {\n alphaMode = alphaMode || \"opaque\";\n let value = alphaModes$1[alphaMode];\n if (value === undefined) {\n this.error(\"Unsupported value for 'alphaMode': \" + alphaMode + \" - defaulting to 'opaque'\");\n value = \"opaque\";\n }\n if (this._state.alphaMode === value) {\n return;\n }\n this._state.alphaMode = value;\n this.glRedraw();\n }\n\n /**\n * Gets the PhongMaterial's alpha rendering mode.\n *\n *@type {String}\n */\n get alphaMode() {\n return alphaModeNames$1[this._state.alphaMode];\n }\n\n /**\n * Sets the PhongMaterial's alpha cutoff value.\n *\n * This specifies the cutoff threshold when {@link PhongMaterial#alphaMode} equals \"mask\". If the alpha is greater than or equal to this value then it is rendered as fully\n * opaque, otherwise, it is rendered as fully transparent. A value greater than 1.0 will render the entire material as fully transparent. This value is ignored for other modes.\n *\n * Alpha is the combined result of {@link PhongMaterial#alpha} and {@link PhongMaterial#alphaMap}.\n *\n * Default value is ````0.5````.\n *\n * @type {Number}\n */\n set alphaCutoff(alphaCutoff) {\n if (alphaCutoff === null || alphaCutoff === undefined) {\n alphaCutoff = 0.5;\n }\n if (this._state.alphaCutoff === alphaCutoff) {\n return;\n }\n this._state.alphaCutoff = alphaCutoff;\n }\n\n /**\n * Gets the PhongMaterial's alpha cutoff value.\n *\n * @type {Number}\n */\n get alphaCutoff() {\n return this._state.alphaCutoff;\n }\n\n /**\n * Sets whether backfaces are visible on attached {@link Mesh}es.\n *\n * The backfaces will belong to {@link Geometry} compoents that are also attached to the {@link Mesh}es.\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n set backfaces(value) {\n value = !!value;\n if (this._state.backfaces === value) {\n return;\n }\n this._state.backfaces = value;\n this.glRedraw();\n }\n\n /**\n * Gets whether backfaces are visible on attached {@link Mesh}es.\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n get backfaces() {\n return this._state.backfaces;\n }\n\n /**\n * Sets the winding direction of geometry front faces.\n *\n * Default is ````\"ccw\"````.\n * @type {String}\n */\n set frontface(value) {\n value = value !== \"cw\";\n if (this._state.frontface === value) {\n return;\n }\n this._state.frontface = value;\n this.glRedraw();\n }\n\n /**\n * Gets the winding direction of front faces on attached {@link Mesh}es.\n *\n * Default is ````\"ccw\"````.\n * @type {String}\n */\n get frontface() {\n return this._state.frontface ? \"ccw\" : \"cw\";\n }\n\n /**\n * Destroys this PhongMaterial.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\nconst PRESETS$3 = {\n \"default\": {\n fill: true,\n fillColor: [0.4, 0.4, 0.4],\n fillAlpha: 0.2,\n edges: true,\n edgeColor: [0.2, 0.2, 0.2],\n edgeAlpha: 0.5,\n edgeWidth: 1\n },\n \"defaultWhiteBG\": {\n fill: true,\n fillColor: [1, 1, 1],\n fillAlpha: 0.6,\n edgeColor: [0.2, 0.2, 0.2],\n edgeAlpha: 1.0,\n edgeWidth: 1\n },\n \"defaultLightBG\": {\n fill: true,\n fillColor: [0.4, 0.4, 0.4],\n fillAlpha: 0.2,\n edges: true,\n edgeColor: [0.2, 0.2, 0.2],\n edgeAlpha: 0.5,\n edgeWidth: 1\n },\n \"defaultDarkBG\": {\n fill: true,\n fillColor: [0.4, 0.4, 0.4],\n fillAlpha: 0.2,\n edges: true,\n edgeColor: [0.5, 0.5, 0.5],\n edgeAlpha: 0.5,\n edgeWidth: 1\n },\n \"phosphorous\": {\n fill: true,\n fillColor: [0.0, 0.0, 0.0],\n fillAlpha: 0.4,\n edges: true,\n edgeColor: [0.9, 0.9, 0.9],\n edgeAlpha: 0.5,\n edgeWidth: 2\n },\n \"sunset\": {\n fill: true,\n fillColor: [0.9, 0.9, 0.6],\n fillAlpha: 0.2,\n edges: true,\n edgeColor: [0.9, 0.9, 0.9],\n edgeAlpha: 0.5,\n edgeWidth: 1\n },\n \"vectorscope\": {\n fill: true,\n fillColor: [0.0, 0.0, 0.0],\n fillAlpha: 0.7,\n edges: true,\n edgeColor: [0.2, 1.0, 0.2],\n edgeAlpha: 1,\n edgeWidth: 2\n },\n \"battlezone\": {\n fill: true,\n fillColor: [0.0, 0.0, 0.0],\n fillAlpha: 1.0,\n edges: true,\n edgeColor: [0.2, 1.0, 0.2],\n edgeAlpha: 1,\n edgeWidth: 3\n },\n \"sepia\": {\n fill: true,\n fillColor: [0.970588207244873, 0.7965892553329468, 0.6660899519920349],\n fillAlpha: 0.4,\n edges: true,\n edgeColor: [0.529411792755127, 0.4577854573726654, 0.4100345969200134],\n edgeAlpha: 1.0,\n edgeWidth: 1\n },\n \"yellowHighlight\": {\n fill: true,\n fillColor: [1.0, 1.0, 0.0],\n fillAlpha: 0.5,\n edges: true,\n edgeColor: [1.0, 1.0, 1.0],\n edgeAlpha: 1.0,\n edgeWidth: 1\n },\n \"greenSelected\": {\n fill: true,\n fillColor: [0.0, 1.0, 0.0],\n fillAlpha: 0.5,\n edges: true,\n edgeColor: [1.0, 1.0, 1.0],\n edgeAlpha: 1.0,\n edgeWidth: 1\n },\n \"gamegrid\": {\n fill: true,\n fillColor: [0.2, 0.2, 0.7],\n fillAlpha: 0.9,\n edges: true,\n edgeColor: [0.4, 0.4, 1.6],\n edgeAlpha: 0.8,\n edgeWidth: 3\n }\n};\n\n/**\n * Configures the appearance of {@link Entity}s when they are xrayed, highlighted or selected.\n *\n * * XRay an {@link Entity} by setting {@link Entity#xrayed} ````true````.\n * * Highlight an {@link Entity} by setting {@link Entity#highlighted} ````true````.\n * * Select an {@link Entity} by setting {@link Entity#selected} ````true````.\n * * When {@link Entity}s are within the subtree of a root {@link Entity}, then setting {@link Entity#xrayed}, {@link Entity#highlighted} or {@link Entity#selected}\n * on the root will collectively set those properties on all sub-{@link Entity}s.\n * * EmphasisMaterial provides several presets. Select a preset by setting {@link EmphasisMaterial#preset} to the ID of a preset in {@link EmphasisMaterial#presets}.\n * * By default, a {@link Mesh} uses the default EmphasisMaterials in {@link Scene#xrayMaterial}, {@link Scene#highlightMaterial} and {@link Scene#selectedMaterial}\n * but you can assign each {@link Mesh#xrayMaterial}, {@link Mesh#highlightMaterial} or {@link Mesh#selectedMaterial} to a custom EmphasisMaterial, if required.\n *\n * ## Usage\n *\n * In the example below, we'll create a {@link Mesh} with its own XRayMaterial and set {@link Mesh#xrayed} ````true```` to xray it.\n *\n * Recall that {@link Mesh} is a concrete subtype of the abstract {@link Entity} base class.\n *\n * ````javascript\n * new Mesh(viewer.scene, {\n * geometry: new BoxGeometry(viewer.scene, {\n * edgeThreshold: 1\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.2, 0.2, 1.0]\n * }),\n * xrayMaterial: new EmphasisMaterial(viewer.scene, {\n * fill: true,\n * fillColor: [0, 0, 0],\n * fillAlpha: 0.7,\n * edges: true,\n * edgeColor: [0.2, 1.0, 0.2],\n * edgeAlpha: 1.0,\n * edgeWidth: 2\n * }),\n * xrayed: true\n * });\n * ````\n *\n * Note the ````edgeThreshold```` configuration for the {@link ReadableGeometry} on our {@link Mesh}. EmphasisMaterial configures\n * a wireframe representation of the {@link ReadableGeometry} for the selected emphasis mode, which will have inner edges (those edges between\n * adjacent co-planar triangles) removed for visual clarity. The ````edgeThreshold```` indicates that, for\n * this particular {@link ReadableGeometry}, an inner edge is one where the angle between the surface normals of adjacent triangles\n * is not greater than ````5```` degrees. That's set to ````2```` by default, but we can override it to tweak the effect\n * as needed for particular Geometries.\n *\n * Here's the example again, this time implicitly defaulting to the {@link Scene#edgeMaterial}. We'll also modify that EdgeMaterial\n * to customize the effect.\n *\n * ````javascript\n * new Mesh({\n * geometry: new TeapotGeometry(viewer.scene, {\n * edgeThreshold: 5\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.2, 0.2, 1.0]\n * }),\n * xrayed: true\n * });\n *\n * var xrayMaterial = viewer.scene.xrayMaterial;\n *\n * xrayMaterial.fillColor = [0.2, 1.0, 0.2];\n * xrayMaterial.fillAlpha = 1.0;\n * ````\n *\n * ## Presets\n *\n * Let's switch the {@link Scene#xrayMaterial} to one of the presets in {@link EmphasisMaterial#presets}:\n *\n * ````javascript\n * viewer.xrayMaterial.preset = EmphasisMaterial.presets[\"sepia\"];\n * ````\n *\n * We can also create an EmphasisMaterial from a preset, while overriding properties of the preset as required:\n *\n * ````javascript\n * var myEmphasisMaterial = new EMphasisMaterial(viewer.scene, {\n * preset: \"sepia\",\n * fillColor = [1.0, 0.5, 0.5]\n * });\n * ````\n */\nclass EmphasisMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"EmphasisMaterial\";\n }\n\n /**\n * Gets available EmphasisMaterial presets.\n *\n * @type {Object}\n */\n get presets() {\n return PRESETS$3;\n };\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] The EmphasisMaterial configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Boolean} [cfg.fill=true] Indicates if xray surfaces are filled with color.\n * @param {Number[]} [cfg.fillColor=[0.4,0.4,0.4]] EmphasisMaterial fill color.\n * @param {Number} [cfg.fillAlpha=0.2] Transparency of filled xray faces. A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n * @param {Boolean} [cfg.edges=true] Indicates if xray edges are visible.\n * @param {Number[]} [cfg.edgeColor=[0.2,0.2,0.2]] RGB color of xray edges.\n * @param {Number} [cfg.edgeAlpha=0.5] Transparency of xray edges. A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n * @param {Number} [cfg.edgeWidth=1] Width of xray edges, in pixels.\n * @param {String} [cfg.preset] Selects a preset EmphasisMaterial configuration - see {@link EmphasisMaterial#presets}.\n * @param {Boolean} [cfg.backfaces=false] Whether to render geometry backfaces when emphasising.\n * @param {Boolean} [cfg.glowThrough=true] Whether to make the emphasized object appear to float on top of other objects, as if it were \"glowing through\" them.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"EmphasisMaterial\",\n fill: null,\n fillColor: null,\n fillAlpha: null,\n edges: null,\n edgeColor: null,\n edgeAlpha: null,\n edgeWidth: null,\n backfaces: true,\n glowThrough: true\n });\n\n this._preset = \"default\";\n\n if (cfg.preset) { // Apply preset then override with configs where provided\n this.preset = cfg.preset;\n if (cfg.fill !== undefined) {\n this.fill = cfg.fill;\n }\n if (cfg.fillColor) {\n this.fillColor = cfg.fillColor;\n }\n if (cfg.fillAlpha !== undefined) {\n this.fillAlpha = cfg.fillAlpha;\n }\n if (cfg.edges !== undefined) {\n this.edges = cfg.edges;\n }\n if (cfg.edgeColor) {\n this.edgeColor = cfg.edgeColor;\n }\n if (cfg.edgeAlpha !== undefined) {\n this.edgeAlpha = cfg.edgeAlpha;\n }\n if (cfg.edgeWidth !== undefined) {\n this.edgeWidth = cfg.edgeWidth;\n }\n if (cfg.backfaces !== undefined) {\n this.backfaces = cfg.backfaces;\n }\n if (cfg.glowThrough !== undefined) {\n this.glowThrough = cfg.glowThrough;\n }\n } else {\n this.fill = cfg.fill;\n this.fillColor = cfg.fillColor;\n this.fillAlpha = cfg.fillAlpha;\n this.edges = cfg.edges;\n this.edgeColor = cfg.edgeColor;\n this.edgeAlpha = cfg.edgeAlpha;\n this.edgeWidth = cfg.edgeWidth;\n this.backfaces = cfg.backfaces;\n this.glowThrough = cfg.glowThrough;\n }\n }\n\n /**\n * Sets if surfaces are filled with color.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set fill(value) {\n value = value !== false;\n if (this._state.fill === value) {\n return;\n }\n this._state.fill = value;\n this.glRedraw();\n }\n\n /**\n * Gets if surfaces are filled with color.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get fill() {\n return this._state.fill;\n }\n\n /**\n * Sets the RGB color of filled faces.\n *\n * Default is ````[0.4, 0.4, 0.4]````.\n *\n * @type {Number[]}\n */\n set fillColor(value) {\n let fillColor = this._state.fillColor;\n if (!fillColor) {\n fillColor = this._state.fillColor = new Float32Array(3);\n } else if (value && fillColor[0] === value[0] && fillColor[1] === value[1] && fillColor[2] === value[2]) {\n return;\n }\n if (value) {\n fillColor[0] = value[0];\n fillColor[1] = value[1];\n fillColor[2] = value[2];\n } else {\n fillColor[0] = 0.4;\n fillColor[1] = 0.4;\n fillColor[2] = 0.4;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the RGB color of filled faces.\n *\n * Default is ````[0.4, 0.4, 0.4]````.\n *\n * @type {Number[]}\n */\n get fillColor() {\n return this._state.fillColor;\n }\n\n /**\n * Sets the transparency of filled faces.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default is ````0.2````.\n *\n * @type {Number}\n */\n set fillAlpha(value) {\n value = (value !== undefined && value !== null) ? value : 0.2;\n if (this._state.fillAlpha === value) {\n return;\n }\n this._state.fillAlpha = value;\n this.glRedraw();\n }\n\n /**\n * Gets the transparency of filled faces.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default is ````0.2````.\n *\n * @type {Number}\n */\n get fillAlpha() {\n return this._state.fillAlpha;\n }\n\n /**\n * Sets if edges are visible.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set edges(value) {\n value = value !== false;\n if (this._state.edges === value) {\n return;\n }\n this._state.edges = value;\n this.glRedraw();\n }\n\n /**\n * Gets if edges are visible.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get edges() {\n return this._state.edges;\n }\n\n /**\n * Sets the RGB color of edges.\n *\n * Default is ```` [0.2, 0.2, 0.2]````.\n *\n * @type {Number[]}\n */\n set edgeColor(value) {\n let edgeColor = this._state.edgeColor;\n if (!edgeColor) {\n edgeColor = this._state.edgeColor = new Float32Array(3);\n } else if (value && edgeColor[0] === value[0] && edgeColor[1] === value[1] && edgeColor[2] === value[2]) {\n return;\n }\n if (value) {\n edgeColor[0] = value[0];\n edgeColor[1] = value[1];\n edgeColor[2] = value[2];\n } else {\n edgeColor[0] = 0.2;\n edgeColor[1] = 0.2;\n edgeColor[2] = 0.2;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the RGB color of edges.\n *\n * Default is ```` [0.2, 0.2, 0.2]````.\n *\n * @type {Number[]}\n */\n get edgeColor() {\n return this._state.edgeColor;\n }\n\n /**\n * Sets the transparency of edges.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default is ````0.2````.\n *\n * @type {Number}\n */\n set edgeAlpha(value) {\n value = (value !== undefined && value !== null) ? value : 0.5;\n if (this._state.edgeAlpha === value) {\n return;\n }\n this._state.edgeAlpha = value;\n this.glRedraw();\n }\n\n /**\n * Gets the transparency of edges.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default is ````0.2````.\n *\n * @type {Number}\n */\n get edgeAlpha() {\n return this._state.edgeAlpha;\n }\n\n /**\n * Sets edge width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0```` pixels.\n *\n * @type {Number}\n */\n set edgeWidth(value) {\n this._state.edgeWidth = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets edge width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0```` pixels.\n *\n * @type {Number}\n */\n get edgeWidth() {\n return this._state.edgeWidth;\n }\n\n /**\n * Sets whether to render backfaces when {@link EmphasisMaterial#fill} is ````true````.\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n set backfaces(value) {\n value = !!value;\n if (this._state.backfaces === value) {\n return;\n }\n this._state.backfaces = value;\n this.glRedraw();\n }\n\n /**\n * Gets whether to render backfaces when {@link EmphasisMaterial#fill} is ````true````.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get backfaces() {\n return this._state.backfaces;\n }\n\n /**\n * Sets whether to render emphasized objects over the top of other objects, as if they were \"glowing through\".\n *\n * Default is ````true````.\n *\n * Note: updating this property will not affect the appearance of objects that are already emphasized.\n *\n * @type {Boolean}\n */\n set glowThrough(value) {\n value = (value !== false);\n if (this._state.glowThrough === value) {\n return;\n }\n this._state.glowThrough = value;\n this.glRedraw();\n }\n\n /**\n * Sets whether to render emphasized objects over the top of other objects, as if they were \"glowing through\".\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get glowThrough() {\n return this._state.glowThrough;\n }\n\n /**\n * Selects a preset EmphasisMaterial configuration.\n *\n * Default value is \"default\".\n *\n * @type {String}\n */\n set preset(value) {\n value = value || \"default\";\n if (this._preset === value) {\n return;\n }\n const preset = PRESETS$3[value];\n if (!preset) {\n this.error(\"unsupported preset: '\" + value + \"' - supported values are \" + Object.keys(PRESETS$3).join(\", \"));\n return;\n }\n this.fill = preset.fill;\n this.fillColor = preset.fillColor;\n this.fillAlpha = preset.fillAlpha;\n this.edges = preset.edges;\n this.edgeColor = preset.edgeColor;\n this.edgeAlpha = preset.edgeAlpha;\n this.edgeWidth = preset.edgeWidth;\n this.glowThrough = preset.glowThrough;\n this._preset = value;\n }\n\n /**\n * Gets the current preset EmphasisMaterial configuration.\n *\n * Default value is \"default\".\n *\n * @type {String}\n */\n get preset() {\n return this._preset;\n }\n\n /**\n * Destroys this EmphasisMaterial.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\nconst PRESETS$2 = {\n \"default\": {\n edgeColor: [0.0, 0.0, 0.0],\n edgeAlpha: 1.0,\n edgeWidth: 1\n },\n \"defaultWhiteBG\": {\n edgeColor: [0.2, 0.2, 0.2],\n edgeAlpha: 1.0,\n edgeWidth: 1\n },\n \"defaultLightBG\": {\n edgeColor: [0.2, 0.2, 0.2],\n edgeAlpha: 1.0,\n edgeWidth: 1\n },\n \"defaultDarkBG\": {\n edgeColor: [0.5, 0.5, 0.5],\n edgeAlpha: 1.0,\n edgeWidth: 1\n }\n};\n\n/**\n * @desc Configures the appearance of {@link Entity}s when their edges are emphasised.\n *\n * * Emphasise edges of an {@link Entity} by setting {@link Entity#edges} ````true````.\n * * When {@link Entity}s are within the subtree of a root {@link Entity}, then setting {@link Entity#edges} on the root\n * will collectively set that property on all sub-{@link Entity}s.\n * * EdgeMaterial provides several presets. Select a preset by setting {@link EdgeMaterial#preset} to the ID of a preset in {@link EdgeMaterial#presets}.\n * * By default, a {@link Mesh} uses the default EdgeMaterial in {@link Scene#edgeMaterial}, but you can assign each {@link Mesh#edgeMaterial} to a custom EdgeMaterial if required.\n *\n * ## Usage\n *\n * In the example below, we'll create a {@link Mesh} with its own EdgeMaterial and set {@link Mesh#edges} ````true```` to emphasise its edges.\n *\n * Recall that {@link Mesh} is a concrete subtype of the abstract {@link Entity} base class.\n *\n * [[Run this example](/examples/index.html#materials_EdgeMaterial)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildSphereGeometry,\n * ReadableGeometry, PhongMaterial, EdgeMaterial} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n *\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * radius: 1.5,\n * heightSegments: 24,\n * widthSegments: 16,\n * edgeThreshold: 2 // Default is 10\n * })),\n *\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.4, 0.4, 1.0],\n * ambient: [0.9, 0.3, 0.9],\n * shininess: 30,\n * alpha: 0.5,\n * alphaMode: \"blend\"\n * }),\n *\n * edgeMaterial: new EdgeMaterial(viewer.scene, {\n * edgeColor: [0.0, 0.0, 1.0]\n * edgeAlpha: 1.0,\n * edgeWidth: 2\n * }),\n *\n * edges: true\n * });\n * ````\n *\n * Note the ````edgeThreshold```` configuration for the {@link ReadableGeometry} on our {@link Mesh}. EdgeMaterial configures\n * a wireframe representation of the {@link ReadableGeometry}, which will have inner edges (those edges between\n * adjacent co-planar triangles) removed for visual clarity. The ````edgeThreshold```` indicates that, for\n * this particular {@link ReadableGeometry}, an inner edge is one where the angle between the surface normals of adjacent triangles\n * is not greater than ````5```` degrees. That's set to ````2```` by default, but we can override it to tweak the effect\n * as needed for particular Geometries.\n *\n * Here's the example again, this time implicitly defaulting to the {@link Scene#edgeMaterial}. We'll also modify that EdgeMaterial\n * to customize the effect.\n *\n * ````javascript\n * new Mesh({\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * radius: 1.5,\n * heightSegments: 24,\n * widthSegments: 16,\n * edgeThreshold: 2 // Default is 10\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.2, 0.2, 1.0]\n * }),\n * edges: true\n * });\n *\n * var edgeMaterial = viewer.scene.edgeMaterial;\n *\n * edgeMaterial.edgeColor = [0.2, 1.0, 0.2];\n * edgeMaterial.edgeAlpha = 1.0;\n * edgeMaterial.edgeWidth = 2;\n * ````\n *\n * ## Presets\n *\n * Let's switch the {@link Scene#edgeMaterial} to one of the presets in {@link EdgeMaterial#presets}:\n *\n * ````javascript\n * viewer.edgeMaterial.preset = EdgeMaterial.presets[\"sepia\"];\n * ````\n *\n * We can also create an EdgeMaterial from a preset, while overriding properties of the preset as required:\n *\n * ````javascript\n * var myEdgeMaterial = new EdgeMaterial(viewer.scene, {\n * preset: \"sepia\",\n * edgeColor = [1.0, 0.5, 0.5]\n * });\n * ````\n */\nclass EdgeMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"EdgeMaterial\";\n }\n\n /**\n * Gets available EdgeMaterial presets.\n *\n * @type {Object}\n */\n get presets() {\n return PRESETS$2;\n };\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] The EdgeMaterial configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.edgeColor=[0.2,0.2,0.2]] RGB edge color.\n * @param {Number} [cfg.edgeAlpha=1.0] Edge transparency. A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n * @param {Number} [cfg.edgeWidth=1] Edge width in pixels.\n * @param {String} [cfg.preset] Selects a preset EdgeMaterial configuration - see {@link EdgeMaterial#presets}.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"EdgeMaterial\",\n edges: null,\n edgeColor: null,\n edgeAlpha: null,\n edgeWidth: null\n });\n\n this._preset = \"default\";\n\n if (cfg.preset) { // Apply preset then override with configs where provided\n this.preset = cfg.preset;\n if (cfg.edgeColor) {\n this.edgeColor = cfg.edgeColor;\n }\n if (cfg.edgeAlpha !== undefined) {\n this.edgeAlpha = cfg.edgeAlpha;\n }\n if (cfg.edgeWidth !== undefined) {\n this.edgeWidth = cfg.edgeWidth;\n }\n } else {\n this.edgeColor = cfg.edgeColor;\n this.edgeAlpha = cfg.edgeAlpha;\n this.edgeWidth = cfg.edgeWidth;\n }\n this.edges = (cfg.edges !== false);\n }\n\n\n /**\n * Sets if edges are visible.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set edges(value) {\n value = value !== false;\n if (this._state.edges === value) {\n return;\n }\n this._state.edges = value;\n this.glRedraw();\n }\n\n /**\n * Gets if edges are visible.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get edges() {\n return this._state.edges;\n }\n\n /**\n * Sets RGB edge color.\n *\n * Default value is ````[0.2, 0.2, 0.2]````.\n *\n * @type {Number[]}\n */\n set edgeColor(value) {\n let edgeColor = this._state.edgeColor;\n if (!edgeColor) {\n edgeColor = this._state.edgeColor = new Float32Array(3);\n } else if (value && edgeColor[0] === value[0] && edgeColor[1] === value[1] && edgeColor[2] === value[2]) {\n return;\n }\n if (value) {\n edgeColor[0] = value[0];\n edgeColor[1] = value[1];\n edgeColor[2] = value[2];\n } else {\n edgeColor[0] = 0.2;\n edgeColor[1] = 0.2;\n edgeColor[2] = 0.2;\n }\n this.glRedraw();\n }\n\n /**\n * Gets RGB edge color.\n *\n * Default value is ````[0.2, 0.2, 0.2]````.\n *\n * @type {Number[]}\n */\n get edgeColor() {\n return this._state.edgeColor;\n }\n\n /**\n * Sets edge transparency.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set edgeAlpha(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.edgeAlpha === value) {\n return;\n }\n this._state.edgeAlpha = value;\n this.glRedraw();\n }\n\n /**\n * Gets edge transparency.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n get edgeAlpha() {\n return this._state.edgeAlpha;\n }\n\n /**\n * Sets edge width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0```` pixels.\n *\n * @type {Number}\n */\n set edgeWidth(value) {\n this._state.edgeWidth = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets edge width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0```` pixels.\n *\n * @type {Number}\n */\n get edgeWidth() {\n return this._state.edgeWidth;\n }\n\n /**\n * Selects a preset EdgeMaterial configuration.\n *\n * Default value is ````\"default\"````.\n *\n * @type {String}\n */\n set preset(value) {\n value = value || \"default\";\n if (this._preset === value) {\n return;\n }\n const preset = PRESETS$2[value];\n if (!preset) {\n this.error(\"unsupported preset: '\" + value + \"' - supported values are \" + Object.keys(PRESETS$2).join(\", \"));\n return;\n }\n this.edgeColor = preset.edgeColor;\n this.edgeAlpha = preset.edgeAlpha;\n this.edgeWidth = preset.edgeWidth;\n this._preset = value;\n }\n\n /**\n * The current preset EdgeMaterial configuration.\n *\n * Default value is ````\"default\"````.\n *\n * @type {String}\n */\n get preset() {\n return this._preset;\n }\n\n /**\n * Destroys this EdgeMaterial.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nconst unitsInfo = {\n meters: {\n abbrev: \"m\"\n },\n metres: {\n abbrev: \"m\"\n },\n centimeters: {\n abbrev: \"cm\"\n },\n centimetres: {\n abbrev: \"cm\"\n },\n millimeters: {\n abbrev: \"mm\"\n },\n millimetres: {\n abbrev: \"mm\"\n },\n yards: {\n abbrev: \"yd\"\n },\n feet: {\n abbrev: \"ft\"\n },\n inches: {\n abbrev: \"in\"\n }\n};\n\n/**\n * @desc Configures its {@link Scene}'s measurement unit and mapping between the Real-space and World-space 3D Cartesian coordinate systems.\n *\n *\n * ## Overview\n *\n * * Located at {@link Scene#metrics}.\n * * {@link Metrics#units} configures the Real-space unit type, which is ````\"meters\"```` by default.\n * * {@link Metrics#scale} configures the number of Real-space units represented by each unit within the World-space 3D coordinate system. This is ````1.0```` by default.\n * * {@link Metrics#origin} configures the 3D Real-space origin, in current Real-space units, at which this {@link Scene}'s World-space coordinate origin sits, This is ````[0,0,0]```` by default.\n *\n * ## Usage\n *\n * Let's load a model using an {@link XKTLoaderPlugin}, then configure the Real-space unit type and the coordinate\n * mapping between the Real-space and World-space 3D coordinate systems.\n *\n * ````JavaScript\n * import {Viewer, XKTLoaderPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/duplex.xkt\"\n * });\n *\n * const metrics = viewer.scene.metrics;\n *\n * metrics.units = \"meters\";\n * metrics.scale = 10.0;\n * metrics.origin = [100.0, 0.0, 200.0];\n * ````\n */\nclass Metrics extends Component {\n\n /**\n * @constructor\n * @private\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._units = \"meters\";\n this._scale = 1.0;\n this._origin = math.vec3([0, 0, 0]);\n\n this.units = cfg.units;\n this.scale = cfg.scale;\n this.origin = cfg.origin;\n }\n\n /**\n * Gets info about the supported Real-space unit types.\n *\n * This will be:\n *\n * ````javascript\n * {\n * {\n * meters: {\n * abbrev: \"m\"\n * },\n * metres: {\n * abbrev: \"m\"\n * },\n * centimeters: {\n * abbrev: \"cm\"\n * },\n * centimetres: {\n * abbrev: \"cm\"\n * },\n * millimeters: {\n * abbrev: \"mm\"\n * },\n * millimetres: {\n * abbrev: \"mm\"\n * },\n * yards: {\n * abbrev: \"yd\"\n * },\n * feet: {\n * abbrev: \"ft\"\n * },\n * inches: {\n * abbrev: \"in\"\n * }\n * }\n * }\n * ````\n *\n * @type {*}\n */\n get unitsInfo() {\n return unitsInfo;\n }\n\n /**\n * Sets the {@link Scene}'s Real-space unit type.\n *\n * Accepted values are ````\"meters\"````, ````\"centimeters\"````, ````\"millimeters\"````, ````\"metres\"````, ````\"centimetres\"````, ````\"millimetres\"````, ````\"yards\"````, ````\"feet\"```` and ````\"inches\"````.\n *\n * @emits ````\"units\"```` event on change, with the value of this property.\n * @type {String}\n */\n set units(value) {\n if (!value) {\n value = \"meters\";\n }\n const info = unitsInfo[value];\n if (!info) {\n this.error(\"Unsupported value for 'units': \" + value + \" defaulting to 'meters'\");\n value = \"meters\";\n }\n this._units = value;\n this.fire(\"units\", this._units);\n }\n\n /**\n * Gets the {@link Scene}'s Real-space unit type.\n *\n * @type {String}\n */\n get units() {\n return this._units;\n }\n\n /**\n * Sets the number of Real-space units represented by each unit of the {@link Scene}'s World-space coordinate system.\n *\n * For example, if {@link Metrics#units} is ````\"meters\"````, and there are ten meters per World-space coordinate system unit, then ````scale```` would have a value of ````10.0````.\n *\n * @emits ````\"scale\"```` event on change, with the value of this property.\n * @type {Number}\n */\n set scale(value) {\n value = value || 1;\n if (value <= 0) {\n this.error(\"scale value should be larger than zero\");\n return;\n }\n this._scale = value;\n this.fire(\"scale\", this._scale);\n }\n\n /**\n * Gets the number of Real-space units represented by each unit of the {@link Scene}'s World-space coordinate system.\n *\n * @type {Number}\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the Real-space 3D origin, in Real-space units, at which this {@link Scene}'s World-space coordinate origin ````[0,0,0]```` sits.\n *\n * @emits \"origin\" event on change, with the value of this property.\n * @type {Number[]}\n */\n set origin(value) {\n if (!value) {\n this._origin[0] = 0;\n this._origin[1] = 0;\n this._origin[2] = 0;\n return;\n }\n this._origin[0] = value[0];\n this._origin[1] = value[1];\n this._origin[2] = value[2];\n this.fire(\"origin\", this._origin);\n }\n\n /**\n * Gets the 3D Real-space origin, in Real-space units, at which this {@link Scene}'s World-space coordinate origin ````[0,0,0]```` sits.\n *\n * @type {Number[]}\n */\n get origin() {\n return this._origin;\n }\n\n /**\n * Converts a 3D position from World-space to Real-space.\n *\n * This is equivalent to ````realPos = #origin + (worldPos * #scale)````.\n *\n * @param {Number[]} worldPos World-space 3D position, in World coordinate system units.\n * @param {Number[]} [realPos] Destination for Real-space 3D position.\n * @returns {Number[]} Real-space 3D position, in units indicated by {@link Metrics#units}.\n */\n worldToRealPos(worldPos, realPos = math.vec3(3)) {\n realPos[0] = this._origin[0] + (this._scale * worldPos[0]);\n realPos[1] = this._origin[1] + (this._scale * worldPos[1]);\n realPos[2] = this._origin[2] + (this._scale * worldPos[2]);\n }\n\n /**\n * Converts a 3D position from Real-space to World-space.\n *\n * This is equivalent to ````worldPos = (worldPos - #origin) / #scale````.\n *\n * @param {Number[]} realPos Real-space 3D position.\n * @param {Number[]} [worldPos] Destination for World-space 3D position.\n * @returns {Number[]} World-space 3D position.\n */\n realToWorldPos(realPos, worldPos = math.vec3(3)) {\n worldPos[0] = (realPos[0] - this._origin[0]) / this._scale;\n worldPos[1] = (realPos[1] - this._origin[1]) / this._scale;\n worldPos[2] = (realPos[2] - this._origin[2]) / this._scale;\n return worldPos;\n }\n}\n\n/**\n * @desc Configures Scalable Ambient Obscurance (SAO) for a {@link Scene}.\n *\n * \n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/viewer/#sao_ConferenceCenter)]\n *\n * ## Overview\n *\n * SAO approximates [Ambient Occlusion](https://en.wikipedia.org/wiki/Ambient_occlusion) in realtime. It darkens creases, cavities and surfaces\n * that are close to each other, which tend to be occluded from ambient light and appear darker.\n *\n * The animated GIF above shows the effect as we repeatedly enable and disable SAO. When SAO is enabled, we can see darkening\n * in regions such as the corners, and the crevices between stairs. This increases the amount of detail we can see when ambient\n * light is high, or when objects have uniform colors across their surfaces. Run the example to experiment with the various\n * SAO configurations.\n *\n * xeokit's implementation of SAO is based on the paper [Scalable Ambient Obscurance](https://research.nvidia.com/sites/default/files/pubs/2012-06_Scalable-Ambient-Obscurance/McGuire12SAO.pdf).\n *\n * ## Caveats\n *\n * Currently, SAO only works with perspective and orthographic projections. Therefore, to use SAO, make sure {@link Camera#projection} is\n * either \"perspective\" or \"ortho\".\n *\n * {@link SAO#scale} and {@link SAO#intensity} must be tuned to the distance\n * between {@link Perspective#near} and {@link Perspective#far}, or the distance\n * between {@link Ortho#near} and {@link Ortho#far}, depending on which of those two projections the {@link Camera} is currently\n * using. Use the [live example](https://xeokit.github.io/xeokit-sdk/examples/viewer/#sao_ConferenceCenter) to get a\n * feel for that.\n *\n * ## Usage\n *\n * In the example below, we'll start by logging a warning message to the console if SAO is not supported by the\n * system.\n *\n *Then we'll enable and configure SAO, position the camera, and configure the near and far perspective and orthographic\n * clipping planes. Finally, we'll use {@link XKTLoaderPlugin} to load the OTC Conference Center model.\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * const sao = viewer.scene.sao;\n *\n * if (!sao.supported) {\n * sao.warn(\"SAO is not supported on this system - ignoring SAO configs\")\n * }\n *\n * sao.enabled = true; // Enable SAO - only works if supported (see above)\n * sao.intensity = 0.15;\n * sao.bias = 0.5;\n * sao.scale = 1.0;\n * sao.minResolution = 0.0;\n * sao.numSamples = 10;\n * sao.kernelRadius = 100;\n * sao.blendCutoff = 0.1;\n *\n * const camera = viewer.scene.camera;\n *\n * camera.eye = [3.69, 5.83, -23.98];\n * camera.look = [84.31, -29.88, -116.21];\n * camera.up = [0.18, 0.96, -0.21];\n *\n * camera.perspective.near = 0.1;\n * camera.perspective.far = 2000.0;\n *\n * camera.ortho.near = 0.1;\n * camera.ortho.far = 2000.0;\n * camera.projection = \"perspective\";\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/OTCConferenceCenter.xkt\"\n * edges: true\n * });\n * ````\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/viewer/#sao_ConferenceCenter)]\n *\n * ## Efficiency\n *\n * SAO can incur some rendering overhead, especially on objects that are viewed close to the camera. For this reason,\n * it's recommended to use a low value for {@link SAO#kernelRadius}. A low radius will sample pixels that are close\n * to the source pixel, which will allow the GPU to efficiently cache those pixels. When {@link Camera#projection} is \"perspective\",\n * objects near to the viewpoint will use larger radii than farther pixels. Therefore, computing SAO for close objects\n * is more expensive than for objects far away, that occupy fewer pixels on the canvas.\n *\n * ## Selectively enabling SAO for models\n *\n * When loading multiple models into a Scene, we sometimes only want SAO on the models that are actually going to\n * show it, such as the architecture or structure, and not show SAO on models that won't show it well, such as the\n * electrical wiring, or plumbing.\n *\n * To illustrate, lets load some of the models for the West Riverside Hospital. We'll enable SAO on the structure model,\n * but disable it on the electrical and plumbing.\n *\n * This will only apply SAO to those models if {@link SAO#supported} and {@link SAO#enabled} are both true.\n *\n * Note, by the way, how we load the models in sequence. Since XKTLoaderPlugin uses scratch memory as part of its loading\n * process, this allows the plugin to reuse that same memory across multiple loads, instead of having to create multiple\n * pools of scratch memory.\n *\n * ````javascript\n * const structure = xktLoader.load({\n * id: \"structure\",\n * src: \"./models/xkt/WestRiverSideHospital/structure.xkt\"\n * edges: true,\n * saoEnabled: true\n * });\n *\n * structure.on(\"loaded\", () => {\n *\n * const electrical = xktLoader.load({\n * id: \"electrical\",\n * src: \"./models/xkt/WestRiverSideHospital/electrical.xkt\",\n * edges: true\n * });\n *\n * electrical.on(\"loaded\", () => {\n *\n * const plumbing = xktLoader.load({\n * id: \"plumbing\",\n * src: \"./models/xkt/WestRiverSideHospital/plumbing.xkt\",\n * edges: true\n * });\n * });\n * });\n * ````\n *\n * ## Disabling SAO while camera is moving\n *\n * For smoother interaction with large models on low-power hardware, we can disable SAO while the {@link Camera} is moving:\n *\n * ````javascript\n * const timeoutDuration = 150; // Milliseconds\n * var timer = timeoutDuration;\n * var saoDisabled = false;\n *\n * const onCameraMatrix = scene.camera.on(\"matrix\", () => {\n * timer = timeoutDuration;\n * if (!saoDisabled) {\n * scene.sao.enabled = false;\n * saoDisabled = true;\n * }\n * });\n *\n * const onSceneTick = scene.on(\"tick\", (tickEvent) => {\n * if (!saoDisabled) {\n * return;\n * }\n * timer -= tickEvent.deltaTime; // Milliseconds\n * if (timer <= 0) {\n * if (saoDisabled) {\n * scene.sao.enabled = true;\n * saoDisabled = false;\n * }\n * }\n * });\n * ````\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#techniques_nonInteractiveQuality)]\n */\nclass SAO extends Component {\n\n /** @private */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._supported = WEBGL_INFO.SUPPORTED_EXTENSIONS[\"OES_standard_derivatives\"]; // For computing normals in SAO fragment shader\n\n this.enabled = cfg.enabled;\n this.kernelRadius = cfg.kernelRadius;\n this.intensity = cfg.intensity;\n this.bias = cfg.bias;\n this.scale = cfg.scale;\n this.minResolution = cfg.minResolution;\n this.numSamples = cfg.numSamples;\n this.blur = cfg.blur;\n this.blendCutoff = cfg.blendCutoff;\n this.blendFactor = cfg.blendFactor;\n }\n\n /**\n * Gets whether or not SAO is supported by this browser and GPU.\n *\n * Even when enabled, SAO will only work if supported.\n *\n * @type {Boolean}\n */\n get supported() {\n return this._supported;\n }\n\n /**\n * Sets whether SAO is enabled for the {@link Scene}.\n *\n * Even when enabled, SAO will only work if supported.\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n set enabled(value) {\n value = !!value;\n if (this._enabled === value) {\n return;\n }\n this._enabled = value;\n this.glRedraw();\n }\n\n /**\n * Gets whether SAO is enabled for the {@link Scene}.\n *\n * Even when enabled, SAO will only apply if supported.\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n get enabled() {\n return this._enabled;\n }\n\n /**\n * Returns true if SAO is currently possible, where it is supported, enabled, and the current scene state is compatible.\n * Called internally by renderer logic.\n * @private\n * @returns {Boolean}\n */\n get possible() {\n if (!this._supported) {\n return false;\n }\n if (!this._enabled) {\n return false;\n }\n const projection = this.scene.camera.projection;\n if (projection === \"customProjection\") {\n return false;\n }\n if (projection === \"frustum\") {\n return false;\n }\n return true;\n }\n\n /**\n * @private\n * @returns {boolean|*}\n */\n get active() {\n return this._active;\n }\n\n /**\n * Sets the maximum area that SAO takes into account when checking for possible occlusion for each fragment.\n *\n * Default value is ````100.0````.\n *\n * @type {Number}\n */\n set kernelRadius(value) {\n if (value === undefined || value === null) {\n value = 100.0;\n }\n if (this._kernelRadius === value) {\n return;\n }\n this._kernelRadius = value;\n this.glRedraw();\n }\n\n /**\n * Gets the maximum area that SAO takes into account when checking for possible occlusion for each fragment.\n *\n * Default value is ````100.0````.\n *\n * @type {Number}\n */\n get kernelRadius() {\n return this._kernelRadius;\n }\n\n /**\n * Sets the degree of darkening (ambient obscurance) produced by the SAO effect.\n *\n * Default value is ````0.15````.\n *\n * @type {Number}\n */\n set intensity(value) {\n if (value === undefined || value === null) {\n value = 0.15;\n }\n if (this._intensity === value) {\n return;\n }\n this._intensity = value;\n this.glRedraw();\n }\n\n /**\n * Gets the degree of darkening (ambient obscurance) produced by the SAO effect.\n *\n * Default value is ````0.15````.\n *\n * @type {Number}\n */\n get intensity() {\n return this._intensity;\n }\n\n /**\n * Sets the SAO bias.\n *\n * Default value is ````0.5````.\n *\n * @type {Number}\n */\n set bias(value) {\n if (value === undefined || value === null) {\n value = 0.5;\n }\n if (this._bias === value) {\n return;\n }\n this._bias = value;\n this.glRedraw();\n }\n\n /**\n * Gets the SAO bias.\n *\n * Default value is ````0.5````.\n *\n * @type {Number}\n */\n get bias() {\n return this._bias;\n }\n\n /**\n * Sets the SAO occlusion scale.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set scale(value) {\n if (value === undefined || value === null) {\n value = 1.0;\n }\n if (this._scale === value) {\n return;\n }\n this._scale = value;\n this.glRedraw();\n }\n\n /**\n * Gets the SAO occlusion scale.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the SAO minimum resolution.\n *\n * Default value is ````0.0````.\n *\n * @type {Number}\n */\n set minResolution(value) {\n if (value === undefined || value === null) {\n value = 0.0;\n }\n if (this._minResolution === value) {\n return;\n }\n this._minResolution = value;\n this.glRedraw();\n }\n\n /**\n * Gets the SAO minimum resolution.\n *\n * Default value is ````0.0````.\n *\n * @type {Number}\n */\n get minResolution() {\n return this._minResolution;\n }\n\n /**\n * Sets the number of SAO samples.\n *\n * Default value is ````10````.\n *\n * Update this sparingly, since it causes a shader recompile.\n *\n * @type {Number}\n */\n set numSamples(value) {\n if (value === undefined || value === null) {\n value = 10;\n }\n if (this._numSamples === value) {\n return;\n }\n this._numSamples = value;\n this.glRedraw();\n }\n\n /**\n * Gets the number of SAO samples.\n *\n * Default value is ````10````.\n *\n * @type {Number}\n */\n get numSamples() {\n return this._numSamples;\n }\n\n /**\n * Sets whether Guassian blur is enabled.\n *\n * Default value is ````true````.\n *\n * @type {Boolean}\n */\n set blur(value) {\n value = (value !== false);\n if (this._blur === value) {\n return;\n }\n this._blur = value;\n this.glRedraw();\n }\n\n /**\n * Gets whether Guassian blur is enabled.\n *\n * Default value is ````true````.\n *\n * @type {Boolean}\n */\n get blur() {\n return this._blur;\n }\n\n /**\n * Sets the SAO blend cutoff.\n *\n * Default value is ````0.3````.\n *\n * Normally you don't need to alter this.\n *\n * @type {Number}\n */\n set blendCutoff(value) {\n if (value === undefined || value === null) {\n value = 0.3;\n }\n if (this._blendCutoff === value) {\n return;\n }\n this._blendCutoff = value;\n this.glRedraw();\n }\n\n /**\n * Gets the SAO blend cutoff.\n *\n * Default value is ````0.3````.\n *\n * Normally you don't need to alter this.\n *\n * @type {Number}\n */\n get blendCutoff() {\n return this._blendCutoff;\n }\n\n /**\n * Sets the SAO blend factor.\n *\n * Default value is ````1.0````.\n *\n * Normally you don't need to alter this.\n *\n * @type {Number}\n */\n set blendFactor(value) {\n if (value === undefined || value === null) {\n value = 1.0;\n }\n if (this._blendFactor === value) {\n return;\n }\n this._blendFactor = value;\n this.glRedraw();\n }\n\n /**\n * Gets the SAO blend scale.\n *\n * Default value is ````1.0````.\n *\n * Normally you don't need to alter this.\n *\n * @type {Number}\n */\n get blendFactor() {\n return this._blendFactor;\n }\n\n /**\n * Destroys this component.\n */\n destroy() {\n super.destroy();\n }\n}\n\n/**\n * @desc Configures cross-section slices for a {@link Scene}.\n *\n * ## Overview\n *\n * Cross-sections allow to create an additional colored slice for used clipping planes. It is only a visual effect\n * calculated by shaders. It makes it easier to see the intersection between the model and the clipping plane this way.\n *\n * ## Usage\n *\n * In the example below, we'll configure CrossSections to manipulate the slice representation.\n *\n * ````javascript\n * //------------------------------------------------------------------------------------------------------------------\n * // Import the modules we need for this example\n * //------------------------------------------------------------------------------------------------------------------\n *\n * import {PhongMaterial, Viewer, math, SectionPlanesPlugin, XKTLoaderPlugin, Mesh, ReadableGeometry, buildPolylineGeometryFromCurve, SplineCurve} from \"../../dist/xeokit-sdk.es.js\";\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a Viewer and arrange the camera\n * //------------------------------------------------------------------------------------------------------------------\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.camera.eye = [-2.341298674548419, 22.43987089731119, 7.236688436028655];\n * viewer.camera.look = [4.399999999999963, 3.7240000000000606, 8.899000000000006];\n * viewer.camera.up = [0.9102954845584759, 0.34781746407929504, 0.22446635042673466];\n *\n * const cameraControl = viewer.cameraControl;\n * cameraControl.navMode = \"orbit\";\n * cameraControl.followPointer = true;\n *\n * //----------------------------------------------------------------------------------------------------------------------\n * // Create a xeokit loader plugin, load a model, fit to view\n * //----------------------------------------------------------------------------------------------------------------------\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * var t0 = performance.now();\n *\n * document.getElementById(\"time\").innerHTML = \"Loading model...\";\n *\n * const sceneModel = xktLoader.load({\n * id: \"myModel\",\n * src: \"../../assets/models/xkt/v10/glTF-Embedded/Duplex_A_20110505.glTFEmbedded.xkt\",\n * edges: true\n * });\n *\n * sceneModel.on(\"loaded\", () => {\n * var t1 = performance.now();\n * document.getElementById(\"time\").innerHTML = \"Model loaded in \" + Math.floor(t1 - t0) / 1000.0 + \" seconds
Objects: \" + sceneModel.numEntities;\n *\n * let path = new SplineCurve(viewer.scene, {\n * points: [\n * [0, 0, -10],\n * [0, 0, -3],\n * [10, 0, 10],\n * [10, 0, 30],\n * ],\n * });\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildPolylineGeometryFromCurve({\n * id: \"SplineCurve\",\n * curve: path,\n * divisions: 50,\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * emissive: [1, 0, 0]\n * })\n * });\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a moving SectionPlane, that moves through the table models\n * //------------------------------------------------------------------------------------------------------------------\n *\n * const sectionPlanes = new SectionPlanesPlugin(viewer, {\n * overviewCanvasId: \"mySectionPlanesOverviewCanvas\",\n * overviewVisible: true\n * });\n *\n * let currentPoint = path.getPoint(0);\n * let currentDirection = path.getTangent(0);\n *\n * const sectionPlane = sectionPlanes.createSectionPlane({\n * id: \"mySectionPlane\",\n * pos: currentPoint,\n * dir: currentDirection\n * });\n *\n * sectionPlanes.showControl(sectionPlane.id);\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Controlling SectionPlane position and direction\n * //------------------------------------------------------------------------------------------------------------------\n *\n * let currentT = 0.0;\n * document.getElementById(\"section_path\").oninput = function() {\n * currentT = Number(document.getElementById(\"section_path\").value);\n * currentPoint = path.getPoint(currentT);\n * currentDirection = path.getTangent(currentT);\n * sectionPlane.pos = currentPoint;\n * sectionPlane.dir = currentDirection;\n * };\n *\n * window.viewer = viewer;\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Controlling CrossSections settings\n * //------------------------------------------------------------------------------------------------------------------\n *\n * viewer.scene.crossSections.sliceThickness = 0.05;\n * viewer.scene.crossSections.sliceColor = [0.0, 0.0, 0.0, 1.0];\n * });\n * ````\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/slicing/#SectionPlanesPlugin_Duplex_SectionPath_CrossSections)]\n *\n */\nclass CrossSections extends Component {\n\n /** @private */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this.sliceColor = cfg.sliceColor;\n this.sliceThickness = cfg.sliceThickness;\n }\n\n /**\n * Sets the thickness of a slice created by a section.\n *\n * Default value is ````0.0````.\n *\n * @type {Number}\n */\n set sliceThickness(value) {\n if (value === undefined || value === null) {\n value = 0.0;\n }\n if (this._sliceThickness === value) {\n return;\n }\n this._sliceThickness = value;\n this.glRedraw();\n }\n\n /**\n * Gets the thickness of a slice created by a section.\n *\n * Default value is ````0.0````.\n *\n * @type {Number}\n */\n get sliceThickness() {\n return this._sliceThickness;\n }\n\n /**\n * Sets the color of a slice created by a section.\n *\n * Default value is ````[0.0, 0.0, 0.0, 1.0]````.\n *\n * @type {Number}\n */\n set sliceColor(value) {\n if (value === undefined || value === null) {\n value = [0.0, 0.0, 0.0, 1.0];\n }\n if (this._sliceColor === value) {\n return;\n }\n this._sliceColor = value;\n this.glRedraw();\n }\n\n /**\n * Gets the color of a slice created by a section.\n *\n * Default value is ````[0.0, 0.0, 0.0, 1.0]````.\n *\n * @type {Number}\n */\n get sliceColor() {\n return this._sliceColor;\n }\n\n /**\n * Destroys this component.\n */\n destroy() {\n super.destroy();\n }\n}\n\nconst PRESETS$1 = {\n \"default\": {\n pointSize: 4,\n roundPoints: true,\n perspectivePoints: true\n },\n \"square\": {\n pointSize: 4,\n roundPoints: false,\n perspectivePoints: true\n },\n \"round\": {\n pointSize: 4,\n roundPoints: true,\n perspectivePoints: true\n }\n};\n\n/**\n * @desc Configures the size and shape of \"points\" geometry primitives.\n *\n * * Located at {@link Scene#pointsMaterial}.\n * * Supports round and square points.\n * * Optional perspective point scaling.\n * * Globally configures \"points\" primitives for all {@link VBOSceneModel}s.\n *\n * ## Usage\n *\n * In the example below, we'll customize the {@link Scene}'s global ````PointsMaterial````, then use\n * an {@link XKTLoaderPlugin} to load a model containing a point cloud.\n *\n * [[Run this example](/examples/index.html#materials_PointsMaterial)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * viewer.scene.pointsMaterial.pointSize = 2;\n * viewer.scene.pointsMaterial.roundPoints = true;\n * viewer.scene.pointsMaterial.perspectivePoints = true;\n * viewer.scene.pointsMaterial.minPerspectivePointSize = 1;\n * viewer.scene.pointsMaterial.maxPerspectivePointSize = 6;\n * viewer.scene.pointsMaterial.filterIntensity = true;\n * viewer.scene.pointsMaterial.minIntensity = 0.0;\n * viewer.scene.pointsMaterial.maxIntensity = 1.0;\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"../assets/models/xkt/MAP-PointCloud.xkt\"\n * });\n * ````\n */\nclass PointsMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"PointsMaterial\";\n }\n\n /**\n * Gets available PointsMaterial presets.\n *\n * @type {Object}\n */\n get presets() {\n return PRESETS$1;\n };\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] The PointsMaterial configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number} [cfg.pointSize=2] Point size in pixels.\n * @param {Boolean} [cfg.roundPoints=true] Whether points are round (````true````) or square (````false````).\n * @param {Boolean} [cfg.perspectivePoints=true] Whether apparent point size reduces with distance when {@link Camera#projection} is set to \"perspective\".\n * @param {Number} [cfg.minPerspectivePointSize=1] When ````perspectivePoints```` is ````true````, this is the minimum rendered size of each point in pixels.\n * @param {Number} [cfg.maxPerspectivePointSize=6] When ````perspectivePoints```` is ````true````, this is the maximum rendered size of each point in pixels.\n * @param {Boolean} [cfg.filterIntensity=false] When this is true, points are only rendered when their intensity value falls within the range given in {@link }\n * @param {Number} [cfg.minIntensity=0] When ````filterIntensity```` is ````true````, points with intensity below this value will not be rendered.\n * @param {Number} [cfg.maxIntensity=1] When ````filterIntensity```` is ````true````, points with intensity above this value will not be rendered.\n * @param {String} [cfg.preset] Selects a preset PointsMaterial configuration - see {@link PointsMaterial#presets}.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"PointsMaterial\",\n pointSize: null,\n roundPoints: null,\n perspectivePoints: null,\n minPerspectivePointSize: null,\n maxPerspectivePointSize: null,\n filterIntensity: null,\n minIntensity: null,\n maxIntensity: null\n });\n\n if (cfg.preset) { // Apply preset then override with configs where provided\n this.preset = cfg.preset;\n if (cfg.pointSize !== undefined) {\n this.pointSize = cfg.pointSize;\n }\n if (cfg.roundPoints !== undefined) {\n this.roundPoints = cfg.roundPoints;\n }\n if (cfg.perspectivePoints !== undefined) {\n this.perspectivePoints = cfg.perspectivePoints;\n }\n if (cfg.minPerspectivePointSize !== undefined) {\n this.minPerspectivePointSize = cfg.minPerspectivePointSize;\n }\n if (cfg.maxPerspectivePointSize !== undefined) {\n this.maxPerspectivePointSize = cfg.minPerspectivePointSize;\n }\n } else {\n this._preset = \"default\";\n this.pointSize = cfg.pointSize;\n this.roundPoints = cfg.roundPoints;\n\n this.perspectivePoints = cfg.perspectivePoints;\n this.minPerspectivePointSize = cfg.minPerspectivePointSize;\n this.maxPerspectivePointSize = cfg.maxPerspectivePointSize;\n }\n\n this.filterIntensity = cfg.filterIntensity;\n this.minIntensity = cfg.minIntensity;\n this.maxIntensity = cfg.maxIntensity;\n }\n\n /**\n * Sets point size.\n *\n * Default value is ````2.0```` pixels.\n *\n * @type {Number}\n */\n set pointSize(value) {\n this._state.pointSize = value || 2.0;\n this.glRedraw();\n }\n\n /**\n * Gets point size.\n *\n * Default value is ````2.0```` pixels.\n *\n * @type {Number}\n */\n get pointSize() {\n return this._state.pointSize;\n }\n\n\n /**\n * Sets if points are round or square.\n *\n * Default is ````true```` to set points round.\n *\n * @type {Boolean}\n */\n set roundPoints(value) {\n value = (value !== false);\n if (this._state.roundPoints === value) {\n return;\n }\n this._state.roundPoints = value;\n this.scene._needRecompile = true;\n this.glRedraw();\n }\n\n /**\n * Gets if points are round or square.\n *\n * Default is ````true```` to set points round.\n *\n * @type {Boolean}\n */\n get roundPoints() {\n return this._state.roundPoints;\n }\n\n /**\n * Sets if rendered point size reduces with distance when {@link Camera#projection} is set to ````\"perspective\"````.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set perspectivePoints(value) {\n value = (value !== false);\n if (this._state.perspectivePoints === value) {\n return;\n }\n this._state.perspectivePoints = value;\n this.scene._needRecompile = true;\n this.glRedraw();\n }\n\n /**\n * Gets if rendered point size reduces with distance when {@link Camera#projection} is set to \"perspective\".\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n get perspectivePoints() {\n return this._state.perspectivePoints;\n }\n\n /**\n * Sets the minimum rendered size of points when {@link PointsMaterial#perspectivePoints} is ````true````.\n *\n * Default value is ````1.0```` pixels.\n *\n * @type {Number}\n */\n set minPerspectivePointSize(value) {\n this._state.minPerspectivePointSize = value || 1.0;\n this.scene._needRecompile = true;\n this.glRedraw();\n }\n\n /**\n * Gets the minimum rendered size of points when {@link PointsMaterial#perspectivePoints} is ````true````.\n *\n * Default value is ````1.0```` pixels.\n *\n * @type {Number}\n */\n get minPerspectivePointSize() {\n return this._state.minPerspectivePointSize;\n }\n\n /**\n * Sets the maximum rendered size of points when {@link PointsMaterial#perspectivePoints} is ````true````.\n *\n * Default value is ````6```` pixels.\n *\n * @type {Number}\n */\n set maxPerspectivePointSize(value) {\n this._state.maxPerspectivePointSize = value || 6;\n this.scene._needRecompile = true;\n this.glRedraw();\n }\n\n /**\n * Gets the maximum rendered size of points when {@link PointsMaterial#perspectivePoints} is ````true````.\n *\n * Default value is ````6```` pixels.\n *\n * @type {Number}\n */\n get maxPerspectivePointSize() {\n return this._state.maxPerspectivePointSize;\n }\n\n /**\n * Sets if rendered point size reduces with distance when {@link Camera#projection} is set to ````\"perspective\"````.\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n set filterIntensity(value) {\n value = (value !== false);\n if (this._state.filterIntensity === value) {\n return;\n }\n this._state.filterIntensity = value;\n this.scene._needRecompile = true;\n this.glRedraw();\n }\n\n /**\n * Gets if rendered point size reduces with distance when {@link Camera#projection} is set to \"perspective\".\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n get filterIntensity() {\n return this._state.filterIntensity;\n }\n\n /**\n * Sets the minimum rendered size of points when {@link PointsMaterial#perspectivePoints} is ````true````.\n *\n * Default value is ````0````.\n *\n * @type {Number}\n */\n set minIntensity(value) {\n this._state.minIntensity = (value !== undefined && value !== null) ? value: 0.0;\n this.glRedraw();\n }\n\n /**\n * Gets the minimum rendered size of points when {@link PointsMaterial#filterIntensity} is ````true````.\n *\n * Default value is ````0````.\n *\n * @type {Number}\n */\n get minIntensity() {\n return this._state.minIntensity;\n }\n\n /**\n * Sets the maximum rendered size of points when {@link PointsMaterial#filterIntensity} is ````true````.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n set maxIntensity(value) {\n this._state.maxIntensity = (value !== undefined && value !== null) ? value: 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the maximum rendered size of points when {@link PointsMaterial#filterIntensity} is ````true````.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n get maxIntensity() {\n return this._state.maxIntensity;\n }\n\n /**\n * Selects a preset ````PointsMaterial```` configuration.\n *\n * Default value is ````\"default\"````.\n *\n * @type {String}\n */\n set preset(value) {\n value = value || \"default\";\n if (this._preset === value) {\n return;\n }\n const preset = PRESETS$1[value];\n if (!preset) {\n this.error(\"unsupported preset: '\" + value + \"' - supported values are \" + Object.keys(PRESETS$1).join(\", \"));\n return;\n }\n this.pointSize = preset.pointSize;\n this.roundPoints = preset.roundPoints;\n this.perspectivePoints = preset.perspectivePoints;\n this.minPerspectivePointSize = preset.minPerspectivePointSize;\n this.maxPerspectivePointSize = preset.maxPerspectivePointSize;\n this._preset = value;\n }\n\n /**\n * The current preset ````PointsMaterial```` configuration.\n *\n * Default value is ````\"default\"````.\n *\n * @type {String}\n */\n get preset() {\n return this._preset;\n }\n\n /**\n * @private\n * @return {string}\n */\n get hash() {\n return [\n this.pointSize,\n this.roundPoints,\n this.perspectivePoints,\n this.minPerspectivePointSize,\n this.maxPerspectivePointSize,\n this.filterIntensity\n ].join((\";\"));\n }\n\n /**\n * Destroys this ````PointsMaterial````.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\nconst PRESETS = {\n \"default\": {\n lineWidth: 1\n },\n \"thick\": {\n lineWidth: 2\n },\n \"thicker\": {\n lineWidth: 4\n }\n};\n\n/**\n * @desc Configures the shape of \"lines\" geometry primitives.\n *\n * * Located at {@link Scene#linesMaterial}.\n * * Globally configures \"lines\" primitives for all {@link VBOSceneModel}s.\n *\n * ## Usage\n *\n * In the example below, we'll customize the {@link Scene}'s global ````LinesMaterial````, then use\n * an {@link XKTLoaderPlugin} to load a model containing line segments.\n *\n * [[Run this example](/examples/index.html#materials_LinesMaterial)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * viewer.scene.linesMaterial.lineWidth = 3;\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/Duplex.ifc.xkt\"\n * });\n * ````\n */\nclass LinesMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"LinesMaterial\";\n }\n\n /**\n * Gets available LinesMaterial presets.\n *\n * @type {Object}\n */\n get presets() {\n return PRESETS;\n };\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] The LinesMaterial configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number} [cfg.lineWidth=1] Line width in pixels.\n * @param {String} [cfg.preset] Selects a preset LinesMaterial configuration - see {@link LinesMaterial#presets}.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"LinesMaterial\",\n lineWidth: null\n });\n\n if (cfg.preset) { // Apply preset then override with configs where provided\n this.preset = cfg.preset;\n if (cfg.lineWidth !== undefined) {\n this.lineWidth = cfg.lineWidth;\n }\n } else {\n this._preset = \"default\";\n this.lineWidth = cfg.lineWidth;\n }\n }\n\n /**\n * Sets line width.\n *\n * Default value is ````1```` pixels.\n *\n * @type {Number}\n */\n set lineWidth(value) {\n this._state.lineWidth = value || 1;\n this.glRedraw();\n }\n\n /**\n * Gets the line width.\n *\n * Default value is ````1```` pixels.\n *\n * @type {Number}\n */\n get lineWidth() {\n return this._state.lineWidth;\n }\n\n /**\n * Selects a preset LinesMaterial configuration.\n *\n * Default value is ````\"default\"````.\n *\n * @type {String}\n */\n set preset(value) {\n value = value || \"default\";\n if (this._preset === value) {\n return;\n }\n const preset = PRESETS[value];\n if (!preset) {\n this.error(\"unsupported preset: '\" + value + \"' - supported values are \" + Object.keys(PRESETS).join(\", \"));\n return;\n }\n this.lineWidth = preset.lineWidth;\n this._preset = value;\n }\n\n /**\n * The current preset LinesMaterial configuration.\n *\n * Default value is ````\"default\"````.\n *\n * @type {String}\n */\n get preset() {\n return this._preset;\n }\n\n /**\n * @private\n * @return {string}\n */\n get hash() {\n return [\"\" + this.lineWidth].join((\";\"));\n }\n\n /**\n * Destroys this LinesMaterial.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\n// Cached vars to avoid garbage collection\n\nfunction getEntityIDMap(scene, entityIds) {\n const map = {};\n let entityId;\n let entity;\n for (let i = 0, len = entityIds.length; i < len; i++) {\n entityId = entityIds[i];\n entity = scene.components[entityId];\n if (!entity) {\n scene.warn(\"pick(): Component not found: \" + entityId);\n continue;\n }\n if (!entity.isEntity) {\n scene.warn(\"pick(): Component is not an Entity: \" + entityId);\n continue;\n }\n map[entityId] = true;\n }\n return map;\n}\n\n/**\n * Fired whenever a debug message is logged on a component within this Scene.\n * @event log\n * @param {String} value The debug message\n */\n\n/**\n * Fired whenever an error is logged on a component within this Scene.\n * @event error\n * @param {String} value The error message\n */\n\n/**\n * Fired whenever a warning is logged on a component within this Scene.\n * @event warn\n * @param {String} value The warning message\n */\n\n/**\n * @desc Contains the components that comprise a 3D scene.\n *\n * * A {@link Viewer} has a single Scene, which it provides in {@link Viewer#scene}.\n * * Plugins like {@link AxisGizmoPlugin} also have their own private Scenes.\n * * Each Scene has a corresponding {@link MetaScene}, which the Viewer provides in {@link Viewer#metaScene}.\n *\n * ## Getting a Viewer's Scene\n *\n * ````javascript\n * var scene = viewer.scene;\n * ````\n *\n * ## Creating and accessing Scene components\n *\n * As a brief introduction to creating Scene components, we'll create a {@link Mesh} that has a\n * {@link buildTorusGeometry} and a {@link PhongMaterial}:\n *\n * ````javascript\n * var teapotMesh = new Mesh(scene, {\n * id: \"myMesh\", // <<---------- ID automatically generated if not provided\n * geometry: new TorusGeometry(scene),\n * material: new PhongMaterial(scene, {\n * id: \"myMaterial\",\n * diffuse: [0.2, 0.2, 1.0]\n * })\n * });\n *\n * teapotMesh.scene.camera.eye = [45, 45, 45];\n * ````\n *\n * Find components by ID in their Scene's {@link Scene#components} map:\n *\n * ````javascript\n * var teapotMesh = scene.components[\"myMesh\"];\n * teapotMesh.visible = false;\n *\n * var teapotMaterial = scene.components[\"myMaterial\"];\n * teapotMaterial.diffuse = [1,0,0]; // Change to red\n * ````\n *\n * A Scene also has a map of component instances for each {@link Component} subtype:\n *\n * ````javascript\n * var meshes = scene.types[\"Mesh\"];\n * var teapotMesh = meshes[\"myMesh\"];\n * teapotMesh.xrayed = true;\n *\n * var phongMaterials = scene.types[\"PhongMaterial\"];\n * var teapotMaterial = phongMaterials[\"myMaterial\"];\n * teapotMaterial.diffuse = [0,1,0]; // Change to green\n * ````\n *\n * See {@link Node}, {@link Node} and {@link Model} for how to create and access more sophisticated content.\n *\n * ## Controlling the camera\n *\n * Use the Scene's {@link Camera} to control the current viewpoint and projection:\n *\n * ````javascript\n * var camera = myScene.camera;\n *\n * camera.eye = [-10,0,0];\n * camera.look = [-10,0,0];\n * camera.up = [0,1,0];\n *\n * camera.projection = \"perspective\";\n * camera.perspective.fov = 45;\n * //...\n * ````\n *\n * ## Managing the canvas\n *\n * The Scene's {@link Canvas} component provides various conveniences relevant to the WebGL canvas, such\n * as firing resize events etc:\n *\n * ````javascript\n * var canvas = scene.canvas;\n *\n * canvas.on(\"boundary\", function(boundary) {\n * //...\n * });\n * ````\n *\n * ## Picking\n *\n * Use {@link Scene#pick} to pick and raycast entites.\n *\n * For example, to pick a point on the surface of the closest entity at the given canvas coordinates:\n *\n * ````javascript\n * var pickResult = scene.pick({\n * pickSurface: true,\n * canvasPos: [23, 131]\n * });\n *\n * if (pickResult) { // Picked an entity\n *\n * var entity = pickResult.entity;\n *\n * var primitive = pickResult.primitive; // Type of primitive that was picked, usually \"triangles\"\n * var primIndex = pickResult.primIndex; // Position of triangle's first index in the picked Mesh's Geometry's indices array\n * var indices = pickResult.indices; // UInt32Array containing the triangle's vertex indices\n * var localPos = pickResult.localPos; // Float64Array containing the picked Local-space position on the triangle\n * var worldPos = pickResult.worldPos; // Float64Array containing the picked World-space position on the triangle\n * var viewPos = pickResult.viewPos; // Float64Array containing the picked View-space position on the triangle\n * var bary = pickResult.bary; // Float64Array containing the picked barycentric position within the triangle\n * var normal = pickResult.normal; // Float64Array containing the interpolated normal vector at the picked position on the triangle\n * var uv = pickResult.uv; // Float64Array containing the interpolated UV coordinates at the picked position on the triangle\n * }\n * ````\n *\n * ## Pick masking\n *\n * We can use {@link Scene#pick}'s ````includeEntities```` and ````excludeEntities```` options to mask which {@link Mesh}es we attempt to pick.\n *\n * This is useful for picking through things, to pick only the Entities of interest.\n *\n * To pick only Entities ````\"gearbox#77.0\"```` and ````\"gearbox#79.0\"````, picking through any other Entities that are\n * in the way, as if they weren't there:\n *\n * ````javascript\n * var pickResult = scene.pick({\n * canvasPos: [23, 131],\n * includeEntities: [\"gearbox#77.0\", \"gearbox#79.0\"]\n * });\n *\n * if (pickResult) {\n * // Entity will always be either \"gearbox#77.0\" or \"gearbox#79.0\"\n * var entity = pickResult.entity;\n * }\n * ````\n *\n * To pick any pickable Entity, except for ````\"gearbox#77.0\"```` and ````\"gearbox#79.0\"````, picking through those\n * Entities if they happen to be in the way:\n *\n * ````javascript\n * var pickResult = scene.pick({\n * canvasPos: [23, 131],\n * excludeEntities: [\"gearbox#77.0\", \"gearbox#79.0\"]\n * });\n *\n * if (pickResult) {\n * // Entity will never be \"gearbox#77.0\" or \"gearbox#79.0\"\n * var entity = pickResult.entity;\n * }\n * ````\n *\n * See {@link Scene#pick} for more info on picking.\n *\n * ## Querying and tracking boundaries\n *\n * Getting a Scene's World-space axis-aligned boundary (AABB):\n *\n * ````javascript\n * var aabb = scene.aabb; // [xmin, ymin, zmin, xmax, ymax, zmax]\n * ````\n *\n * Subscribing to updates to the AABB, which occur whenever {@link Entity}s are transformed, their\n * {@link ReadableGeometry}s have been updated, or the {@link Camera} has moved:\n *\n * ````javascript\n * scene.on(\"boundary\", function() {\n * var aabb = scene.aabb;\n * });\n * ````\n *\n * Getting the AABB of the {@link Entity}s with the given IDs:\n *\n * ````JavaScript\n * scene.getAABB(); // Gets collective boundary of all Entities in the scene\n * scene.getAABB(\"saw\"); // Gets boundary of an Object\n * scene.getAABB([\"saw\", \"gearbox\"]); // Gets collective boundary of two Objects\n * ````\n *\n * See {@link Scene#getAABB} and {@link Entity} for more info on querying and tracking boundaries.\n *\n * ## Managing the viewport\n *\n * The Scene's {@link Viewport} component manages the WebGL viewport:\n *\n * ````javascript\n * var viewport = scene.viewport\n * viewport.boundary = [0, 0, 500, 400];;\n * ````\n *\n * ## Controlling rendering\n *\n * You can configure a Scene to perform multiple \"passes\" (renders) per frame. This is useful when we want to render the\n * scene to multiple viewports, such as for stereo effects.\n *\n * In the example, below, we'll configure the Scene to render twice on each frame, each time to different viewport. We'll do this\n * with a callback that intercepts the Scene before each render and sets its {@link Viewport} to a\n * different portion of the canvas. By default, the Scene will clear the canvas only before the first render, allowing the\n * two views to be shown on the canvas at the same time.\n *\n * ````Javascript\n * var viewport = scene.viewport;\n *\n * // Configure Scene to render twice for each frame\n * scene.passes = 2; // Default is 1\n * scene.clearEachPass = false; // Default is false\n *\n * // Render to a separate viewport on each render\n *\n * var viewport = scene.viewport;\n * viewport.autoBoundary = false;\n *\n * scene.on(\"rendering\", function (e) {\n * switch (e.pass) {\n * case 0:\n * viewport.boundary = [0, 0, 200, 200]; // xmin, ymin, width, height\n * break;\n *\n * case 1:\n * viewport.boundary = [200, 0, 200, 200];\n * break;\n * }\n * });\n *\n * // We can also intercept the Scene after each render,\n * // (though we're not using this for anything here)\n * scene.on(\"rendered\", function (e) {\n * switch (e.pass) {\n * case 0:\n * break;\n *\n * case 1:\n * break;\n * }\n * });\n * ````\n *\n * ## Gamma correction\n *\n * Within its shaders, xeokit performs shading calculations in linear space.\n *\n * By default, the Scene expects color textures (eg. {@link PhongMaterial#diffuseMap},\n * {@link MetallicMaterial#baseColorMap} and {@link SpecularMaterial#diffuseMap}) to\n * be in pre-multipled gamma space, so will convert those to linear space before they are used in shaders. Other textures are\n * always expected to be in linear space.\n *\n * By default, the Scene will also gamma-correct its rendered output.\n *\n * You can configure the Scene to expect all those color textures to be linear space, so that it does not gamma-correct them:\n *\n * ````javascript\n * scene.gammaInput = false;\n * ````\n *\n * You would still need to gamma-correct the output, though, if it's going straight to the canvas, so normally we would\n * leave that enabled:\n *\n * ````javascript\n * scene.gammaOutput = true;\n * ````\n *\n * See {@link Texture} for more information on texture encoding and gamma.\n *\n * @class Scene\n */\nclass Scene extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Scene\";\n }\n\n /**\n * @private\n * @constructor\n * @param {Viewer} viewer The Viewer this Scene belongs to.\n * @param {Object} cfg Scene configuration.\n * @param {String} [cfg.canvasId] ID of an existing HTML canvas for the {@link Scene#canvas} - either this or canvasElement is mandatory. When both values are given, the element reference is always preferred to the ID.\n * @param {HTMLCanvasElement} [cfg.canvasElement] Reference of an existing HTML canvas for the {@link Scene#canvas} - either this or canvasId is mandatory. When both values are given, the element reference is always preferred to the ID.\n * @param {HTMLElement} [cfg.keyboardEventsElement] Optional reference to HTML element on which key events should be handled. Defaults to the HTML Document.\n * @param {number} [cfg.numCachedSectionPlanes=0] Enhances the efficiency of SectionPlane creation by proactively allocating Viewer resources for a specified quantity\n * of SectionPlanes. Introducing this parameter streamlines the initial creation speed of SectionPlanes, particularly up to the designated quantity. This parameter internally\n * configures renderer logic for the specified number of SectionPlanes, eliminating the need for setting up logic with each SectionPlane creation and thereby enhancing\n * responsiveness. It is important to consider that each SectionPlane imposes rendering performance, so it is recommended to set this value to a quantity that aligns with\n * your expected usage.\n * @throws {String} Throws an exception when both canvasId or canvasElement are missing or they aren't pointing to a valid HTMLCanvasElement.\n */\n constructor(viewer, cfg = {}) {\n\n super(null, cfg);\n\n const canvas = cfg.canvasElement || document.getElementById(cfg.canvasId);\n\n if (!(canvas instanceof HTMLCanvasElement)) {\n throw \"Mandatory config expected: valid canvasId or canvasElement\";\n }\n\n /**\n * @type {{[key: string]: {wrapperFunc: Function, tickSubId: string}}}\n */\n this._tickifiedFunctions = {};\n\n const transparent = (!!cfg.transparent);\n const alphaDepthMask = (!!cfg.alphaDepthMask);\n\n this._aabbDirty = true;\n\n /**\n * The {@link Viewer} this Scene belongs to.\n * @type {Viewer}\n */\n this.viewer = viewer;\n\n /** Decremented each frame, triggers occlusion test for occludable {@link Marker}s when zero.\n * @private\n * @type {number}\n */\n this.occlusionTestCountdown = 0;\n\n /**\n The number of models currently loading.\n\n @property loading\n @final\n @type {Number}\n */\n this.loading = 0;\n\n /**\n The epoch time (in milliseconds since 1970) when this Scene was instantiated.\n\n @property timeCreated\n @final\n @type {Number}\n */\n this.startTime = (new Date()).getTime();\n\n /**\n * Map of {@link Entity}s that represent models.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id} when {@link Entity#isModel} is ````true````.\n *\n * @property models\n * @final\n * @type {{String:Entity}}\n */\n this.models = {};\n\n /**\n * Map of {@link Entity}s that represents objects.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id} when {@link Entity#isObject} is ````true````.\n *\n * @property objects\n * @final\n * @type {{String:Entity}}\n */\n this.objects = {};\n this._numObjects = 0;\n\n /**\n * Map of currently visible {@link Entity}s that represent objects.\n *\n * An Entity represents an object if {@link Entity#isObject} is ````true````, and is visible when {@link Entity#visible} is true.\n *\n * @property visibleObjects\n * @final\n * @type {{String:Object}}\n */\n this.visibleObjects = {};\n this._numVisibleObjects = 0;\n\n /**\n * Map of currently xrayed {@link Entity}s that represent objects.\n *\n * An Entity represents an object if {@link Entity#isObject} is ````true````, and is xrayed when {@link Entity#xrayed} is true.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id}.\n *\n * @property xrayedObjects\n * @final\n * @type {{String:Object}}\n */\n this.xrayedObjects = {};\n this._numXRayedObjects = 0;\n\n /**\n * Map of currently highlighted {@link Entity}s that represent objects.\n *\n * An Entity represents an object if {@link Entity#isObject} is ````true```` is true, and is highlighted when {@link Entity#highlighted} is true.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id}.\n *\n * @property highlightedObjects\n * @final\n * @type {{String:Object}}\n */\n this.highlightedObjects = {};\n this._numHighlightedObjects = 0;\n\n /**\n * Map of currently selected {@link Entity}s that represent objects.\n *\n * An Entity represents an object if {@link Entity#isObject} is true, and is selected while {@link Entity#selected} is true.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id}.\n *\n * @property selectedObjects\n * @final\n * @type {{String:Object}}\n */\n this.selectedObjects = {};\n this._numSelectedObjects = 0;\n\n /**\n * Map of currently colorized {@link Entity}s that represent objects.\n *\n * An Entity represents an object if {@link Entity#isObject} is ````true````.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id}.\n *\n * @property colorizedObjects\n * @final\n * @type {{String:Object}}\n */\n this.colorizedObjects = {};\n this._numColorizedObjects = 0;\n\n /**\n * Map of {@link Entity}s that represent objects whose opacity was updated.\n *\n * An Entity represents an object if {@link Entity#isObject} is ````true````.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id}.\n *\n * @property opacityObjects\n * @final\n * @type {{String:Object}}\n */\n this.opacityObjects = {};\n this._numOpacityObjects = 0;\n\n /**\n * Map of {@link Entity}s that represent objects whose {@link Entity#offset}s were updated.\n *\n * An Entity represents an object if {@link Entity#isObject} is ````true````.\n *\n * Each {@link Entity} is mapped here by {@link Entity#id}.\n *\n * @property offsetObjects\n * @final\n * @type {{String:Object}}\n */\n this.offsetObjects = {};\n this._numOffsetObjects = 0;\n\n // Cached ID arrays, lazy-rebuilt as needed when stale after map updates\n\n /**\n Lazy-regenerated ID lists.\n */\n this._modelIds = null;\n this._objectIds = null;\n this._visibleObjectIds = null;\n this._xrayedObjectIds = null;\n this._highlightedObjectIds = null;\n this._selectedObjectIds = null;\n this._colorizedObjectIds = null;\n this._opacityObjectIds = null;\n this._offsetObjectIds = null;\n\n this._collidables = {}; // Components that contribute to the Scene AABB\n this._compilables = {}; // Components that require shader compilation\n\n this._needRecompile = false;\n\n /**\n * For each {@link Component} type, a map of IDs to {@link Component} instances of that type.\n *\n * @type {{String:{String:Component}}}\n */\n this.types = {};\n\n /**\n * The {@link Component}s within this Scene, each mapped to its {@link Component#id}.\n *\n * *@type {{String:Component}}\n */\n this.components = {};\n\n /**\n * The {@link SectionPlane}s in this Scene, each mapped to its {@link SectionPlane#id}.\n *\n * @type {{String:SectionPlane}}\n */\n this.sectionPlanes = {};\n\n /**\n * The {@link Light}s in this Scene, each mapped to its {@link Light#id}.\n *\n * @type {{String:Light}}\n */\n this.lights = {};\n\n /**\n * The {@link LightMap}s in this Scene, each mapped to its {@link LightMap#id}.\n *\n * @type {{String:LightMap}}\n */\n this.lightMaps = {};\n\n /**\n * The {@link ReflectionMap}s in this Scene, each mapped to its {@link ReflectionMap#id}.\n *\n * @type {{String:ReflectionMap}}\n */\n this.reflectionMaps = {};\n\n /**\n * The {@link Bitmap}s in this Scene, each mapped to its {@link Bitmap#id}.\n *\n * @type {{String:Bitmap}}\n */\n this.bitmaps = {};\n\n /**\n * The {@link LineSet}s in this Scene, each mapped to its {@link LineSet#id}.\n *\n * @type {{String:LineSet}}\n */\n this.lineSets = {};\n\n /**\n * The real world offset for this Scene\n *\n * @type {Number[]}\n */\n this.realWorldOffset = cfg.realWorldOffset || new Float64Array([0, 0, 0]);\n\n /**\n * Manages the HTML5 canvas for this Scene.\n *\n * @type {Canvas}\n */\n this.canvas = new Canvas(this, {\n dontClear: true, // Never destroy this component with Scene#clear();\n canvas: canvas,\n spinnerElementId: cfg.spinnerElementId,\n transparent: transparent,\n webgl2: cfg.webgl2 !== false,\n contextAttr: cfg.contextAttr || {},\n backgroundColor: cfg.backgroundColor,\n backgroundColorFromAmbientLight: cfg.backgroundColorFromAmbientLight,\n premultipliedAlpha: cfg.premultipliedAlpha\n });\n\n this.canvas.on(\"boundary\", () => {\n this.glRedraw();\n });\n\n this.canvas.on(\"webglContextFailed\", () => {\n alert(\"xeokit failed to find WebGL!\");\n });\n\n this._renderer = new Renderer$1(this, {\n transparent: transparent,\n alphaDepthMask: alphaDepthMask\n });\n\n this._sectionPlanesState = new (function () {\n\n this.sectionPlanes = [];\n\n this.clippingCaps = false;\n\n this._numCachedSectionPlanes = 0;\n\n let hash = null;\n\n this.getHash = function () {\n if (hash) {\n return hash;\n }\n const numAllocatedSectionPlanes = this.getNumAllocatedSectionPlanes();\n this.sectionPlanes;\n if (numAllocatedSectionPlanes === 0) {\n return this.hash = \";\";\n }\n\n const hashParts = [];\n for (let i = 0, len = numAllocatedSectionPlanes; i < len; i++) {\n hashParts.push(\"cp\");\n }\n hashParts.push(\";\");\n hash = hashParts.join(\"\");\n return hash;\n };\n\n this.addSectionPlane = function (sectionPlane) {\n this.sectionPlanes.push(sectionPlane);\n hash = null;\n };\n\n this.removeSectionPlane = function (sectionPlane) {\n for (let i = 0, len = this.sectionPlanes.length; i < len; i++) {\n if (this.sectionPlanes[i].id === sectionPlane.id) {\n this.sectionPlanes.splice(i, 1);\n hash = null;\n return;\n }\n }\n };\n\n this.setNumCachedSectionPlanes = function (numCachedSectionPlanes) {\n this._numCachedSectionPlanes = numCachedSectionPlanes;\n hash = null;\n };\n\n this.getNumCachedSectionPlanes = function () {\n return this._numCachedSectionPlanes;\n };\n\n this.getNumAllocatedSectionPlanes = function () {\n const num = this.sectionPlanes.length;\n return (num > this._numCachedSectionPlanes) ? num : this._numCachedSectionPlanes;\n };\n })();\n\n this._sectionPlanesState.setNumCachedSectionPlanes(cfg.numCachedSectionPlanes || 0);\n\n this._lightsState = new (function () {\n\n const DEFAULT_AMBIENT = math.vec4([0, 0, 0, 0]);\n const ambientColorIntensity = math.vec4();\n\n this.lights = [];\n this.reflectionMaps = [];\n this.lightMaps = [];\n\n let hash = null;\n let ambientLight = null;\n\n this.getHash = function () {\n if (hash) {\n return hash;\n }\n const hashParts = [];\n const lights = this.lights;\n let light;\n for (let i = 0, len = lights.length; i < len; i++) {\n light = lights[i];\n hashParts.push(\"/\");\n hashParts.push(light.type);\n hashParts.push((light.space === \"world\") ? \"w\" : \"v\");\n if (light.castsShadow) {\n hashParts.push(\"sh\");\n }\n }\n if (this.lightMaps.length > 0) {\n hashParts.push(\"/lm\");\n }\n if (this.reflectionMaps.length > 0) {\n hashParts.push(\"/rm\");\n }\n hashParts.push(\";\");\n hash = hashParts.join(\"\");\n return hash;\n };\n\n this.addLight = function (state) {\n this.lights.push(state);\n ambientLight = null;\n hash = null;\n };\n\n this.removeLight = function (state) {\n for (let i = 0, len = this.lights.length; i < len; i++) {\n const light = this.lights[i];\n if (light.id === state.id) {\n this.lights.splice(i, 1);\n if (ambientLight && ambientLight.id === state.id) {\n ambientLight = null;\n }\n hash = null;\n return;\n }\n }\n };\n\n this.addReflectionMap = function (state) {\n this.reflectionMaps.push(state);\n hash = null;\n };\n\n this.removeReflectionMap = function (state) {\n for (let i = 0, len = this.reflectionMaps.length; i < len; i++) {\n if (this.reflectionMaps[i].id === state.id) {\n this.reflectionMaps.splice(i, 1);\n hash = null;\n return;\n }\n }\n };\n\n this.addLightMap = function (state) {\n this.lightMaps.push(state);\n hash = null;\n };\n\n this.removeLightMap = function (state) {\n for (let i = 0, len = this.lightMaps.length; i < len; i++) {\n if (this.lightMaps[i].id === state.id) {\n this.lightMaps.splice(i, 1);\n hash = null;\n return;\n }\n }\n };\n\n this.getAmbientColorAndIntensity = function () {\n if (!ambientLight) {\n for (let i = 0, len = this.lights.length; i < len; i++) {\n const light = this.lights[i];\n if (light.type === \"ambient\") {\n ambientLight = light;\n break;\n }\n }\n }\n if (ambientLight) {\n const color = ambientLight.color;\n const intensity = ambientLight.intensity;\n ambientColorIntensity[0] = color[0];\n ambientColorIntensity[1] = color[1];\n ambientColorIntensity[2] = color[2];\n ambientColorIntensity[3] = intensity;\n return ambientColorIntensity;\n } else {\n return DEFAULT_AMBIENT;\n }\n };\n\n })();\n\n /**\n * Publishes input events that occur on this Scene's canvas.\n *\n * @property input\n * @type {Input}\n * @final\n */\n this.input = new Input(this, {\n dontClear: true, // Never destroy this component with Scene#clear();\n element: this.canvas.canvas,\n keyboardEventsElement: cfg.keyboardEventsElement\n });\n\n /**\n * Configures this Scene's units of measurement and coordinate mapping between Real-space and World-space 3D coordinate systems.\n *\n * @property metrics\n * @type {Metrics}\n * @final\n */\n this.metrics = new Metrics(this, {\n units: cfg.units,\n scale: cfg.scale,\n origin: cfg.origin\n });\n\n /** Configures Scalable Ambient Obscurance (SAO) for this Scene.\n * @type {SAO}\n * @final\n */\n this.sao = new SAO(this, {\n enabled: cfg.saoEnabled\n });\n\n /** Configures Cross Sections for this Scene.\n * @type {CrossSections}\n * @final\n */\n this.crossSections = new CrossSections(this, {\n\n });\n\n this.ticksPerRender = cfg.ticksPerRender;\n this.ticksPerOcclusionTest = cfg.ticksPerOcclusionTest;\n this.passes = cfg.passes;\n this.clearEachPass = cfg.clearEachPass;\n this.gammaInput = cfg.gammaInput;\n this.gammaOutput = cfg.gammaOutput;\n this.gammaFactor = cfg.gammaFactor;\n\n this._entityOffsetsEnabled = !!cfg.entityOffsetsEnabled;\n this._logarithmicDepthBufferEnabled = !!cfg.logarithmicDepthBufferEnabled;\n\n this._dtxEnabled = (cfg.dtxEnabled !== false);\n this._pbrEnabled = !!cfg.pbrEnabled;\n this._colorTextureEnabled = (cfg.colorTextureEnabled !== false);\n this._dtxEnabled = !!cfg.dtxEnabled;\n\n // Register Scene on xeokit\n // Do this BEFORE we add components below\n core._addScene(this);\n\n this._initDefaults();\n\n // Global components\n\n this._viewport = new Viewport(this, {\n id: \"default.viewport\",\n autoBoundary: true,\n dontClear: true // Never destroy this component with Scene#clear();\n });\n\n this._camera = new Camera(this, {\n id: \"default.camera\",\n dontClear: true // Never destroy this component with Scene#clear();\n });\n\n // Default lights\n\n new AmbientLight(this, {\n color: [1.0, 1.0, 1.0],\n intensity: 0.7\n });\n\n new DirLight(this, {\n dir: [0.8, -.5, -0.5],\n color: [0.67, 0.67, 1.0],\n intensity: 0.7,\n space: \"world\"\n });\n\n new DirLight(this, {\n dir: [-0.8, -1.0, 0.5],\n color: [1, 1, .9],\n intensity: 0.9,\n space: \"world\"\n });\n\n this._camera.on(\"dirty\", () => {\n this._renderer.imageDirty();\n });\n }\n\n _initDefaults() {\n }\n\n _addComponent(component) {\n if (component.id) { // Manual ID\n if (this.components[component.id]) {\n this.error(\"Component \" + utils.inQuotes(component.id) + \" already exists in Scene - ignoring ID, will randomly-generate instead\");\n component.id = null;\n }\n }\n if (!component.id) { // Auto ID\n if (window.nextID === undefined) {\n window.nextID = 0;\n }\n //component.id = math.createUUID();\n component.id = \"__\" + window.nextID++;\n while (this.components[component.id]) {\n component.id = math.createUUID();\n }\n }\n this.components[component.id] = component;\n\n // Register for class type\n const type = component.type;\n let types = this.types[component.type];\n if (!types) {\n types = this.types[type] = {};\n }\n types[component.id] = component;\n\n if (component.compile) {\n this._compilables[component.id] = component;\n }\n if (component.isDrawable) {\n this._renderer.addDrawable(component.id, component);\n this._collidables[component.id] = component;\n }\n }\n\n _removeComponent(component) {\n var id = component.id;\n var type = component.type;\n delete this.components[id];\n // Unregister for types\n const types = this.types[type];\n if (types) {\n delete types[id];\n if (utils.isEmptyObject(types)) {\n delete this.types[type];\n }\n }\n if (component.compile) {\n delete this._compilables[component.id];\n }\n if (component.isDrawable) {\n this._renderer.removeDrawable(component.id);\n delete this._collidables[component.id];\n }\n }\n\n // Methods below are called by various component types to register themselves on their\n // Scene. Violates Hollywood Principle, where we could just filter on type in _addComponent,\n // but this is faster than checking the type of each component in such a filter.\n\n _sectionPlaneCreated(sectionPlane) {\n this.sectionPlanes[sectionPlane.id] = sectionPlane;\n this.scene._sectionPlanesState.addSectionPlane(sectionPlane._state);\n this.scene.fire(\"sectionPlaneCreated\", sectionPlane, true /* Don't retain event */);\n this._needRecompile = true;\n }\n\n _bitmapCreated(bitmap) {\n this.bitmaps[bitmap.id] = bitmap;\n this.scene.fire(\"bitmapCreated\", bitmap, true /* Don't retain event */);\n }\n\n _lineSetCreated(lineSet) {\n this.lineSets[lineSet.id] = lineSet;\n this.scene.fire(\"lineSetCreated\", lineSet, true /* Don't retain event */);\n }\n\n _lightCreated(light) {\n this.lights[light.id] = light;\n this.scene._lightsState.addLight(light._state);\n this._needRecompile = true;\n }\n\n _lightMapCreated(lightMap) {\n this.lightMaps[lightMap.id] = lightMap;\n this.scene._lightsState.addLightMap(lightMap._state);\n this._needRecompile = true;\n }\n\n _reflectionMapCreated(reflectionMap) {\n this.reflectionMaps[reflectionMap.id] = reflectionMap;\n this.scene._lightsState.addReflectionMap(reflectionMap._state);\n this._needRecompile = true;\n }\n\n _sectionPlaneDestroyed(sectionPlane) {\n delete this.sectionPlanes[sectionPlane.id];\n this.scene._sectionPlanesState.removeSectionPlane(sectionPlane._state);\n this.scene.fire(\"sectionPlaneDestroyed\", sectionPlane, true /* Don't retain event */);\n this._needRecompile = true;\n }\n\n _bitmapDestroyed(bitmap) {\n delete this.bitmaps[bitmap.id];\n this.scene.fire(\"bitmapDestroyed\", bitmap, true /* Don't retain event */);\n }\n\n _lineSetDestroyed(lineSet) {\n delete this.lineSets[lineSet.id];\n this.scene.fire(\"lineSetDestroyed\", lineSet, true /* Don't retain event */);\n }\n\n _lightDestroyed(light) {\n delete this.lights[light.id];\n this.scene._lightsState.removeLight(light._state);\n this._needRecompile = true;\n }\n\n _lightMapDestroyed(lightMap) {\n delete this.lightMaps[lightMap.id];\n this.scene._lightsState.removeLightMap(lightMap._state);\n this._needRecompile = true;\n }\n\n _reflectionMapDestroyed(reflectionMap) {\n delete this.reflectionMaps[reflectionMap.id];\n this.scene._lightsState.removeReflectionMap(reflectionMap._state);\n this._needRecompile = true;\n }\n\n _registerModel(entity) {\n this.models[entity.id] = entity;\n this._modelIds = null; // Lazy regenerate\n }\n\n _deregisterModel(entity) {\n const modelId = entity.id;\n delete this.models[modelId];\n this._modelIds = null; // Lazy regenerate\n this.fire(\"modelUnloaded\", modelId);\n }\n\n _registerObject(entity) {\n this.objects[entity.id] = entity;\n this._numObjects++;\n this._objectIds = null; // Lazy regenerate\n }\n\n _deregisterObject(entity) {\n delete this.objects[entity.id];\n this._numObjects--;\n this._objectIds = null; // Lazy regenerate\n }\n\n _objectVisibilityUpdated(entity, notify = true) {\n if (entity.visible) {\n this.visibleObjects[entity.id] = entity;\n this._numVisibleObjects++;\n } else {\n delete this.visibleObjects[entity.id];\n this._numVisibleObjects--;\n }\n this._visibleObjectIds = null; // Lazy regenerate\n if (notify) {\n this.fire(\"objectVisibility\", entity, true);\n }\n }\n\n _deRegisterVisibleObject(entity) {\n delete this.visibleObjects[entity.id];\n this._numVisibleObjects--;\n this._visibleObjectIds = null; // Lazy regenerate\n }\n\n _objectXRayedUpdated(entity, notify = true) {\n if (entity.xrayed) {\n this.xrayedObjects[entity.id] = entity;\n this._numXRayedObjects++;\n } else {\n delete this.xrayedObjects[entity.id];\n this._numXRayedObjects--;\n }\n this._xrayedObjectIds = null; // Lazy regenerate\n if (notify) {\n this.fire(\"objectXRayed\", entity, true);\n }\n }\n\n _deRegisterXRayedObject(entity) {\n delete this.xrayedObjects[entity.id];\n this._numXRayedObjects--;\n this._xrayedObjectIds = null; // Lazy regenerate\n }\n\n _objectHighlightedUpdated(entity) {\n if (entity.highlighted) {\n this.highlightedObjects[entity.id] = entity;\n this._numHighlightedObjects++;\n } else {\n delete this.highlightedObjects[entity.id];\n this._numHighlightedObjects--;\n }\n this._highlightedObjectIds = null; // Lazy regenerate\n }\n\n _deRegisterHighlightedObject(entity) {\n delete this.highlightedObjects[entity.id];\n this._numHighlightedObjects--;\n this._highlightedObjectIds = null; // Lazy regenerate\n }\n\n _objectSelectedUpdated(entity, notify = true) {\n if (entity.selected) {\n this.selectedObjects[entity.id] = entity;\n this._numSelectedObjects++;\n } else {\n delete this.selectedObjects[entity.id];\n this._numSelectedObjects--;\n }\n this._selectedObjectIds = null; // Lazy regenerate\n if (notify) {\n this.fire(\"objectSelected\", entity, true);\n }\n }\n\n _deRegisterSelectedObject(entity) {\n delete this.selectedObjects[entity.id];\n this._numSelectedObjects--;\n this._selectedObjectIds = null; // Lazy regenerate\n }\n\n\n _objectColorizeUpdated(entity, colorized) {\n if (colorized) {\n this.colorizedObjects[entity.id] = entity;\n this._numColorizedObjects++;\n } else {\n delete this.colorizedObjects[entity.id];\n this._numColorizedObjects--;\n }\n this._colorizedObjectIds = null; // Lazy regenerate\n }\n\n _deRegisterColorizedObject(entity) {\n delete this.colorizedObjects[entity.id];\n this._numColorizedObjects--;\n this._colorizedObjectIds = null; // Lazy regenerate\n }\n\n _objectOpacityUpdated(entity, opacityUpdated) {\n if (opacityUpdated) {\n this.opacityObjects[entity.id] = entity;\n this._numOpacityObjects++;\n } else {\n delete this.opacityObjects[entity.id];\n this._numOpacityObjects--;\n }\n this._opacityObjectIds = null; // Lazy regenerate\n }\n\n _deRegisterOpacityObject(entity) {\n delete this.opacityObjects[entity.id];\n this._numOpacityObjects--;\n this._opacityObjectIds = null; // Lazy regenerate\n }\n\n _objectOffsetUpdated(entity, offset) {\n if (!offset || offset[0] === 0 && offset[1] === 0 && offset[2] === 0) {\n this.offsetObjects[entity.id] = entity;\n this._numOffsetObjects++;\n } else {\n delete this.offsetObjects[entity.id];\n this._numOffsetObjects--;\n }\n this._offsetObjectIds = null; // Lazy regenerate\n }\n\n _deRegisterOffsetObject(entity) {\n delete this.offsetObjects[entity.id];\n this._numOffsetObjects--;\n this._offsetObjectIds = null; // Lazy regenerate\n }\n\n _webglContextLost() {\n // this.loading++;\n this.canvas.spinner.processes++;\n for (const id in this.components) {\n if (this.components.hasOwnProperty(id)) {\n const component = this.components[id];\n if (component._webglContextLost) {\n component._webglContextLost();\n }\n }\n }\n this._renderer.webglContextLost();\n }\n\n _webglContextRestored() {\n const gl = this.canvas.gl;\n for (const id in this.components) {\n if (this.components.hasOwnProperty(id)) {\n const component = this.components[id];\n if (component._webglContextRestored) {\n component._webglContextRestored(gl);\n }\n }\n }\n this._renderer.webglContextRestored(gl);\n //this.loading--;\n this.canvas.spinner.processes--;\n }\n\n /**\n * Returns the capabilities of this Scene.\n *\n * @private\n * @returns {{astcSupported: boolean, etc1Supported: boolean, pvrtcSupported: boolean, etc2Supported: boolean, dxtSupported: boolean, bptcSupported: boolean}}\n */\n get capabilities() {\n return this._renderer.capabilities;\n }\n\n /**\n * Whether {@link Entity#offset} is enabled.\n *\n * This is set via the {@link Viewer} constructor and is ````false```` by default.\n *\n * @returns {Boolean} True if {@link Entity#offset} is enabled.\n */\n get entityOffsetsEnabled() {\n return this._entityOffsetsEnabled;\n }\n\n /**\n * Whether precision surface picking is enabled.\n *\n * This is set via the {@link Viewer} constructor and is ````false```` by default.\n *\n * The ````pickSurfacePrecision```` option for ````Scene#pick```` only works if this is set ````true````.\n *\n * Note that when ````true````, this configuration will increase the amount of browser memory used by the Viewer.\n *\n * @returns {Boolean} True if precision picking is enabled.\n */\n get pickSurfacePrecisionEnabled() {\n return false; // Removed\n }\n\n /**\n * Whether logarithmic depth buffer is enabled.\n *\n * This is set via the {@link Viewer} constructor and is ````false```` by default.\n *\n * @returns {Boolean} True if logarithmic depth buffer is enabled.\n */\n get logarithmicDepthBufferEnabled() {\n return this._logarithmicDepthBufferEnabled;\n }\n\n /**\n * Sets the number of {@link SectionPlane}s for which this Scene pre-caches resources.\n *\n * This property enhances the efficiency of SectionPlane creation by proactively allocating and caching Viewer resources for a specified quantity\n * of SectionPlanes. Introducing this parameter streamlines the initial creation speed of SectionPlanes, particularly up to the designated quantity. This parameter internally\n * configures renderer logic for the specified number of SectionPlanes, eliminating the need for setting up logic with each SectionPlane creation and thereby enhancing\n * responsiveness. It is important to consider that each SectionPlane impacts rendering performance, so it is recommended to set this value to a quantity that aligns with\n * your expected usage.\n *\n * Default is ````0````.\n */\n set numCachedSectionPlanes(numCachedSectionPlanes) {\n numCachedSectionPlanes = numCachedSectionPlanes || 0;\n if (this._sectionPlanesState.getNumCachedSectionPlanes() !== numCachedSectionPlanes) {\n this._sectionPlanesState.setNumCachedSectionPlanes(numCachedSectionPlanes);\n this._needRecompile = true;\n this.glRedraw();\n }\n }\n\n /**\n * Gets the number of {@link SectionPlane}s for which this Scene pre-caches resources.\n *\n * This property enhances the efficiency of SectionPlane creation by proactively allocating and caching Viewer resources for a specified quantity\n * of SectionPlanes. Introducing this parameter streamlines the initial creation speed of SectionPlanes, particularly up to the designated quantity. This parameter internally\n * configures renderer logic for the specified number of SectionPlanes, eliminating the need for setting up logic with each SectionPlane creation and thereby enhancing\n * responsiveness. It is important to consider that each SectionPlane impacts rendering performance, so it is recommended to set this value to a quantity that aligns with\n * your expected usage.\n *\n * Default is ````0````.\n *\n * @returns {number} The number of {@link SectionPlane}s for which this Scene pre-caches resources.\n */\n get numCachedSectionPlanes() {\n return this._sectionPlanesState.getNumCachedSectionPlanes();\n }\n\n /**\n * Sets whether physically-based rendering is enabled.\n *\n * Default is ````false````.\n */\n set pbrEnabled(pbrEnabled) {\n this._pbrEnabled = !!pbrEnabled;\n this.glRedraw();\n }\n\n /**\n * Gets whether physically-based rendering is enabled.\n *\n * Default is ````false````.\n *\n * @returns {Boolean} True if quality rendering is enabled.\n */\n get pbrEnabled() {\n return this._pbrEnabled;\n }\n\n /**\n * Sets whether data texture scene representation (DTX) is enabled for the {@link Scene}.\n *\n * Even when enabled, DTX will only work if supported.\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n set dtxEnabled(value) {\n value = !!value;\n if (this._dtxEnabled === value) {\n return;\n }\n this._dtxEnabled = value;\n }\n\n /**\n * Gets whether data texture-based scene representation (DTX) is enabled for the {@link Scene}.\n *\n * Even when enabled, DTX will only apply if supported.\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n get dtxEnabled() {\n return this._dtxEnabled;\n }\n\n /**\n * Sets whether basic color texture rendering is enabled.\n *\n * Default is ````true````.\n *\n * @returns {Boolean} True if basic color texture rendering is enabled.\n */\n set colorTextureEnabled(colorTextureEnabled) {\n this._colorTextureEnabled = !!colorTextureEnabled;\n this.glRedraw();\n }\n\n /**\n * Gets whether basic color texture rendering is enabled.\n *\n * Default is ````true````.\n *\n * @returns {Boolean} True if basic color texture rendering is enabled.\n */\n get colorTextureEnabled() {\n return this._colorTextureEnabled;\n }\n\n /**\n * Performs an occlusion test on all {@link Marker}s in this {@link Scene}.\n *\n * Sets each {@link Marker#visible} ````true```` if the Marker is currently not occluded by any opaque {@link Entity}s\n * in the Scene, or ````false```` if an Entity is occluding it.\n */\n doOcclusionTest() {\n if (this._needRecompile) {\n this._recompile();\n this._needRecompile = false;\n }\n this._renderer.doOcclusionTest();\n }\n\n /**\n * Renders a single frame of this Scene.\n *\n * The Scene will periodically render itself after any updates, but you can call this method to force a render\n * if required.\n *\n * @param {Boolean} [forceRender=false] Forces a render when true, otherwise only renders if something has changed in this Scene\n * since the last render.\n */\n render(forceRender) {\n\n if (forceRender) {\n core.runTasks();\n }\n\n const renderEvent = {\n sceneId: null,\n pass: 0\n };\n\n if (this._needRecompile) {\n this._recompile();\n this._renderer.imageDirty();\n this._needRecompile = false;\n }\n\n if (!forceRender && !this._renderer.needsRender()) {\n return;\n }\n\n renderEvent.sceneId = this.id;\n\n const passes = this._passes;\n const clearEachPass = this._clearEachPass;\n let pass;\n let clear;\n\n for (pass = 0; pass < passes; pass++) {\n\n renderEvent.pass = pass;\n\n /**\n * Fired when about to render a frame for a Scene.\n *\n * @event rendering\n * @param {String} sceneID The ID of this Scene.\n * @param {Number} pass Index of the pass we are about to render (see {@link Scene#passes}).\n */\n this.fire(\"rendering\", renderEvent, true);\n\n clear = clearEachPass || (pass === 0);\n\n this._renderer.render({pass: pass, clear: clear, force: forceRender});\n\n /**\n * Fired when we have just rendered a frame for a Scene.\n *\n * @event rendering\n * @param {String} sceneID The ID of this Scene.\n * @param {Number} pass Index of the pass we rendered (see {@link Scene#passes}).\n */\n this.fire(\"rendered\", renderEvent, true);\n }\n\n this._saveAmbientColor();\n }\n\n\n /**\n * @private\n */\n compile() {\n if (this._needRecompile) {\n this._recompile();\n this._renderer.imageDirty();\n this._needRecompile = false;\n }\n }\n\n _recompile() {\n for (const id in this._compilables) {\n if (this._compilables.hasOwnProperty(id)) {\n this._compilables[id].compile();\n }\n }\n this._renderer.shadowsDirty();\n this.fire(\"compile\", this, true);\n }\n\n _saveAmbientColor() {\n const canvas = this.canvas;\n if (!canvas.transparent && !canvas.backgroundImage && !canvas.backgroundColor) {\n const ambientColorIntensity = this._lightsState.getAmbientColorAndIntensity();\n if (!this._lastAmbientColor ||\n this._lastAmbientColor[0] !== ambientColorIntensity[0] ||\n this._lastAmbientColor[1] !== ambientColorIntensity[1] ||\n this._lastAmbientColor[2] !== ambientColorIntensity[2] ||\n this._lastAmbientColor[3] !== ambientColorIntensity[3]) {\n canvas.backgroundColor = ambientColorIntensity;\n if (!this._lastAmbientColor) {\n this._lastAmbientColor = math.vec4([0, 0, 0, 1]);\n }\n this._lastAmbientColor.set(ambientColorIntensity);\n }\n } else {\n this._lastAmbientColor = null;\n }\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#models}.\n *\n * @type {String[]}\n */\n get modelIds() {\n if (!this._modelIds) {\n this._modelIds = Object.keys(this.models);\n }\n return this._modelIds;\n }\n\n /**\n * Gets the number of {@link Entity}s in {@link Scene#objects}.\n *\n * @type {Number}\n */\n get numObjects() {\n return this._numObjects;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#objects}.\n *\n * @type {String[]}\n */\n get objectIds() {\n if (!this._objectIds) {\n this._objectIds = Object.keys(this.objects);\n }\n return this._objectIds;\n }\n\n /**\n * Gets the number of {@link Entity}s in {@link Scene#visibleObjects}.\n *\n * @type {Number}\n */\n get numVisibleObjects() {\n return this._numVisibleObjects;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#visibleObjects}.\n *\n * @type {String[]}\n */\n get visibleObjectIds() {\n if (!this._visibleObjectIds) {\n this._visibleObjectIds = Object.keys(this.visibleObjects);\n }\n return this._visibleObjectIds;\n }\n\n /**\n * Gets the number of {@link Entity}s in {@link Scene#xrayedObjects}.\n *\n * @type {Number}\n */\n get numXRayedObjects() {\n return this._numXRayedObjects;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#xrayedObjects}.\n *\n * @type {String[]}\n */\n get xrayedObjectIds() {\n if (!this._xrayedObjectIds) {\n this._xrayedObjectIds = Object.keys(this.xrayedObjects);\n }\n return this._xrayedObjectIds;\n }\n\n /**\n * Gets the number of {@link Entity}s in {@link Scene#highlightedObjects}.\n *\n * @type {Number}\n */\n get numHighlightedObjects() {\n return this._numHighlightedObjects;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#highlightedObjects}.\n *\n * @type {String[]}\n */\n get highlightedObjectIds() {\n if (!this._highlightedObjectIds) {\n this._highlightedObjectIds = Object.keys(this.highlightedObjects);\n }\n return this._highlightedObjectIds;\n }\n\n /**\n * Gets the number of {@link Entity}s in {@link Scene#selectedObjects}.\n *\n * @type {Number}\n */\n get numSelectedObjects() {\n return this._numSelectedObjects;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#selectedObjects}.\n *\n * @type {String[]}\n */\n get selectedObjectIds() {\n if (!this._selectedObjectIds) {\n this._selectedObjectIds = Object.keys(this.selectedObjects);\n }\n return this._selectedObjectIds;\n }\n\n /**\n * Gets the number of {@link Entity}s in {@link Scene#colorizedObjects}.\n *\n * @type {Number}\n */\n get numColorizedObjects() {\n return this._numColorizedObjects;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#colorizedObjects}.\n *\n * @type {String[]}\n */\n get colorizedObjectIds() {\n if (!this._colorizedObjectIds) {\n this._colorizedObjectIds = Object.keys(this.colorizedObjects);\n }\n return this._colorizedObjectIds;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#opacityObjects}.\n *\n * @type {String[]}\n */\n get opacityObjectIds() {\n if (!this._opacityObjectIds) {\n this._opacityObjectIds = Object.keys(this.opacityObjects);\n }\n return this._opacityObjectIds;\n }\n\n /**\n * Gets the IDs of the {@link Entity}s in {@link Scene#offsetObjects}.\n *\n * @type {String[]}\n */\n get offsetObjectIds() {\n if (!this._offsetObjectIds) {\n this._offsetObjectIds = Object.keys(this.offsetObjects);\n }\n return this._offsetObjectIds;\n }\n\n /**\n * Sets the number of \"ticks\" that happen between each render or this Scene.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n set ticksPerRender(value) {\n if (value === undefined || value === null) {\n value = 1;\n } else if (!utils.isNumeric(value) || value <= 0) {\n this.error(\"Unsupported value for 'ticksPerRender': '\" + value +\n \"' - should be an integer greater than zero.\");\n value = 1;\n }\n if (value === this._ticksPerRender) {\n return;\n }\n this._ticksPerRender = value;\n }\n\n /**\n * Gets the number of \"ticks\" that happen between each render or this Scene.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n get ticksPerRender() {\n return this._ticksPerRender;\n }\n\n /**\n * Sets the number of \"ticks\" that happen between occlusion testing for {@link Marker}s.\n *\n * Default value is ````20````.\n *\n * @type {Number}\n */\n set ticksPerOcclusionTest(value) {\n if (value === undefined || value === null) {\n value = 20;\n } else if (!utils.isNumeric(value) || value <= 0) {\n this.error(\"Unsupported value for 'ticksPerOcclusionTest': '\" + value +\n \"' - should be an integer greater than zero.\");\n value = 20;\n }\n if (value === this._ticksPerOcclusionTest) {\n return;\n }\n this._ticksPerOcclusionTest = value;\n }\n\n /**\n * Gets the number of \"ticks\" that happen between each render of this Scene.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n get ticksPerOcclusionTest() {\n return this._ticksPerOcclusionTest;\n }\n\n /**\n * Sets the number of times this Scene renders per frame.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n set passes(value) {\n if (value === undefined || value === null) {\n value = 1;\n } else if (!utils.isNumeric(value) || value <= 0) {\n this.error(\"Unsupported value for 'passes': '\" + value +\n \"' - should be an integer greater than zero.\");\n value = 1;\n }\n if (value === this._passes) {\n return;\n }\n this._passes = value;\n this.glRedraw();\n }\n\n /**\n * Gets the number of times this Scene renders per frame.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n get passes() {\n return this._passes;\n }\n\n /**\n * When {@link Scene#passes} is greater than ````1````, indicates whether or not to clear the canvas before each pass (````true````) or just before the first pass (````false````).\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n set clearEachPass(value) {\n value = !!value;\n if (value === this._clearEachPass) {\n return;\n }\n this._clearEachPass = value;\n this.glRedraw();\n }\n\n /**\n * When {@link Scene#passes} is greater than ````1````, indicates whether or not to clear the canvas before each pass (````true````) or just before the first pass (````false````).\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n get clearEachPass() {\n return this._clearEachPass;\n }\n\n /**\n * Sets whether or not {@link Scene} should expect all {@link Texture}s and colors to have pre-multiplied gamma.\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n set gammaInput(value) {\n value = value !== false;\n if (value === this._renderer.gammaInput) {\n return;\n }\n this._renderer.gammaInput = value;\n this._needRecompile = true;\n this.glRedraw();\n }\n\n /**\n * Gets whether or not {@link Scene} should expect all {@link Texture}s and colors to have pre-multiplied gamma.\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n get gammaInput() {\n return this._renderer.gammaInput;\n }\n\n /**\n * Sets whether or not to render pixels with pre-multiplied gama.\n *\n * Default value is ````false````.\n *\n * @type {Boolean}\n */\n set gammaOutput(value) {\n value = !!value;\n if (value === this._renderer.gammaOutput) {\n return;\n }\n this._renderer.gammaOutput = value;\n this._needRecompile = true;\n this.glRedraw();\n }\n\n /**\n * Gets whether or not to render pixels with pre-multiplied gama.\n *\n * Default value is ````true````.\n *\n * @type {Boolean}\n */\n get gammaOutput() {\n return this._renderer.gammaOutput;\n }\n\n /**\n * Sets the gamma factor to use when {@link Scene#gammaOutput} is set true.\n *\n * Default value is ````2.2````.\n *\n * @type {Number}\n */\n set gammaFactor(value) {\n value = (value === undefined || value === null) ? 2.2 : value;\n if (value === this._renderer.gammaFactor) {\n return;\n }\n this._renderer.gammaFactor = value;\n this.glRedraw();\n }\n\n /**\n * Gets the gamma factor to use when {@link Scene#gammaOutput} is set true.\n *\n * Default value is ````2.2````.\n *\n * @type {Number}\n */\n get gammaFactor() {\n return this._renderer.gammaFactor;\n }\n\n /**\n * Gets the default {@link Geometry} for this Scene, which is a {@link ReadableGeometry} with a unit-sized box shape.\n *\n * Has {@link ReadableGeometry#id} set to \"default.geometry\".\n *\n * {@link Mesh}s in this Scene have {@link Mesh#geometry} set to this {@link ReadableGeometry} by default.\n *\n * @type {ReadableGeometry}\n */\n get geometry() {\n return this.components[\"default.geometry\"] || buildBoxGeometry(ReadableGeometry);\n }\n\n /**\n * Gets the default {@link Material} for this Scene, which is a {@link PhongMaterial}.\n *\n * Has {@link PhongMaterial#id} set to \"default.material\".\n *\n * {@link Mesh}s in this Scene have {@link Mesh#material} set to this {@link PhongMaterial} by default.\n *\n * @type {PhongMaterial}\n */\n get material() {\n return this.components[\"default.material\"] || new PhongMaterial(this, {\n id: \"default.material\",\n emissive: [0.4, 0.4, 0.4], // Visible by default on geometry without normals\n dontClear: true\n });\n }\n\n /**\n * Gets the default xraying {@link EmphasisMaterial} for this Scene.\n *\n * Has {@link EmphasisMaterial#id} set to \"default.xrayMaterial\".\n *\n * {@link Mesh}s in this Scene have {@link Mesh#xrayMaterial} set to this {@link EmphasisMaterial} by default.\n *\n * {@link Mesh}s are xrayed while {@link Mesh#xrayed} is ````true````.\n *\n * @type {EmphasisMaterial}\n */\n get xrayMaterial() {\n return this.components[\"default.xrayMaterial\"] || new EmphasisMaterial(this, {\n id: \"default.xrayMaterial\",\n preset: \"sepia\",\n dontClear: true\n });\n }\n\n /**\n * Gets the default highlight {@link EmphasisMaterial} for this Scene.\n *\n * Has {@link EmphasisMaterial#id} set to \"default.highlightMaterial\".\n *\n * {@link Mesh}s in this Scene have {@link Mesh#highlightMaterial} set to this {@link EmphasisMaterial} by default.\n *\n * {@link Mesh}s are highlighted while {@link Mesh#highlighted} is ````true````.\n *\n * @type {EmphasisMaterial}\n */\n get highlightMaterial() {\n return this.components[\"default.highlightMaterial\"] || new EmphasisMaterial(this, {\n id: \"default.highlightMaterial\",\n preset: \"yellowHighlight\",\n dontClear: true\n });\n }\n\n /**\n * Gets the default selection {@link EmphasisMaterial} for this Scene.\n *\n * Has {@link EmphasisMaterial#id} set to \"default.selectedMaterial\".\n *\n * {@link Mesh}s in this Scene have {@link Mesh#highlightMaterial} set to this {@link EmphasisMaterial} by default.\n *\n * {@link Mesh}s are highlighted while {@link Mesh#highlighted} is ````true````.\n *\n * @type {EmphasisMaterial}\n */\n get selectedMaterial() {\n return this.components[\"default.selectedMaterial\"] || new EmphasisMaterial(this, {\n id: \"default.selectedMaterial\",\n preset: \"greenSelected\",\n dontClear: true\n });\n }\n\n /**\n * Gets the default {@link EdgeMaterial} for this Scene.\n *\n * Has {@link EdgeMaterial#id} set to \"default.edgeMaterial\".\n *\n * {@link Mesh}s in this Scene have {@link Mesh#edgeMaterial} set to this {@link EdgeMaterial} by default.\n *\n * {@link Mesh}s have their edges emphasized while {@link Mesh#edges} is ````true````.\n *\n * @type {EdgeMaterial}\n */\n get edgeMaterial() {\n return this.components[\"default.edgeMaterial\"] || new EdgeMaterial(this, {\n id: \"default.edgeMaterial\",\n preset: \"default\",\n edgeColor: [0.0, 0.0, 0.0],\n edgeAlpha: 1.0,\n edgeWidth: 1,\n dontClear: true\n });\n }\n\n /**\n * Gets the {@link PointsMaterial} for this Scene.\n *\n * @type {PointsMaterial}\n */\n get pointsMaterial() {\n return this.components[\"default.pointsMaterial\"] || new PointsMaterial(this, {\n id: \"default.pointsMaterial\",\n preset: \"default\",\n dontClear: true\n });\n }\n\n /**\n * Gets the {@link LinesMaterial} for this Scene.\n *\n * @type {LinesMaterial}\n */\n get linesMaterial() {\n return this.components[\"default.linesMaterial\"] || new LinesMaterial(this, {\n id: \"default.linesMaterial\",\n preset: \"default\",\n dontClear: true\n });\n }\n\n /**\n * Gets the {@link Viewport} for this Scene.\n *\n * @type Viewport\n */\n get viewport() {\n return this._viewport;\n }\n\n /**\n * Gets the {@link Camera} for this Scene.\n *\n * @type {Camera}\n */\n get camera() {\n return this._camera;\n }\n\n /**\n * Gets the World-space 3D center of this Scene.\n *\n *@type {Number[]}\n */\n get center() {\n if (this._aabbDirty || !this._center) {\n if (!this._center || !this._center) {\n this._center = math.vec3();\n }\n const aabb = this.aabb;\n this._center[0] = (aabb[0] + aabb[3]) / 2;\n this._center[1] = (aabb[1] + aabb[4]) / 2;\n this._center[2] = (aabb[2] + aabb[5]) / 2;\n }\n return this._center;\n }\n\n /**\n * Gets the World-space axis-aligned 3D boundary (AABB) of this Scene.\n *\n * The AABB is represented by a six-element Float64Array containing the min/max extents of the axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````.\n *\n * When the Scene has no content, will be ````[-100,-100,-100,100,100,100]````.\n *\n * @type {Number[]}\n */\n get aabb() {\n if (this._aabbDirty) {\n if (!this._aabb) {\n this._aabb = math.AABB3();\n }\n let xmin = math.MAX_DOUBLE;\n let ymin = math.MAX_DOUBLE;\n let zmin = math.MAX_DOUBLE;\n let xmax = math.MIN_DOUBLE;\n let ymax = math.MIN_DOUBLE;\n let zmax = math.MIN_DOUBLE;\n let aabb;\n const collidables = this._collidables;\n let collidable;\n let valid = false;\n for (const collidableId in collidables) {\n if (collidables.hasOwnProperty(collidableId)) {\n collidable = collidables[collidableId];\n if (collidable.collidable === false) {\n continue;\n }\n aabb = collidable.aabb;\n if (aabb[0] < xmin) {\n xmin = aabb[0];\n }\n if (aabb[1] < ymin) {\n ymin = aabb[1];\n }\n if (aabb[2] < zmin) {\n zmin = aabb[2];\n }\n if (aabb[3] > xmax) {\n xmax = aabb[3];\n }\n if (aabb[4] > ymax) {\n ymax = aabb[4];\n }\n if (aabb[5] > zmax) {\n zmax = aabb[5];\n }\n valid = true;\n }\n }\n if (!valid) {\n xmin = -100;\n ymin = -100;\n zmin = -100;\n xmax = 100;\n ymax = 100;\n zmax = 100;\n }\n this._aabb[0] = xmin;\n this._aabb[1] = ymin;\n this._aabb[2] = zmin;\n this._aabb[3] = xmax;\n this._aabb[4] = ymax;\n this._aabb[5] = zmax;\n this._aabbDirty = false;\n }\n return this._aabb;\n }\n\n _setAABBDirty() {\n //if (!this._aabbDirty) {\n this._aabbDirty = true;\n this.fire(\"boundary\");\n // }\n }\n\n /**\n * Attempts to pick an {@link Entity} in this Scene.\n *\n * Ignores {@link Entity}s with {@link Entity#pickable} set ````false````.\n *\n * When an {@link Entity} is picked, fires a \"pick\" event on the {@link Entity} with the pick result as parameters.\n *\n * Picking the {@link Entity} at the given canvas coordinates:\n\n * ````javascript\n * var pickResult = scene.pick({\n * canvasPos: [23, 131]\n * });\n *\n * if (pickResult) { // Picked an Entity\n * var entity = pickResult.entity;\n * }\n * ````\n *\n * Picking, with a ray cast through the canvas, hits an {@link Entity}:\n *\n * ````javascript\n * var pickResult = scene.pick({\n * pickSurface: true,\n * canvasPos: [23, 131]\n * });\n *\n * if (pickResult) { // Picked an Entity\n *\n * var entity = pickResult.entity;\n *\n * if (pickResult.primitive === \"triangle\") {\n *\n * // Picked a triangle on the entity surface\n *\n * var primIndex = pickResult.primIndex; // Position of triangle's first index in the picked Entity's Geometry's indices array\n * var indices = pickResult.indices; // UInt32Array containing the triangle's vertex indices\n * var localPos = pickResult.localPos; // Float64Array containing the picked Local-space position on the triangle\n * var worldPos = pickResult.worldPos; // Float64Array containing the picked World-space position on the triangle\n * var viewPos = pickResult.viewPos; // Float64Array containing the picked View-space position on the triangle\n * var bary = pickResult.bary; // Float64Array containing the picked barycentric position within the triangle\n * var worldNormal = pickResult.worldNormal; // Float64Array containing the interpolated World-space normal vector at the picked position on the triangle\n * var uv = pickResult.uv; // Float64Array containing the interpolated UV coordinates at the picked position on the triangle\n *\n * } else if (pickResult.worldPos && pickResult.worldNormal) {\n *\n * // Picked a point and normal on the entity surface\n *\n * var worldPos = pickResult.worldPos; // Float64Array containing the picked World-space position on the Entity surface\n * var worldNormal = pickResult.worldNormal; // Float64Array containing the picked World-space normal vector on the Entity Surface\n * }\n * }\n * ````\n *\n * Picking the {@link Entity} that intersects an arbitrarily-aligned World-space ray:\n *\n * ````javascript\n * var pickResult = scene.pick({\n * pickSurface: true, // Picking with arbitrarily-positioned ray\n * origin: [0,0,-5], // Ray origin\n * direction: [0,0,1] // Ray direction\n * });\n *\n * if (pickResult) { // Picked an Entity with the ray\n *\n * var entity = pickResult.entity;\n *\n * if (pickResult.primitive == \"triangle\") {\n *\n * // Picked a triangle on the entity surface\n *\n * var primitive = pickResult.primitive; // Type of primitive that was picked, usually \"triangles\"\n * var primIndex = pickResult.primIndex; // Position of triangle's first index in the picked Entity's Geometry's indices array\n * var indices = pickResult.indices; // UInt32Array containing the triangle's vertex indices\n * var localPos = pickResult.localPos; // Float64Array containing the picked Local-space position on the triangle\n * var worldPos = pickResult.worldPos; // Float64Array containing the picked World-space position on the triangle\n * var viewPos = pickResult.viewPos; // Float64Array containing the picked View-space position on the triangle\n * var bary = pickResult.bary; // Float64Array containing the picked barycentric position within the triangle\n * var worldNormal = pickResult.worldNormal; // Float64Array containing the interpolated World-space normal vector at the picked position on the triangle\n * var uv = pickResult.uv; // Float64Array containing the interpolated UV coordinates at the picked position on the triangle\n * var origin = pickResult.origin; // Float64Array containing the World-space ray origin\n * var direction = pickResult.direction; // Float64Array containing the World-space ray direction\n *\n * } else if (pickResult.worldPos && pickResult.worldNormal) {\n *\n * // Picked a point and normal on the entity surface\n *\n * var worldPos = pickResult.worldPos; // Float64Array containing the picked World-space position on the Entity surface\n * var worldNormal = pickResult.worldNormal; // Float64Array containing the picked World-space normal vector on the Entity Surface\n * }\n * }\n * ````\n *\n * @param {*} params Picking parameters.\n * @param {Boolean} [params.pickSurface=false] Whether to find the picked position on the surface of the Entity.\n * @param {Boolean} [params.pickSurfacePrecision=false] When picking an Entity surface position, indicates whether or not we want full-precision {@link PickResult#worldPos}. Only works when {@link Scene#pickSurfacePrecisionEnabled} is ````true````. If pick succeeds, the returned {@link PickResult} will have {@link PickResult#precision} set ````true````, to indicate that it contains full-precision surface pick results.\n * @param {Boolean} [params.pickSurfaceNormal=false] Whether to find the picked normal on the surface of the Entity. Only works if ````pickSurface```` is given.\n * @param {Number[]} [params.canvasPos] Canvas-space coordinates. When ray-picking, this will override the **origin** and ** direction** parameters and will cause the ray to be fired through the canvas at this position, directly along the negative View-space Z-axis.\n * @param {Number[]} [params.origin] World-space ray origin when ray-picking. Ignored when canvasPos given.\n * @param {Number[]} [params.direction] World-space ray direction when ray-picking. Also indicates the length of the ray. Ignored when canvasPos given.\n * @param {Number[]} [params.matrix] 4x4 transformation matrix to define the World-space ray origin and direction, as an alternative to ````origin```` and ````direction````.\n * @param {String[]} [params.includeEntities] IDs of {@link Entity}s to restrict picking to. When given, ignores {@link Entity}s whose IDs are not in this list.\n * @param {String[]} [params.excludeEntities] IDs of {@link Entity}s to ignore. When given, will pick *through* these {@link Entity}s, as if they were not there.\n * @param {Number} [params.snapRadius=30] The snap radius, in canvas pixels\n * @param {boolean} [params.snapToVertex=true] Whether to snap to vertex.\n * @param {boolean} [params.snapToEdge=true] Whether to snap to edge.\n * @param {PickResult} [pickResult] Holds the results of the pick attempt. Will use the Scene's singleton PickResult if you don't supply your own.\n * @returns {PickResult} Holds results of the pick attempt, returned when an {@link Entity} is picked, else null. See method comments for description.\n */\n pick(params, pickResult) {\n\n if (this.canvas.boundary[2] === 0 || this.canvas.boundary[3] === 0) {\n this.error(\"Picking not allowed while canvas has zero width or height\");\n return null;\n }\n\n params = params || {};\n\n params.pickSurface = params.pickSurface || params.rayPick; // Backwards compatibility\n\n if (!params.canvasPos && !params.matrix && (!params.origin || !params.direction)) {\n this.warn(\"picking without canvasPos, matrix, or ray origin and direction\");\n }\n\n const includeEntities = params.includeEntities || params.include; // Backwards compat\n if (includeEntities) {\n params.includeEntityIds = getEntityIDMap(this, includeEntities);\n }\n\n const excludeEntities = params.excludeEntities || params.exclude; // Backwards compat\n if (excludeEntities) {\n params.excludeEntityIds = getEntityIDMap(this, excludeEntities);\n }\n\n if (this._needRecompile) {\n this._recompile();\n this._renderer.imageDirty();\n this._needRecompile = false;\n }\n\n if (params.snapToEdge || params.snapToVertex) {\n pickResult = this._renderer.snapPick(\n params.canvasPos,\n params.snapRadius || 30,\n params.snapToVertex,\n params.snapToEdge,\n pickResult\n );\n } else {\n pickResult = this._renderer.pick(params, pickResult);\n }\n\n if (pickResult) {\n if (pickResult.entity && pickResult.entity.fire) {\n pickResult.entity.fire(\"picked\", pickResult); // TODO: SceneModelEntity doesn't fire events\n }\n }\n\n return pickResult;\n }\n\n /**\n * @param {Object} params Picking parameters.\n * @param {Number[]} [params.canvasPos] Canvas-space coordinates. When ray-picking, this will override the **origin** and ** direction** parameters and will cause the ray to be fired through the canvas at this position, directly along the negative View-space Z-axis.\n * @param {Number} [params.snapRadius=30] The snap radius, in canvas pixels\n * @param {boolean} [params.snapToVertex=true] Whether to snap to vertex.\n * @param {boolean} [params.snapToEdge=true] Whether to snap to edge.\n * @deprecated\n */\n snapPick(params) {\n if (undefined === this._warnSnapPickDeprecated) {\n this._warnSnapPickDeprecated = true;\n this.warn(\"Scene.snapPick() is deprecated since v2.4.2 - use Scene.pick() instead\");\n }\n return this._renderer.snapPick(\n params.canvasPos,\n params.snapRadius || 30,\n params.snapToVertex,\n params.snapToEdge,\n );\n }\n\n /**\n * Destroys all non-default {@link Component}s in this Scene.\n */\n clear() {\n var component;\n for (const id in this.components) {\n if (this.components.hasOwnProperty(id)) {\n component = this.components[id];\n if (!component._dontClear) { // Don't destroy components like Camera, Input, Viewport etc.\n component.destroy();\n }\n }\n }\n }\n\n /**\n * Destroys all {@link Light}s in this Scene..\n */\n clearLights() {\n const ids = Object.keys(this.lights);\n for (let i = 0, len = ids.length; i < len; i++) {\n this.lights[ids[i]].destroy();\n }\n }\n\n /**\n * Destroys all {@link SectionPlane}s in this Scene.\n */\n clearSectionPlanes() {\n const ids = Object.keys(this.sectionPlanes);\n for (let i = 0, len = ids.length; i < len; i++) {\n this.sectionPlanes[ids[i]].destroy();\n }\n }\n\n /**\n * Destroys all {@link Line}s in this Scene.\n */\n clearBitmaps() {\n const ids = Object.keys(this.bitmaps);\n for (let i = 0, len = ids.length; i < len; i++) {\n this.bitmaps[ids[i]].destroy();\n }\n }\n\n\n /**\n * Destroys all {@link Line}s in this Scene.\n */\n clearLines() {\n const ids = Object.keys(this.lineSets);\n for (let i = 0, len = ids.length; i < len; i++) {\n this.lineSets[ids[i]].destroy();\n }\n }\n\n /**\n * Gets the collective axis-aligned boundary (AABB) of a batch of {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * Each {@link Entity} on which {@link Entity#isObject} is registered by {@link Entity#id} in {@link Scene#visibleObjects}.\n *\n * Each {@link Entity} is only included in the AABB when {@link Entity#collidable} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @returns {[Number, Number, Number, Number, Number, Number]} An axis-aligned World-space bounding box, given as elements ````[xmin, ymin, zmin, xmax, ymax, zmax]````.\n */\n getAABB(ids) {\n if (ids === undefined) {\n return this.aabb;\n }\n if (utils.isString(ids)) {\n const entity = this.objects[ids];\n if (entity && entity.aabb) { // A Component subclass with an AABB\n return entity.aabb;\n }\n ids = [ids]; // Must be an entity type\n }\n if (ids.length === 0) {\n return this.aabb;\n }\n let xmin = math.MAX_DOUBLE;\n let ymin = math.MAX_DOUBLE;\n let zmin = math.MAX_DOUBLE;\n let xmax = math.MIN_DOUBLE;\n let ymax = math.MIN_DOUBLE;\n let zmax = math.MIN_DOUBLE;\n let valid;\n this.withObjects(ids, entity => {\n if (entity.collidable) {\n const aabb = entity.aabb;\n if (aabb[0] < xmin) {\n xmin = aabb[0];\n }\n if (aabb[1] < ymin) {\n ymin = aabb[1];\n }\n if (aabb[2] < zmin) {\n zmin = aabb[2];\n }\n if (aabb[3] > xmax) {\n xmax = aabb[3];\n }\n if (aabb[4] > ymax) {\n ymax = aabb[4];\n }\n if (aabb[5] > zmax) {\n zmax = aabb[5];\n }\n valid = true;\n }\n }\n );\n if (valid) {\n const aabb2 = math.AABB3();\n aabb2[0] = xmin;\n aabb2[1] = ymin;\n aabb2[2] = zmin;\n aabb2[3] = xmax;\n aabb2[4] = ymax;\n aabb2[5] = zmax;\n return aabb2;\n } else {\n return this.aabb; // Scene AABB\n }\n }\n\n /**\n * Batch-updates {@link Entity#visible} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * Each {@link Entity} on which both {@link Entity#isObject} and {@link Entity#visible} are ````true```` is\n * registered by {@link Entity#id} in {@link Scene#visibleObjects}.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} visible Whether or not to set visible.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsVisible(ids, visible) {\n return this.withObjects(ids, entity => {\n const changed = (entity.visible !== visible);\n entity.visible = visible;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#collidable} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} collidable Whether or not to set collidable.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsCollidable(ids, collidable) {\n return this.withObjects(ids, entity => {\n const changed = (entity.collidable !== collidable);\n entity.collidable = collidable;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#culled} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} culled Whether or not to cull.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsCulled(ids, culled) {\n return this.withObjects(ids, entity => {\n const changed = (entity.culled !== culled);\n entity.culled = culled;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#selected} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * Each {@link Entity} on which both {@link Entity#isObject} and {@link Entity#selected} are ````true```` is\n * registered by {@link Entity#id} in {@link Scene#selectedObjects}.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} selected Whether or not to select.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsSelected(ids, selected) {\n return this.withObjects(ids, entity => {\n const changed = (entity.selected !== selected);\n entity.selected = selected;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#highlighted} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * Each {@link Entity} on which both {@link Entity#isObject} and {@link Entity#highlighted} are ````true```` is\n * registered by {@link Entity#id} in {@link Scene#highlightedObjects}.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} highlighted Whether or not to highlight.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsHighlighted(ids, highlighted) {\n return this.withObjects(ids, entity => {\n const changed = (entity.highlighted !== highlighted);\n entity.highlighted = highlighted;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#xrayed} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} xrayed Whether or not to xray.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsXRayed(ids, xrayed) {\n return this.withObjects(ids, entity => {\n const changed = (entity.xrayed !== xrayed);\n entity.xrayed = xrayed;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#edges} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} edges Whether or not to show edges.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsEdges(ids, edges) {\n return this.withObjects(ids, entity => {\n const changed = (entity.edges !== edges);\n entity.edges = edges;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#colorize} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Number[]} [colorize=(1,1,1)] RGB colorize factors, multiplied by the rendered pixel colors.\n * @returns {Boolean} True if any {@link Entity}s changed opacity, else false if all updates were redundant and not applied.\n */\n setObjectsColorized(ids, colorize) {\n return this.withObjects(ids, entity => {\n entity.colorize = colorize;\n });\n }\n\n /**\n * Batch-updates {@link Entity#opacity} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Number} [opacity=1.0] Opacity factor, multiplied by the rendered pixel alphas.\n * @returns {Boolean} True if any {@link Entity}s changed opacity, else false if all updates were redundant and not applied.\n */\n setObjectsOpacity(ids, opacity) {\n return this.withObjects(ids, entity => {\n const changed = (entity.opacity !== opacity);\n entity.opacity = opacity;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#pickable} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Boolean} pickable Whether or not to set pickable.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n setObjectsPickable(ids, pickable) {\n return this.withObjects(ids, entity => {\n const changed = (entity.pickable !== pickable);\n entity.pickable = pickable;\n return changed;\n });\n }\n\n /**\n * Batch-updates {@link Entity#offset} on {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Number[]} [offset] 3D offset vector.\n */\n setObjectsOffset(ids, offset) {\n this.withObjects(ids, entity => {\n entity.offset = offset;\n });\n }\n\n /**\n * Iterates with a callback over {@link Entity}s that represent objects.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````.\n *\n * @param {String[]} ids Array of {@link Entity#id} values.\n * @param {Function} callback Callback to execute on eacn {@link Entity}.\n * @returns {Boolean} True if any {@link Entity}s were updated, else false if all updates were redundant and not applied.\n */\n withObjects(ids, callback) {\n if (utils.isString(ids)) {\n ids = [ids];\n }\n let changed = false;\n for (let i = 0, len = ids.length; i < len; i++) {\n const id = ids[i];\n let entity = this.objects[id];\n if (entity) {\n changed = callback(entity) || changed;\n } else {\n const modelIds = this.modelIds;\n for (let i = 0, len = modelIds.length; i < len; i++) {\n const modelId = modelIds[i];\n const globalObjectId = math.globalizeObjectId(modelId, id);\n entity = this.objects[globalObjectId];\n if (entity) {\n changed = callback(entity) || changed;\n }\n }\n }\n }\n return changed;\n }\n\n /**\n * This method will \"tickify\" the provided `cb` function.\n *\n * This means, the function will be wrapped so:\n *\n * - it runs time-aligned to scene ticks\n * - it runs maximum once per scene-tick\n *\n * @param {Function} cb The function to tickify\n * @returns {Function}\n */\n tickify(cb) {\n const cbString = cb.toString();\n\n /**\n * Check if the function is already tickified, and if so return the cached one.\n */\n if (cbString in this._tickifiedFunctions) {\n return this._tickifiedFunctions[cbString].wrapperFunc;\n }\n\n let alreadyRun = 0;\n let needToRun = 0;\n\n let lastArgs;\n\n /**\n * The provided `cb` function is replaced with a \"set-dirty\" function\n *\n * @type {Function}\n */\n const wrapperFunc = function (...args) {\n lastArgs = args;\n needToRun++;\n };\n\n /**\n * An each scene tick, if the \"dirty-flag\" is set, run the `cb` function.\n *\n * This will make it run time-aligned to the scene tick.\n */\n const tickSubId = this.on(\"tick\", () => {\n const tmp = needToRun;\n if (tmp > alreadyRun) {\n alreadyRun = tmp;\n cb(...lastArgs);\n }\n });\n\n /**\n * And, store the list of subscribers.\n */\n this._tickifiedFunctions[cbString] = {tickSubId, wrapperFunc};\n\n return wrapperFunc;\n }\n\n /**\n * Destroys this Scene.\n */\n destroy() {\n\n super.destroy();\n\n for (const id in this.components) {\n if (this.components.hasOwnProperty(id)) {\n this.components[id].destroy();\n }\n }\n\n this.canvas.gl = null;\n\n // Memory leak prevention\n this.components = null;\n this.models = null;\n this.objects = null;\n this.visibleObjects = null;\n this.xrayedObjects = null;\n this.highlightedObjects = null;\n this.selectedObjects = null;\n this.colorizedObjects = null;\n this.opacityObjects = null;\n this.sectionPlanes = null;\n this.lights = null;\n this.lightMaps = null;\n this.reflectionMaps = null;\n this._objectIds = null;\n this._visibleObjectIds = null;\n this._xrayedObjectIds = null;\n this._highlightedObjectIds = null;\n this._selectedObjectIds = null;\n this._colorizedObjectIds = null;\n this.types = null;\n this.components = null;\n this.canvas = null;\n this._renderer = null;\n this.input = null;\n this._viewport = null;\n this._camera = null;\n }\n}\n\n/**\n * Texture wrapping mode in which the texture repeats to infinity.\n */\nconst RepeatWrapping = 1000;\n\n/**\n * Texture wrapping mode in which the last pixel of the texture stretches to the edge of the mesh.\n */\nconst ClampToEdgeWrapping = 1001;\n\n/**\n * Texture wrapping mode in which the texture repeats to infinity, mirroring on each repeat.\n */\nconst MirroredRepeatWrapping = 1002;\n\n/**\n * Texture magnification and minification filter that returns the nearest texel to the given sample coordinates.\n */\nconst NearestFilter = 1003;\n\n/**\n * Texture minification filter that chooses the mipmap that most closely matches the size of the pixel being textured and returns the nearest texel to the given sample coordinates.\n */\nconst NearestMipMapNearestFilter = 1004;\n\n/**\n * Texture minification filter that chooses the mipmap that most closely matches the size of the pixel being textured\n * and returns the nearest texel to the given sample coordinates.\n */\nconst NearestMipmapNearestFilter = 1004;\n\n/**\n * Texture minification filter that chooses two mipmaps that most closely match the size of the pixel being textured\n * and returns the nearest texel to the center of the pixel at the given sample coordinates.\n */\nconst NearestMipmapLinearFilter = 1005;\n\n/**\n * Texture minification filter that chooses two mipmaps that most closely match the size of the pixel being textured\n * and returns the nearest texel to the center of the pixel at the given sample coordinates.\n */\nconst NearestMipMapLinearFilter = 1005;\n\n/**\n * Texture magnification and minification filter that returns the weighted average of the four nearest texels to the given sample coordinates.\n */\nconst LinearFilter = 1006;\n\n/**\n * Texture minification filter that chooses the mipmap that most closely matches the size of the pixel being textured and\n * returns the weighted average of the four nearest texels to the given sample coordinates.\n */\nconst LinearMipmapNearestFilter = 1007;\n\n/**\n * Texture minification filter that chooses the mipmap that most closely matches the size of the pixel being textured and\n * returns the weighted average of the four nearest texels to the given sample coordinates.\n */\nconst LinearMipMapNearestFilter = 1007;\n\n/**\n * Texture minification filter that chooses two mipmaps that most closely match the size of the pixel being textured,\n * finds within each mipmap the weighted average of the nearest texel to the center of the pixel, then returns the\n * weighted average of those two values.\n */\nconst LinearMipmapLinearFilter = 1008;\n\n/**\n * Texture minification filter that chooses two mipmaps that most closely match the size of the pixel being textured,\n * finds within each mipmap the weighted average of the nearest texel to the center of the pixel, then returns the\n * weighted average of those two values.\n */\nconst LinearMipMapLinearFilter = 1008;\n\n/**\n * Unsigned 8-bit integer type.\n */\nconst UnsignedByteType = 1009;\n\n/**\n * Signed 8-bit integer type.\n */\nconst ByteType = 1010;\n\n/**\n * Signed 16-bit integer type.\n */\nconst ShortType = 1011;\n\n/**\n * Unsigned 16-bit integer type.\n */\nconst UnsignedShortType = 1012;\n\n/**\n * Signed 32-bit integer type.\n */\nconst IntType = 1013;\n\n/**\n * Unsigned 32-bit integer type.\n */\nconst UnsignedIntType = 1014;\n\n/**\n * Signed 32-bit floating-point type.\n */\nconst FloatType = 1015;\n\n/**\n * Signed 16-bit half-precision floating-point type.\n */\nconst HalfFloatType = 1016;\n\n/**\n * Texture packing mode in which each ````RGBA```` channel is packed into 4 bits, for a combined total of 16 bits.\n */\nconst UnsignedShort4444Type = 1017;\n\n/**\n * Texture packing mode in which the ````RGB```` channels are each packed into 5 bits, and the ````A```` channel is packed into 1 bit, for a combined total of 16 bits.\n */\nconst UnsignedShort5551Type = 1018;\n\n/**\n * Unsigned integer type for 24-bit depth texture data.\n */\nconst UnsignedInt248Type = 1020;\n\n/**\n * Texture sampling mode that discards the ````RGBA```` components and just reads the ````A```` component.\n */\nconst AlphaFormat = 1021;\n\n/**\n * Texture sampling mode that discards the ````A```` component and reads the ````RGB```` components.\n */\nconst RGBFormat = 1022;\n\n/**\n * Texture sampling mode that reads the ````RGBA```` components.\n */\nconst RGBAFormat = 1023;\n\n/**\n * Texture sampling mode that reads each ````RGB```` texture component as a luminance value, converted to a float and clamped\n * to ````[0,1]````, while always reading the ````A```` channel as ````1.0````.\n */\nconst LuminanceFormat = 1024;\n\n/**\n * Texture sampling mode that reads each of the ````RGBA```` texture components as a luminance/alpha value, converted to a float and clamped to ````[0,1]````.\n */\nconst LuminanceAlphaFormat = 1025;\n\n/**\n * Texture sampling mode that reads each element as a single depth value, converts it to a float and clamps to ````[0,1]````.\n */\nconst DepthFormat = 1026;\n\n/**\n * Texture sampling mode that\n */\nconst DepthStencilFormat = 1027;\n\n/**\n * Texture sampling mode that discards the ````GBA```` components and just reads the ````R```` component.\n */\nconst RedFormat = 1028;\n\n/**\n * Texture sampling mode that discards the ````GBA```` components and just reads the ````R```` component, as an integer instead of as a float.\n */\nconst RedIntegerFormat = 1029;\n\n/**\n * Texture sampling mode that discards the ````A```` and ````B```` components and just reads the ````R```` and ````G```` components.\n */\nconst RGFormat = 1030;\n\n/**\n * Texture sampling mode that discards the ````A```` and ````B```` components and just reads the ````R```` and ````G```` components, as integers instead of floats.\n */\nconst RGIntegerFormat = 1031;\n\n/**\n * Texture sampling mode that reads the ````RGBA```` components as integers instead of floats.\n */\nconst RGBAIntegerFormat = 1033;\n\n/**\n * Texture format mode in which the texture is formatted as a DXT1 compressed ````RGB```` image.\n */\nconst RGB_S3TC_DXT1_Format = 33776;\n\n/**\n * Texture format mode in which the texture is formatted as a DXT1 compressed ````RGBA```` image.\n */\nconst RGBA_S3TC_DXT1_Format = 33777;\n\n/**\n * Texture format mode in which the texture is formatted as a DXT3 compressed ````RGBA```` image.\n */\nconst RGBA_S3TC_DXT3_Format = 33778;\n\n/**\n * Texture format mode in which the texture is formatted as a DXT5 compressed ````RGBA```` image.\n */\nconst RGBA_S3TC_DXT5_Format = 33779;\n\n/**\n * Texture format mode in which the texture is formatted as a PVRTC compressed\n * image, with ````RGB```` compression in 4-bit mode and one block for each 4×4 pixels.\n */\nconst RGB_PVRTC_4BPPV1_Format = 35840;\n\n/**\n * Texture format mode in which the texture is formatted as a PVRTC compressed\n * image, with ````RGB```` compression in 2-bit mode and one block for each 8×4 pixels.\n */\nconst RGB_PVRTC_2BPPV1_Format = 35841;\n\n/**\n * Texture format mode in which the texture is formatted as a PVRTC compressed\n * image, with ````RGBA```` compression in 4-bit mode and one block for each 4×4 pixels.\n */\nconst RGBA_PVRTC_4BPPV1_Format = 35842;\n\n/**\n * Texture format mode in which the texture is formatted as a PVRTC compressed\n * image, with ````RGBA```` compression in 2-bit mode and one block for each 8×4 pixels.\n */\nconst RGBA_PVRTC_2BPPV1_Format = 35843;\n\n/**\n * Texture format mode in which the texture is formatted as an ETC1 compressed\n * ````RGB```` image.\n */\nconst RGB_ETC1_Format = 36196;\n\n/**\n * Texture format mode in which the texture is formatted as an ETC2 compressed\n * ````RGB```` image.\n */\nconst RGB_ETC2_Format = 37492;\n\n/**\n * Texture format mode in which the texture is formatted as an ETC2 compressed\n * ````RGBA```` image.\n */\nconst RGBA_ETC2_EAC_Format = 37496;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_4x4_Format = 37808;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_5x4_Format = 37809;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_5x5_Format = 37810;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_6x5_Format = 37811;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_6x6_Format = 37812;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_8x5_Format = 37813;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_8x6_Format = 37814;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_8x8_Format = 37815;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_10x5_Format = 37816;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_10x6_Format = 37817;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_10x8_Format = 37818;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_10x10_Format = 37819;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_12x10_Format = 37820;\n\n/**\n * Texture format mode in which the texture is formatted as an ATSC compressed\n * ````RGBA```` image.\n */\nconst RGBA_ASTC_12x12_Format = 37821;\n\n/**\n * Texture format mode in which the texture is formatted as an BPTC compressed\n * ````RGBA```` image.\n */\nconst RGBA_BPTC_Format = 36492;\n\n/**\n * Texture encoding mode in which the texture image is in linear color space.\n */\nconst LinearEncoding = 3000;\n\n/**\n * Texture encoding mode in which the texture image is in sRGB color space.\n */\nconst sRGBEncoding = 3001;\n\n/**\n * Media type for GIF images.\n */\nconst GIFMediaType = 10000;\n\n/**\n * Media type for JPEG images.\n */\nconst JPEGMediaType = 10001;\n\n/**\n * Media type for PNG images.\n */\nconst PNGMediaType = 10002;\n\n/**\n * Media type for compressed texture data.\n */\nconst CompressedMediaType = 10003;\n\n/**\n * @private\n */\n\nconst DrawShaderSource = function (mesh) {\n if (mesh._material._state.type === \"LambertMaterial\") {\n this.vertex = buildVertexLambert(mesh);\n this.fragment = buildFragmentLambert(mesh);\n } else {\n this.vertex = buildVertexDraw(mesh);\n this.fragment = buildFragmentDraw(mesh);\n }\n};\n\nconst TEXTURE_DECODE_FUNCS$1 = {};\nTEXTURE_DECODE_FUNCS$1[LinearEncoding] = \"linearToLinear\";\nTEXTURE_DECODE_FUNCS$1[sRGBEncoding] = \"sRGBToLinear\";\n\nfunction getReceivesShadow(mesh) {\n if (!mesh.receivesShadow) {\n return false;\n }\n const lights = mesh.scene._lightsState.lights;\n if (!lights || lights.length === 0) {\n return false;\n }\n for (let i = 0, len = lights.length; i < len; i++) {\n if (lights[i].castsShadow) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasTextures(mesh) {\n if (!mesh._geometry._state.uvBuf) {\n return false;\n }\n const material = mesh._material;\n return !!(material._ambientMap ||\n material._occlusionMap ||\n material._baseColorMap ||\n material._diffuseMap ||\n material._alphaMap ||\n material._specularMap ||\n material._glossinessMap ||\n material._specularGlossinessMap ||\n material._emissiveMap ||\n material._metallicMap ||\n material._roughnessMap ||\n material._metallicRoughnessMap ||\n material._reflectivityMap ||\n material._normalMap);\n}\n\nfunction hasNormals$1(mesh) {\n const primitive = mesh._geometry._state.primitiveName;\n if ((mesh._geometry._state.autoVertexNormals || mesh._geometry._state.normalsBuf) && (primitive === \"triangles\" || primitive === \"triangle-strip\" || primitive === \"triangle-fan\")) {\n return true;\n }\n return false;\n}\n\nfunction buildVertexLambert(mesh) {\n\n const scene = mesh.scene;\n const sectionPlanesState = mesh.scene._sectionPlanesState;\n const lightsState = mesh.scene._lightsState;\n const geometryState = mesh._geometry._state;\n const billboard = mesh._state.billboard;\n const stationary = mesh._state.stationary;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const quantizedGeometry = !!geometryState.compressGeometry;\n\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Lambertian drawing vertex shader\");\n src.push(\"in vec3 position;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"uniform vec4 colorize;\");\n src.push(\"uniform vec3 offset;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n src.push(\"uniform vec4 lightAmbient;\");\n src.push(\"uniform vec4 materialColor;\");\n src.push(\"uniform vec3 materialEmissive;\");\n if (geometryState.normalsBuf) {\n src.push(\"in vec3 normal;\");\n src.push(\"uniform mat4 modelNormalMatrix;\");\n src.push(\"uniform mat4 viewNormalMatrix;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n if (quantizedGeometry) {\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n }\n }\n src.push(\"out vec4 vColor;\");\n if (geometryState.primitiveName === \"points\") {\n src.push(\"uniform float pointSize;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"void billboard(inout mat4 mat) {\");\n src.push(\" mat[0][0] = 1.0;\");\n src.push(\" mat[0][1] = 0.0;\");\n src.push(\" mat[0][2] = 0.0;\");\n if (billboard === \"spherical\") {\n src.push(\" mat[1][0] = 0.0;\");\n src.push(\" mat[1][1] = 1.0;\");\n src.push(\" mat[1][2] = 0.0;\");\n }\n src.push(\" mat[2][0] = 0.0;\");\n src.push(\" mat[2][1] = 0.0;\");\n src.push(\" mat[2][2] =1.0;\");\n src.push(\"}\");\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n src.push(\"vec4 worldPosition;\");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n if (geometryState.normalsBuf) {\n if (quantizedGeometry) {\n src.push(\"vec4 localNormal = vec4(octDecode(normal.xy), 0.0); \");\n } else {\n src.push(\"vec4 localNormal = vec4(normal, 0.0); \");\n }\n src.push(\"mat4 modelNormalMatrix2 = modelNormalMatrix;\");\n src.push(\"mat4 viewNormalMatrix2 = viewNormalMatrix;\");\n }\n src.push(\"mat4 viewMatrix2 = viewMatrix;\");\n src.push(\"mat4 modelMatrix2 = modelMatrix;\");\n if (stationary) {\n src.push(\"viewMatrix2[3][0] = viewMatrix2[3][1] = viewMatrix2[3][2] = 0.0;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"mat4 modelViewMatrix = viewMatrix2 * modelMatrix2;\");\n src.push(\"billboard(modelMatrix2);\");\n src.push(\"billboard(viewMatrix2);\");\n src.push(\"billboard(modelViewMatrix);\");\n if (geometryState.normalsBuf) {\n src.push(\"mat4 modelViewNormalMatrix = viewNormalMatrix2 * modelNormalMatrix2;\");\n src.push(\"billboard(modelNormalMatrix2);\");\n src.push(\"billboard(viewNormalMatrix2);\");\n src.push(\"billboard(modelViewNormalMatrix);\");\n }\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = modelViewMatrix * localPosition;\");\n } else {\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = viewMatrix2 * worldPosition; \");\n }\n if (geometryState.normalsBuf) {\n src.push(\"vec3 viewNormal = normalize((viewNormalMatrix2 * modelNormalMatrix2 * localNormal).xyz);\");\n }\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n src.push(\"float lambertian = 1.0;\");\n if (geometryState.normalsBuf) {\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix2 * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix2 * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix2 * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n }\n src.push(\"vColor = vec4((lightAmbient.rgb * lightAmbient.a * materialColor.rgb) + materialEmissive.rgb + (reflectedColor * materialColor.rgb), materialColor.a) * colorize;\"); // TODO: How to have ambient bright enough for canvas BG but not too bright for scene?\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n }\n if (geometryState.primitiveName === \"points\") {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n return src;\n}\n\nfunction buildFragmentLambert(mesh) {\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n mesh._material._state;\n const geometryState = mesh._geometry._state;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const gammaOutput = scene.gammaOutput; // If set, then it expects that all textures and colors need to be outputted in premultiplied gamma. Default is false.\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Lambertian drawing fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"uniform bool clippable;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n if (gammaOutput) {\n src.push(\"uniform float gammaFactor;\");\n src.push(\" vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (geometryState.primitiveName === \"points\") {\n src.push(\"vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\"float r = dot(cxy, cxy);\");\n src.push(\"if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\"}\");\n\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n if (gammaOutput) {\n src.push(\"outColor = linearToGamma(vColor, gammaFactor);\");\n } else {\n src.push(\"outColor = vColor;\");\n }\n src.push(\"}\");\n return src;\n}\n\nfunction buildVertexDraw(mesh) {\n const scene = mesh.scene;\n mesh._material;\n const meshState = mesh._state;\n const sectionPlanesState = scene._sectionPlanesState;\n const geometryState = mesh._geometry._state;\n const lightsState = scene._lightsState;\n let light;\n const billboard = meshState.billboard;\n const background = meshState.background;\n const stationary = meshState.stationary;\n const texturing = hasTextures(mesh);\n const normals = hasNormals$1(mesh);\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const receivesShadow = getReceivesShadow(mesh);\n const quantizedGeometry = !!geometryState.compressGeometry;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Drawing vertex shader\");\n src.push(\"in vec3 position;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"out vec3 vViewPosition;\");\n src.push(\"uniform vec3 offset;\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (lightsState.lightMaps.length > 0) {\n src.push(\"out vec3 vWorldNormal;\");\n }\n if (normals) {\n src.push(\"in vec3 normal;\");\n src.push(\"uniform mat4 modelNormalMatrix;\");\n src.push(\"uniform mat4 viewNormalMatrix;\");\n src.push(\"out vec3 vViewNormal;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (!(light.type === \"dir\" && light.space === \"view\")) {\n src.push(\"out vec4 vViewLightReverseDirAndDist\" + i + \";\");\n }\n }\n if (quantizedGeometry) {\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n }\n }\n if (texturing) {\n src.push(\"in vec2 uv;\");\n src.push(\"out vec2 vUV;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat3 uvDecodeMatrix;\");\n }\n }\n if (geometryState.colors) {\n src.push(\"in vec4 color;\");\n src.push(\"out vec4 vColor;\");\n }\n if (geometryState.primitiveName === \"points\") {\n src.push(\"uniform float pointSize;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"void billboard(inout mat4 mat) {\");\n src.push(\" mat[0][0] = 1.0;\");\n src.push(\" mat[0][1] = 0.0;\");\n src.push(\" mat[0][2] = 0.0;\");\n if (billboard === \"spherical\") {\n src.push(\" mat[1][0] = 0.0;\");\n src.push(\" mat[1][1] = 1.0;\");\n src.push(\" mat[1][2] = 0.0;\");\n }\n src.push(\" mat[2][0] = 0.0;\");\n src.push(\" mat[2][1] = 0.0;\");\n src.push(\" mat[2][2] =1.0;\");\n src.push(\"}\");\n }\n if (receivesShadow) {\n src.push(\"const mat4 texUnitConverter = mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0);\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) { // Light sources\n if (lightsState.lights[i].castsShadow) {\n src.push(\"uniform mat4 shadowViewMatrix\" + i + \";\");\n src.push(\"uniform mat4 shadowProjMatrix\" + i + \";\");\n src.push(\"out vec4 vShadowPosFromLight\" + i + \";\");\n }\n }\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n src.push(\"vec4 worldPosition;\");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n if (normals) {\n if (quantizedGeometry) {\n src.push(\"vec4 localNormal = vec4(octDecode(normal.xy), 0.0); \");\n } else {\n src.push(\"vec4 localNormal = vec4(normal, 0.0); \");\n }\n src.push(\"mat4 modelNormalMatrix2 = modelNormalMatrix;\");\n src.push(\"mat4 viewNormalMatrix2 = viewNormalMatrix;\");\n }\n src.push(\"mat4 viewMatrix2 = viewMatrix;\");\n src.push(\"mat4 modelMatrix2 = modelMatrix;\");\n if (stationary) {\n src.push(\"viewMatrix2[3][0] = viewMatrix2[3][1] = viewMatrix2[3][2] = 0.0;\");\n } else if (background) {\n src.push(\"viewMatrix2[3] = vec4(0.0, 0.0, 0.0 ,1.0);\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"mat4 modelViewMatrix = viewMatrix2 * modelMatrix2;\");\n src.push(\"billboard(modelMatrix2);\");\n src.push(\"billboard(viewMatrix2);\");\n src.push(\"billboard(modelViewMatrix);\");\n if (normals) {\n src.push(\"mat4 modelViewNormalMatrix = viewNormalMatrix2 * modelNormalMatrix2;\");\n src.push(\"billboard(modelNormalMatrix2);\");\n src.push(\"billboard(viewNormalMatrix2);\");\n src.push(\"billboard(modelViewNormalMatrix);\");\n }\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = modelViewMatrix * localPosition;\");\n } else {\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = viewMatrix2 * worldPosition; \");\n }\n if (normals) {\n src.push(\"vec3 worldNormal = (modelNormalMatrix2 * localNormal).xyz; \");\n if (lightsState.lightMaps.length > 0) {\n src.push(\"vWorldNormal = worldNormal;\");\n }\n src.push(\"vViewNormal = normalize((viewNormalMatrix2 * vec4(worldNormal, 1.0)).xyz);\");\n src.push(\"vec3 tmpVec3;\");\n src.push(\"float lightDist;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) { // Lights\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"world\") {\n src.push(\"tmpVec3 = vec3(viewMatrix2 * vec4(lightDir\" + i + \", 0.0) ).xyz;\");\n src.push(\"vViewLightReverseDirAndDist\" + i + \" = vec4(-tmpVec3, 0.0);\");\n }\n }\n if (light.type === \"point\") {\n if (light.space === \"world\") {\n src.push(\"tmpVec3 = (viewMatrix2 * vec4(lightPos\" + i + \", 1.0)).xyz - viewPosition.xyz;\");\n src.push(\"lightDist = abs(length(tmpVec3));\");\n } else {\n src.push(\"tmpVec3 = lightPos\" + i + \".xyz - viewPosition.xyz;\");\n src.push(\"lightDist = abs(length(tmpVec3));\");\n }\n src.push(\"vViewLightReverseDirAndDist\" + i + \" = vec4(tmpVec3, lightDist);\");\n }\n }\n }\n if (texturing) {\n if (quantizedGeometry) {\n src.push(\"vUV = (uvDecodeMatrix * vec3(uv, 1.0)).xy;\");\n } else {\n src.push(\"vUV = uv;\");\n }\n }\n if (geometryState.colors) {\n src.push(\"vColor = color;\");\n }\n if (geometryState.primitiveName === \"points\") {\n src.push(\"gl_PointSize = pointSize;\");\n }\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n }\n src.push(\" vViewPosition = viewPosition.xyz;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n if (background) {\n src.push(\"clipPos.z = clipPos.w;\");\n }\n src.push(\"gl_Position = clipPos;\");\n if (receivesShadow) {\n src.push(\"const mat4 texUnitConverter = mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0);\");\n src.push(\"vec4 tempx; \");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) { // Light sources\n if (lightsState.lights[i].castsShadow) {\n src.push(\"vShadowPosFromLight\" + i + \" = texUnitConverter * shadowProjMatrix\" + i + \" * (shadowViewMatrix\" + i + \" * worldPosition); \");\n }\n }\n }\n src.push(\"}\");\n return src;\n}\n\nfunction buildFragmentDraw(mesh) {\n\n const scene = mesh.scene;\n scene.canvas.gl;\n const material = mesh._material;\n const geometryState = mesh._geometry._state;\n const sectionPlanesState = mesh.scene._sectionPlanesState;\n const lightsState = mesh.scene._lightsState;\n const materialState = mesh._material._state;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const normals = hasNormals$1(mesh);\n const uvs = geometryState.uvBuf;\n const phongMaterial = (materialState.type === \"PhongMaterial\");\n const metallicMaterial = (materialState.type === \"MetallicMaterial\");\n const specularMaterial = (materialState.type === \"SpecularMaterial\");\n const receivesShadow = getReceivesShadow(mesh);\n scene.gammaInput; // If set, then it expects that all textures and colors are premultiplied gamma. Default is false.\n const gammaOutput = scene.gammaOutput; // If set, then it expects that all textures and colors need to be outputted in premultiplied gamma. Default is false.\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Drawing fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (receivesShadow) {\n src.push(\"float unpackDepth (vec4 color) {\");\n src.push(\" const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0 * 256.0), 1.0/(256.0*256.0*256.0));\");\n src.push(\" return dot(color, bitShift);\");\n src.push(\"}\");\n }\n\n //--------------------------------------------------------------------------------\n // GAMMA CORRECTION\n //--------------------------------------------------------------------------------\n\n src.push(\"uniform float gammaFactor;\");\n src.push(\"vec4 linearToLinear( in vec4 value ) {\");\n src.push(\" return value;\");\n src.push(\"}\");\n src.push(\"vec4 sRGBToLinear( in vec4 value ) {\");\n src.push(\" return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\");\n src.push(\"}\");\n src.push(\"vec4 gammaToLinear( in vec4 value) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\");\n src.push(\"}\");\n if (gammaOutput) {\n src.push(\"vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n\n //--------------------------------------------------------------------------------\n // USER CLIP PLANES\n //--------------------------------------------------------------------------------\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"uniform bool clippable;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n\n if (normals) {\n\n //--------------------------------------------------------------------------------\n // LIGHT AND REFLECTION MAP INPUTS\n // Define here so available globally to shader functions\n //--------------------------------------------------------------------------------\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"uniform samplerCube lightMap;\");\n src.push(\"uniform mat4 viewNormalMatrix;\");\n }\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\"uniform samplerCube reflectionMap;\");\n }\n if (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0) {\n src.push(\"uniform mat4 viewMatrix;\");\n }\n\n //--------------------------------------------------------------------------------\n // SHADING FUNCTIONS\n //--------------------------------------------------------------------------------\n\n // CONSTANT DEFINITIONS\n\n src.push(\"#define PI 3.14159265359\");\n src.push(\"#define RECIPROCAL_PI 0.31830988618\");\n src.push(\"#define RECIPROCAL_PI2 0.15915494\");\n src.push(\"#define EPSILON 1e-6\");\n\n src.push(\"#define saturate(a) clamp( a, 0.0, 1.0 )\");\n\n // UTILITY DEFINITIONS\n\n src.push(\"vec3 inverseTransformDirection(in vec3 dir, in mat4 matrix) {\");\n src.push(\" return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\");\n src.push(\"}\");\n\n // STRUCTURES\n\n src.push(\"struct IncidentLight {\");\n src.push(\" vec3 color;\");\n src.push(\" vec3 direction;\");\n src.push(\"};\");\n\n src.push(\"struct ReflectedLight {\");\n src.push(\" vec3 diffuse;\");\n src.push(\" vec3 specular;\");\n src.push(\"};\");\n\n src.push(\"struct Geometry {\");\n src.push(\" vec3 position;\");\n src.push(\" vec3 viewNormal;\");\n src.push(\" vec3 worldNormal;\");\n src.push(\" vec3 viewEyeDir;\");\n src.push(\"};\");\n\n src.push(\"struct Material {\");\n src.push(\" vec3 diffuseColor;\");\n src.push(\" float specularRoughness;\");\n src.push(\" vec3 specularColor;\");\n src.push(\" float shine;\"); // Only used for Phong\n src.push(\"};\");\n\n // COMMON UTILS\n\n if (phongMaterial) {\n\n if (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0) {\n\n src.push(\"void computePhongLightMapping(const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\" vec3 irradiance = \" + TEXTURE_DECODE_FUNCS$1[lightsState.lightMaps[0].encoding] + \"(texture(lightMap, geometry.worldNormal)).rgb;\");\n src.push(\" irradiance *= PI;\");\n src.push(\" vec3 diffuseBRDFContrib = (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.diffuse += irradiance * diffuseBRDFContrib;\");\n }\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\" vec3 reflectVec = reflect(-geometry.viewEyeDir, geometry.viewNormal);\");\n src.push(\" vec3 radiance = texture(reflectionMap, reflectVec).rgb * 0.2;\");\n src.push(\" radiance *= PI;\");\n src.push(\" reflectedLight.specular += radiance;\");\n }\n src.push(\"}\");\n }\n\n src.push(\"void computePhongLighting(const in IncidentLight directLight, const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n src.push(\" float dotNL = saturate(dot(geometry.viewNormal, directLight.direction));\");\n src.push(\" vec3 irradiance = dotNL * directLight.color * PI;\");\n src.push(\" reflectedLight.diffuse += irradiance * (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.specular += directLight.color * material.specularColor * pow(max(dot(reflect(-directLight.direction, -geometry.viewNormal), geometry.viewEyeDir), 0.0), material.shine);\");\n src.push(\"}\");\n }\n\n if (metallicMaterial || specularMaterial) {\n\n // IRRADIANCE EVALUATION\n\n src.push(\"float GGXRoughnessToBlinnExponent(const in float ggxRoughness) {\");\n src.push(\" float r = ggxRoughness + 0.0001;\");\n src.push(\" return (2.0 / (r * r) - 2.0);\");\n src.push(\"}\");\n\n src.push(\"float getSpecularMIPLevel(const in float blinnShininessExponent, const in int maxMIPLevel) {\");\n src.push(\" float maxMIPLevelScalar = float( maxMIPLevel );\");\n src.push(\" float desiredMIPLevel = maxMIPLevelScalar - 0.79248 - 0.5 * log2( ( blinnShininessExponent * blinnShininessExponent ) + 1.0 );\");\n src.push(\" return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\");\n src.push(\"}\");\n\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\"vec3 getLightProbeIndirectRadiance(const in vec3 reflectVec, const in float blinnShininessExponent, const in int maxMIPLevel) {\");\n src.push(\" float mipLevel = 0.5 * getSpecularMIPLevel(blinnShininessExponent, maxMIPLevel);\"); //TODO: a random factor - fix this\n src.push(\" vec3 envMapColor = \" + TEXTURE_DECODE_FUNCS$1[lightsState.reflectionMaps[0].encoding] + \"(texture(reflectionMap, reflectVec, mipLevel)).rgb;\");\n src.push(\" return envMapColor;\");\n src.push(\"}\");\n }\n\n // SPECULAR BRDF EVALUATION\n\n src.push(\"vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {\");\n src.push(\" float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\");\n src.push(\" return ( 1.0 - specularColor ) * fresnel + specularColor;\");\n src.push(\"}\");\n\n src.push(\"float G_GGX_Smith(const in float alpha, const in float dotNL, const in float dotNV) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );\");\n src.push(\" float gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );\");\n src.push(\" return 1.0 / ( gl * gv );\");\n src.push(\"}\");\n\n src.push(\"float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );\");\n src.push(\" float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );\");\n src.push(\" return 0.5 / max( gv + gl, EPSILON );\");\n src.push(\"}\");\n\n src.push(\"float D_GGX(const in float alpha, const in float dotNH) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float denom = ( dotNH * dotNH) * ( a2 - 1.0 ) + 1.0;\");\n src.push(\" return RECIPROCAL_PI * a2 / ( denom * denom);\");\n src.push(\"}\");\n\n src.push(\"vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in Geometry geometry, const in vec3 specularColor, const in float roughness) {\");\n src.push(\" float alpha = ( roughness * roughness );\");\n src.push(\" vec3 halfDir = normalize( incidentLight.direction + geometry.viewEyeDir );\");\n src.push(\" float dotNL = saturate( dot( geometry.viewNormal, incidentLight.direction ) );\");\n src.push(\" float dotNV = saturate( dot( geometry.viewNormal, geometry.viewEyeDir ) );\");\n src.push(\" float dotNH = saturate( dot( geometry.viewNormal, halfDir ) );\");\n src.push(\" float dotLH = saturate( dot( incidentLight.direction, halfDir ) );\");\n src.push(\" vec3 F = F_Schlick( specularColor, dotLH );\");\n src.push(\" float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\");\n src.push(\" float D = D_GGX( alpha, dotNH );\");\n src.push(\" return F * (G * D);\");\n src.push(\"}\");\n\n src.push(\"vec3 BRDF_Specular_GGX_Environment(const in Geometry geometry, const in vec3 specularColor, const in float roughness) {\");\n src.push(\" float dotNV = saturate(dot(geometry.viewNormal, geometry.viewEyeDir));\");\n src.push(\" const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022);\");\n src.push(\" const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04);\");\n src.push(\" vec4 r = roughness * c0 + c1;\");\n src.push(\" float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;\");\n src.push(\" vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;\");\n src.push(\" return specularColor * AB.x + AB.y;\");\n src.push(\"}\");\n\n if (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0) {\n\n src.push(\"void computePBRLightMapping(const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n if (lightsState.lightMaps.length > 0) {\n src.push(\" vec3 irradiance = sRGBToLinear(texture(lightMap, geometry.worldNormal)).rgb;\");\n src.push(\" irradiance *= PI;\");\n src.push(\" vec3 diffuseBRDFContrib = (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.diffuse += irradiance * diffuseBRDFContrib;\");\n // src.push(\" reflectedLight.diffuse = vec3(1.0, 0.0, 0.0);\");\n }\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\" vec3 reflectVec = reflect(-geometry.viewEyeDir, geometry.viewNormal);\");\n src.push(\" reflectVec = inverseTransformDirection(reflectVec, viewMatrix);\");\n src.push(\" float blinnExpFromRoughness = GGXRoughnessToBlinnExponent(material.specularRoughness);\");\n src.push(\" vec3 radiance = getLightProbeIndirectRadiance(reflectVec, blinnExpFromRoughness, 8);\");\n src.push(\" vec3 specularBRDFContrib = BRDF_Specular_GGX_Environment(geometry, material.specularColor, material.specularRoughness);\");\n src.push(\" reflectedLight.specular += radiance * specularBRDFContrib;\");\n }\n src.push(\"}\");\n }\n\n // MAIN LIGHTING COMPUTATION FUNCTION\n\n src.push(\"void computePBRLighting(const in IncidentLight incidentLight, const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n src.push(\" float dotNL = saturate(dot(geometry.viewNormal, incidentLight.direction));\");\n src.push(\" vec3 irradiance = dotNL * incidentLight.color * PI;\");\n src.push(\" reflectedLight.diffuse += irradiance * (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.specular += irradiance * BRDF_Specular_GGX(incidentLight, geometry, material.specularColor, material.specularRoughness);\");\n src.push(\"}\");\n\n } // (metallicMaterial || specularMaterial)\n\n } // geometry.normals\n\n //--------------------------------------------------------------------------------\n // GEOMETRY INPUTS\n //--------------------------------------------------------------------------------\n\n src.push(\"in vec3 vViewPosition;\");\n\n if (geometryState.colors) {\n src.push(\"in vec4 vColor;\");\n }\n\n if (uvs &&\n ((normals && material._normalMap)\n || material._ambientMap\n || material._baseColorMap\n || material._diffuseMap\n || material._emissiveMap\n || material._metallicMap\n || material._roughnessMap\n || material._metallicRoughnessMap\n || material._specularMap\n || material._glossinessMap\n || material._specularGlossinessMap\n || material._occlusionMap\n || material._alphaMap)) {\n src.push(\"in vec2 vUV;\");\n }\n\n if (normals) {\n if (lightsState.lightMaps.length > 0) {\n src.push(\"in vec3 vWorldNormal;\");\n }\n src.push(\"in vec3 vViewNormal;\");\n }\n\n //--------------------------------------------------------------------------------\n // MATERIAL CHANNEL INPUTS\n //--------------------------------------------------------------------------------\n\n if (materialState.ambient) {\n src.push(\"uniform vec3 materialAmbient;\");\n }\n if (materialState.baseColor) {\n src.push(\"uniform vec3 materialBaseColor;\");\n }\n if (materialState.alpha !== undefined && materialState.alpha !== null) {\n src.push(\"uniform vec4 materialAlphaModeCutoff;\"); // [alpha, alphaMode, alphaCutoff]\n }\n if (materialState.emissive) {\n src.push(\"uniform vec3 materialEmissive;\");\n }\n if (materialState.diffuse) {\n src.push(\"uniform vec3 materialDiffuse;\");\n }\n if (materialState.glossiness !== undefined && materialState.glossiness !== null) {\n src.push(\"uniform float materialGlossiness;\");\n }\n if (materialState.shininess !== undefined && materialState.shininess !== null) {\n src.push(\"uniform float materialShininess;\"); // Phong channel\n }\n if (materialState.specular) {\n src.push(\"uniform vec3 materialSpecular;\");\n }\n if (materialState.metallic !== undefined && materialState.metallic !== null) {\n src.push(\"uniform float materialMetallic;\");\n }\n if (materialState.roughness !== undefined && materialState.roughness !== null) {\n src.push(\"uniform float materialRoughness;\");\n }\n if (materialState.specularF0 !== undefined && materialState.specularF0 !== null) {\n src.push(\"uniform float materialSpecularF0;\");\n }\n\n //--------------------------------------------------------------------------------\n // MATERIAL TEXTURE INPUTS\n //--------------------------------------------------------------------------------\n\n if (uvs && material._ambientMap) {\n src.push(\"uniform sampler2D ambientMap;\");\n if (material._ambientMap._state.matrix) {\n src.push(\"uniform mat4 ambientMapMatrix;\");\n }\n }\n if (uvs && material._baseColorMap) {\n src.push(\"uniform sampler2D baseColorMap;\");\n if (material._baseColorMap._state.matrix) {\n src.push(\"uniform mat4 baseColorMapMatrix;\");\n }\n }\n if (uvs && material._diffuseMap) {\n src.push(\"uniform sampler2D diffuseMap;\");\n if (material._diffuseMap._state.matrix) {\n src.push(\"uniform mat4 diffuseMapMatrix;\");\n }\n }\n if (uvs && material._emissiveMap) {\n src.push(\"uniform sampler2D emissiveMap;\");\n if (material._emissiveMap._state.matrix) {\n src.push(\"uniform mat4 emissiveMapMatrix;\");\n }\n }\n if (normals && uvs && material._metallicMap) {\n src.push(\"uniform sampler2D metallicMap;\");\n if (material._metallicMap._state.matrix) {\n src.push(\"uniform mat4 metallicMapMatrix;\");\n }\n }\n if (normals && uvs && material._roughnessMap) {\n src.push(\"uniform sampler2D roughnessMap;\");\n if (material._roughnessMap._state.matrix) {\n src.push(\"uniform mat4 roughnessMapMatrix;\");\n }\n }\n if (normals && uvs && material._metallicRoughnessMap) {\n src.push(\"uniform sampler2D metallicRoughnessMap;\");\n if (material._metallicRoughnessMap._state.matrix) {\n src.push(\"uniform mat4 metallicRoughnessMapMatrix;\");\n }\n }\n if (normals && material._normalMap) {\n src.push(\"uniform sampler2D normalMap;\");\n if (material._normalMap._state.matrix) {\n src.push(\"uniform mat4 normalMapMatrix;\");\n }\n src.push(\"vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\");\n src.push(\" vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\");\n src.push(\" vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\");\n src.push(\" vec2 st0 = dFdx( uv.st );\");\n src.push(\" vec2 st1 = dFdy( uv.st );\");\n src.push(\" vec3 S = normalize( q0 * st1.t - q1 * st0.t );\");\n src.push(\" vec3 T = normalize( -q0 * st1.s + q1 * st0.s );\");\n src.push(\" vec3 N = normalize( surf_norm );\");\n src.push(\" vec3 mapN = texture( normalMap, uv ).xyz * 2.0 - 1.0;\");\n src.push(\" mat3 tsn = mat3( S, T, N );\");\n // src.push(\" mapN *= 3.0;\");\n src.push(\" return normalize( tsn * mapN );\");\n src.push(\"}\");\n }\n if (uvs && material._occlusionMap) {\n src.push(\"uniform sampler2D occlusionMap;\");\n if (material._occlusionMap._state.matrix) {\n src.push(\"uniform mat4 occlusionMapMatrix;\");\n }\n }\n if (uvs && material._alphaMap) {\n src.push(\"uniform sampler2D alphaMap;\");\n if (material._alphaMap._state.matrix) {\n src.push(\"uniform mat4 alphaMapMatrix;\");\n }\n }\n if (normals && uvs && material._specularMap) {\n src.push(\"uniform sampler2D specularMap;\");\n if (material._specularMap._state.matrix) {\n src.push(\"uniform mat4 specularMapMatrix;\");\n }\n }\n if (normals && uvs && material._glossinessMap) {\n src.push(\"uniform sampler2D glossinessMap;\");\n if (material._glossinessMap._state.matrix) {\n src.push(\"uniform mat4 glossinessMapMatrix;\");\n }\n }\n if (normals && uvs && material._specularGlossinessMap) {\n src.push(\"uniform sampler2D materialSpecularGlossinessMap;\");\n if (material._specularGlossinessMap._state.matrix) {\n src.push(\"uniform mat4 materialSpecularGlossinessMapMatrix;\");\n }\n }\n\n //--------------------------------------------------------------------------------\n // MATERIAL FRESNEL INPUTS\n //--------------------------------------------------------------------------------\n\n if (normals && (material._diffuseFresnel ||\n material._specularFresnel ||\n material._alphaFresnel ||\n material._emissiveFresnel ||\n material._reflectivityFresnel)) {\n src.push(\"float fresnel(vec3 eyeDir, vec3 normal, float edgeBias, float centerBias, float power) {\");\n src.push(\" float fr = abs(dot(eyeDir, normal));\");\n src.push(\" float finalFr = clamp((fr - edgeBias) / (centerBias - edgeBias), 0.0, 1.0);\");\n src.push(\" return pow(finalFr, power);\");\n src.push(\"}\");\n if (material._diffuseFresnel) {\n src.push(\"uniform float diffuseFresnelCenterBias;\");\n src.push(\"uniform float diffuseFresnelEdgeBias;\");\n src.push(\"uniform float diffuseFresnelPower;\");\n src.push(\"uniform vec3 diffuseFresnelCenterColor;\");\n src.push(\"uniform vec3 diffuseFresnelEdgeColor;\");\n }\n if (material._specularFresnel) {\n src.push(\"uniform float specularFresnelCenterBias;\");\n src.push(\"uniform float specularFresnelEdgeBias;\");\n src.push(\"uniform float specularFresnelPower;\");\n src.push(\"uniform vec3 specularFresnelCenterColor;\");\n src.push(\"uniform vec3 specularFresnelEdgeColor;\");\n }\n if (material._alphaFresnel) {\n src.push(\"uniform float alphaFresnelCenterBias;\");\n src.push(\"uniform float alphaFresnelEdgeBias;\");\n src.push(\"uniform float alphaFresnelPower;\");\n src.push(\"uniform vec3 alphaFresnelCenterColor;\");\n src.push(\"uniform vec3 alphaFresnelEdgeColor;\");\n }\n if (material._reflectivityFresnel) {\n src.push(\"uniform float materialSpecularF0FresnelCenterBias;\");\n src.push(\"uniform float materialSpecularF0FresnelEdgeBias;\");\n src.push(\"uniform float materialSpecularF0FresnelPower;\");\n src.push(\"uniform vec3 materialSpecularF0FresnelCenterColor;\");\n src.push(\"uniform vec3 materialSpecularF0FresnelEdgeColor;\");\n }\n if (material._emissiveFresnel) {\n src.push(\"uniform float emissiveFresnelCenterBias;\");\n src.push(\"uniform float emissiveFresnelEdgeBias;\");\n src.push(\"uniform float emissiveFresnelPower;\");\n src.push(\"uniform vec3 emissiveFresnelCenterColor;\");\n src.push(\"uniform vec3 emissiveFresnelEdgeColor;\");\n }\n }\n\n //--------------------------------------------------------------------------------\n // LIGHT SOURCES\n //--------------------------------------------------------------------------------\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n if (normals) {\n for (let i = 0, len = lightsState.lights.length; i < len; i++) { // Light sources\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightAttenuation\" + i + \";\");\n }\n if (light.type === \"dir\" && light.space === \"view\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\" && light.space === \"view\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n } else {\n src.push(\"in vec4 vViewLightReverseDirAndDist\" + i + \";\");\n }\n }\n }\n\n if (receivesShadow) {\n\n // Variance castsShadow mapping filter\n\n // src.push(\"float linstep(float low, float high, float v){\");\n // src.push(\" return clamp((v-low)/(high-low), 0.0, 1.0);\");\n // src.push(\"}\");\n //\n // src.push(\"float VSM(sampler2D depths, vec2 uv, float compare){\");\n // src.push(\" vec2 moments = texture(depths, uv).xy;\");\n // src.push(\" float p = smoothstep(compare-0.02, compare, moments.x);\");\n // src.push(\" float variance = max(moments.y - moments.x*moments.x, -0.001);\");\n // src.push(\" float d = compare - moments.x;\");\n // src.push(\" float p_max = linstep(0.2, 1.0, variance / (variance + d*d));\");\n // src.push(\" return clamp(max(p, p_max), 0.0, 1.0);\");\n // src.push(\"}\");\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) { // Light sources\n if (lightsState.lights[i].castsShadow) {\n src.push(\"in vec4 vShadowPosFromLight\" + i + \";\");\n src.push(\"uniform sampler2D shadowMap\" + i + \";\");\n }\n }\n }\n\n src.push(\"uniform vec4 colorize;\");\n\n //================================================================================\n // MAIN\n //================================================================================\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n\n if (geometryState.primitiveName === \"points\") {\n src.push(\"vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\"float r = dot(cxy, cxy);\");\n src.push(\"if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\"}\");\n }\n\n src.push(\"float occlusion = 1.0;\");\n\n if (materialState.ambient) {\n src.push(\"vec3 ambientColor = materialAmbient;\");\n } else {\n src.push(\"vec3 ambientColor = vec3(1.0, 1.0, 1.0);\");\n }\n\n if (materialState.diffuse) {\n src.push(\"vec3 diffuseColor = materialDiffuse;\");\n } else if (materialState.baseColor) {\n src.push(\"vec3 diffuseColor = materialBaseColor;\");\n } else {\n src.push(\"vec3 diffuseColor = vec3(1.0, 1.0, 1.0);\");\n }\n\n if (geometryState.colors) {\n src.push(\"diffuseColor *= vColor.rgb;\");\n }\n\n if (materialState.emissive) {\n src.push(\"vec3 emissiveColor = materialEmissive;\"); // Emissive default is (0,0,0), so initializing here\n } else {\n src.push(\"vec3 emissiveColor = vec3(0.0, 0.0, 0.0);\");\n }\n\n if (materialState.specular) {\n src.push(\"vec3 specular = materialSpecular;\");\n } else {\n src.push(\"vec3 specular = vec3(1.0, 1.0, 1.0);\");\n }\n\n if (materialState.alpha !== undefined) {\n src.push(\"float alpha = materialAlphaModeCutoff[0];\");\n } else {\n src.push(\"float alpha = 1.0;\");\n }\n\n if (geometryState.colors) {\n src.push(\"alpha *= vColor.a;\");\n }\n\n if (materialState.glossiness !== undefined) {\n src.push(\"float glossiness = materialGlossiness;\");\n } else {\n src.push(\"float glossiness = 1.0;\");\n }\n\n if (materialState.metallic !== undefined) {\n src.push(\"float metallic = materialMetallic;\");\n } else {\n src.push(\"float metallic = 1.0;\");\n }\n\n if (materialState.roughness !== undefined) {\n src.push(\"float roughness = materialRoughness;\");\n } else {\n src.push(\"float roughness = 1.0;\");\n }\n\n if (materialState.specularF0 !== undefined) {\n src.push(\"float specularF0 = materialSpecularF0;\");\n } else {\n src.push(\"float specularF0 = 1.0;\");\n }\n\n //--------------------------------------------------------------------------------\n // TEXTURING\n //--------------------------------------------------------------------------------\n\n if (uvs && ((normals && material._normalMap)\n || material._ambientMap\n || material._baseColorMap\n || material._diffuseMap\n || material._occlusionMap\n || material._emissiveMap\n || material._metallicMap\n || material._roughnessMap\n || material._metallicRoughnessMap\n || material._specularMap\n || material._glossinessMap\n || material._specularGlossinessMap\n || material._alphaMap)) {\n src.push(\"vec4 texturePos = vec4(vUV.s, vUV.t, 1.0, 1.0);\");\n src.push(\"vec2 textureCoord;\");\n }\n\n if (uvs && material._ambientMap) {\n if (material._ambientMap._state.matrix) {\n src.push(\"textureCoord = (ambientMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"vec4 ambientTexel = texture(ambientMap, textureCoord).rgb;\");\n src.push(\"ambientTexel = \" + TEXTURE_DECODE_FUNCS$1[material._ambientMap._state.encoding] + \"(ambientTexel);\");\n src.push(\"ambientColor *= ambientTexel.rgb;\");\n }\n\n if (uvs && material._diffuseMap) {\n if (material._diffuseMap._state.matrix) {\n src.push(\"textureCoord = (diffuseMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"vec4 diffuseTexel = texture(diffuseMap, textureCoord);\");\n src.push(\"diffuseTexel = \" + TEXTURE_DECODE_FUNCS$1[material._diffuseMap._state.encoding] + \"(diffuseTexel);\");\n src.push(\"diffuseColor *= diffuseTexel.rgb;\");\n src.push(\"alpha *= diffuseTexel.a;\");\n }\n\n if (uvs && material._baseColorMap) {\n if (material._baseColorMap._state.matrix) {\n src.push(\"textureCoord = (baseColorMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"vec4 baseColorTexel = texture(baseColorMap, textureCoord);\");\n src.push(\"baseColorTexel = \" + TEXTURE_DECODE_FUNCS$1[material._baseColorMap._state.encoding] + \"(baseColorTexel);\");\n src.push(\"diffuseColor *= baseColorTexel.rgb;\");\n src.push(\"alpha *= baseColorTexel.a;\");\n }\n\n if (uvs && material._emissiveMap) {\n if (material._emissiveMap._state.matrix) {\n src.push(\"textureCoord = (emissiveMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"vec4 emissiveTexel = texture(emissiveMap, textureCoord);\");\n src.push(\"emissiveTexel = \" + TEXTURE_DECODE_FUNCS$1[material._emissiveMap._state.encoding] + \"(emissiveTexel);\");\n src.push(\"emissiveColor = emissiveTexel.rgb;\");\n }\n\n if (uvs && material._alphaMap) {\n if (material._alphaMap._state.matrix) {\n src.push(\"textureCoord = (alphaMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"alpha *= texture(alphaMap, textureCoord).r;\");\n }\n\n if (uvs && material._occlusionMap) {\n if (material._occlusionMap._state.matrix) {\n src.push(\"textureCoord = (occlusionMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"occlusion *= texture(occlusionMap, textureCoord).r;\");\n }\n\n if (normals && ((lightsState.lights.length > 0) || lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0)) {\n\n //--------------------------------------------------------------------------------\n // SHADING\n //--------------------------------------------------------------------------------\n\n if (uvs && material._normalMap) {\n if (material._normalMap._state.matrix) {\n src.push(\"textureCoord = (normalMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"vec3 viewNormal = perturbNormal2Arb( vViewPosition, normalize(vViewNormal), textureCoord );\");\n } else {\n src.push(\"vec3 viewNormal = normalize(vViewNormal);\");\n }\n\n if (uvs && material._specularMap) {\n if (material._specularMap._state.matrix) {\n src.push(\"textureCoord = (specularMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"specular *= texture(specularMap, textureCoord).rgb;\");\n }\n\n if (uvs && material._glossinessMap) {\n if (material._glossinessMap._state.matrix) {\n src.push(\"textureCoord = (glossinessMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"glossiness *= texture(glossinessMap, textureCoord).r;\");\n }\n\n if (uvs && material._specularGlossinessMap) {\n if (material._specularGlossinessMap._state.matrix) {\n src.push(\"textureCoord = (materialSpecularGlossinessMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"vec4 specGlossRGB = texture(materialSpecularGlossinessMap, textureCoord).rgba;\"); // TODO: what if only RGB texture?\n src.push(\"specular *= specGlossRGB.rgb;\");\n src.push(\"glossiness *= specGlossRGB.a;\");\n }\n\n if (uvs && material._metallicMap) {\n if (material._metallicMap._state.matrix) {\n src.push(\"textureCoord = (metallicMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"metallic *= texture(metallicMap, textureCoord).r;\");\n }\n\n if (uvs && material._roughnessMap) {\n if (material._roughnessMap._state.matrix) {\n src.push(\"textureCoord = (roughnessMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"roughness *= texture(roughnessMap, textureCoord).r;\");\n }\n\n if (uvs && material._metallicRoughnessMap) {\n if (material._metallicRoughnessMap._state.matrix) {\n src.push(\"textureCoord = (metallicRoughnessMapMatrix * texturePos).xy;\");\n } else {\n src.push(\"textureCoord = texturePos.xy;\");\n }\n src.push(\"vec3 metalRoughRGB = texture(metallicRoughnessMap, textureCoord).rgb;\");\n src.push(\"metallic *= metalRoughRGB.b;\");\n src.push(\"roughness *= metalRoughRGB.g;\");\n }\n\n src.push(\"vec3 viewEyeDir = normalize(-vViewPosition);\");\n\n if (material._diffuseFresnel) {\n src.push(\"float diffuseFresnel = fresnel(viewEyeDir, viewNormal, diffuseFresnelEdgeBias, diffuseFresnelCenterBias, diffuseFresnelPower);\");\n src.push(\"diffuseColor *= mix(diffuseFresnelEdgeColor, diffuseFresnelCenterColor, diffuseFresnel);\");\n }\n if (material._specularFresnel) {\n src.push(\"float specularFresnel = fresnel(viewEyeDir, viewNormal, specularFresnelEdgeBias, specularFresnelCenterBias, specularFresnelPower);\");\n src.push(\"specular *= mix(specularFresnelEdgeColor, specularFresnelCenterColor, specularFresnel);\");\n }\n if (material._alphaFresnel) {\n src.push(\"float alphaFresnel = fresnel(viewEyeDir, viewNormal, alphaFresnelEdgeBias, alphaFresnelCenterBias, alphaFresnelPower);\");\n src.push(\"alpha *= mix(alphaFresnelEdgeColor.r, alphaFresnelCenterColor.r, alphaFresnel);\");\n }\n if (material._emissiveFresnel) {\n src.push(\"float emissiveFresnel = fresnel(viewEyeDir, viewNormal, emissiveFresnelEdgeBias, emissiveFresnelCenterBias, emissiveFresnelPower);\");\n src.push(\"emissiveColor *= mix(emissiveFresnelEdgeColor, emissiveFresnelCenterColor, emissiveFresnel);\");\n }\n\n src.push(\"if (materialAlphaModeCutoff[1] == 1.0 && alpha < materialAlphaModeCutoff[2]) {\"); // ie. (alphaMode == \"mask\" && alpha < alphaCutoff)\n src.push(\" discard;\"); // TODO: Discard earlier within this shader?\n src.push(\"}\");\n\n // PREPARE INPUTS FOR SHADER FUNCTIONS\n\n src.push(\"IncidentLight light;\");\n src.push(\"Material material;\");\n src.push(\"Geometry geometry;\");\n src.push(\"ReflectedLight reflectedLight = ReflectedLight(vec3(0.0,0.0,0.0), vec3(0.0,0.0,0.0));\");\n src.push(\"vec3 viewLightDir;\");\n\n if (phongMaterial) {\n src.push(\"material.diffuseColor = diffuseColor;\");\n src.push(\"material.specularColor = specular;\");\n src.push(\"material.shine = materialShininess;\");\n }\n\n if (specularMaterial) {\n src.push(\"float oneMinusSpecularStrength = 1.0 - max(max(specular.r, specular.g ),specular.b);\"); // Energy conservation\n src.push(\"material.diffuseColor = diffuseColor * oneMinusSpecularStrength;\");\n src.push(\"material.specularRoughness = clamp( 1.0 - glossiness, 0.04, 1.0 );\");\n src.push(\"material.specularColor = specular;\");\n }\n\n if (metallicMaterial) {\n src.push(\"float dielectricSpecular = 0.16 * specularF0 * specularF0;\");\n src.push(\"material.diffuseColor = diffuseColor * (1.0 - dielectricSpecular) * (1.0 - metallic);\");\n src.push(\"material.specularRoughness = clamp(roughness, 0.04, 1.0);\");\n src.push(\"material.specularColor = mix(vec3(dielectricSpecular), diffuseColor, metallic);\");\n }\n\n src.push(\"geometry.position = vViewPosition;\");\n if (lightsState.lightMaps.length > 0) {\n src.push(\"geometry.worldNormal = normalize(vWorldNormal);\");\n }\n src.push(\"geometry.viewNormal = viewNormal;\");\n src.push(\"geometry.viewEyeDir = viewEyeDir;\");\n\n // ENVIRONMENT AND REFLECTION MAP SHADING\n\n if ((phongMaterial) && (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0)) {\n src.push(\"computePhongLightMapping(geometry, material, reflectedLight);\");\n }\n\n if ((specularMaterial || metallicMaterial) && (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0)) {\n src.push(\"computePBRLightMapping(geometry, material, reflectedLight);\");\n }\n\n // LIGHT SOURCE SHADING\n\n src.push(\"float shadow = 1.0;\");\n\n // if (receivesShadow) {\n //\n // src.push(\"float lightDepth2 = clamp(length(lightPos)/40.0, 0.0, 1.0);\");\n // src.push(\"float illuminated = VSM(sLightDepth, lightUV, lightDepth2);\");\n //\n src.push(\"float shadowAcneRemover = 0.007;\");\n src.push(\"vec3 fragmentDepth;\");\n src.push(\"float texelSize = 1.0 / 1024.0;\");\n src.push(\"float amountInLight = 0.0;\");\n src.push(\"vec3 shadowCoord;\");\n src.push('vec4 rgbaDepth;');\n src.push(\"float depth;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n\n const light = lightsState.lights[i];\n\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\" && light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightDir\" + i + \");\");\n } else if (light.type === \"point\" && light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightPos\" + i + \" - vViewPosition);\");\n //src.push(\"tmpVec3 = lightPos\" + i + \".xyz - viewPosition.xyz;\");\n //src.push(\"lightDist = abs(length(tmpVec3));\");\n } else {\n src.push(\"viewLightDir = normalize(vViewLightReverseDirAndDist\" + i + \".xyz);\"); // If normal mapping, the fragment->light vector will be in tangent space\n }\n\n if (receivesShadow && light.castsShadow) {\n\n // if (true) {\n // src.push('shadowCoord = (vShadowPosFromLight' + i + '.xyz/vShadowPosFromLight' + i + '.w)/2.0 + 0.5;');\n // src.push(\"lightDepth2 = clamp(length(vec3[0.0, 20.0, 20.0])/40.0, 0.0, 1.0);\");\n // src.push(\"castsShadow *= VSM(shadowMap' + i + ', shadowCoord, lightDepth2);\");\n // }\n //\n // if (false) {\n //\n // PCF\n\n src.push(\"shadow = 0.0;\");\n\n src.push(\"fragmentDepth = vShadowPosFromLight\" + i + \".xyz;\");\n src.push(\"fragmentDepth.z -= shadowAcneRemover;\");\n src.push(\"for (int x = -3; x <= 3; x++) {\");\n src.push(\" for (int y = -3; y <= 3; y++) {\");\n src.push(\" float texelDepth = unpackDepth(texture(shadowMap\" + i + \", fragmentDepth.xy + vec2(x, y) * texelSize));\");\n src.push(\" if (fragmentDepth.z < texelDepth) {\");\n src.push(\" shadow += 1.0;\");\n src.push(\" }\");\n src.push(\" }\");\n src.push(\"}\");\n\n src.push(\"shadow = shadow / 9.0;\");\n\n src.push(\"light.color = lightColor\" + i + \".rgb * (lightColor\" + i + \".a * shadow);\"); // a is intensity\n //\n // }\n //\n // if (false){\n //\n // src.push(\"shadow = 1.0;\");\n //\n // src.push('shadowCoord = (vShadowPosFromLight' + i + '.xyz/vShadowPosFromLight' + i + '.w)/2.0 + 0.5;');\n //\n // src.push('shadow -= (shadowCoord.z > unpackDepth(texture(shadowMap' + i + ', shadowCoord.xy + vec2( -0.94201624, -0.39906216 ) / 700.0)) + 0.0015) ? 0.2 : 0.0;');\n // src.push('shadow -= (shadowCoord.z > unpackDepth(texture(shadowMap' + i + ', shadowCoord.xy + vec2( 0.94558609, -0.76890725 ) / 700.0)) + 0.0015) ? 0.2 : 0.0;');\n // src.push('shadow -= (shadowCoord.z > unpackDepth(texture(shadowMap' + i + ', shadowCoord.xy + vec2( -0.094184101, -0.92938870 ) / 700.0)) + 0.0015) ? 0.2 : 0.0;');\n // src.push('shadow -= (shadowCoord.z > unpackDepth(texture(shadowMap' + i + ', shadowCoord.xy + vec2( 0.34495938, 0.29387760 ) / 700.0)) + 0.0015) ? 0.2 : 0.0;');\n //\n // src.push(\"light.color = lightColor\" + i + \".rgb * (lightColor\" + i + \".a * shadow);\");\n // }\n } else {\n src.push(\"light.color = lightColor\" + i + \".rgb * (lightColor\" + i + \".a );\"); // a is intensity\n }\n\n src.push(\"light.direction = viewLightDir;\");\n\n if (phongMaterial) {\n src.push(\"computePhongLighting(light, geometry, material, reflectedLight);\");\n }\n\n if (specularMaterial || metallicMaterial) {\n src.push(\"computePBRLighting(light, geometry, material, reflectedLight);\");\n }\n }\n\n //src.push(\"reflectedLight.diffuse *= shadow;\");\n\n // COMBINE TERMS\n\n if (phongMaterial) {\n src.push(\"vec3 outgoingLight = (lightAmbient.rgb * lightAmbient.a * diffuseColor) + ((occlusion * (( reflectedLight.diffuse + reflectedLight.specular)))) + emissiveColor;\");\n\n } else {\n src.push(\"vec3 outgoingLight = (occlusion * (reflectedLight.diffuse)) + (occlusion * reflectedLight.specular) + emissiveColor;\");\n }\n\n } else {\n\n //--------------------------------------------------------------------------------\n // NO SHADING - EMISSIVE and AMBIENT ONLY\n //--------------------------------------------------------------------------------\n\n src.push(\"ambientColor *= (lightAmbient.rgb * lightAmbient.a);\");\n\n src.push(\"vec3 outgoingLight = emissiveColor + ambientColor;\");\n }\n\n src.push(\"vec4 fragColor = vec4(outgoingLight, alpha) * colorize;\");\n\n if (gammaOutput) {\n src.push(\"fragColor = linearToGamma(fragColor, gammaFactor);\");\n }\n\n src.push(\"outColor = fragColor;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n\n return src;\n}\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\nconst tempVec3a$I = math.vec3();\n\nconst ids$2 = new Map$1({});\n\n/**\n * @private\n */\nconst DrawRenderer = function (hash, mesh) {\n this.id = ids$2.addItem({});\n this._hash = hash;\n this._scene = mesh.scene;\n this._useCount = 0;\n this._shaderSource = new DrawShaderSource(mesh);\n this._allocate(mesh);\n};\n\nconst drawRenderers = {};\n\nDrawRenderer.get = function (mesh) {\n const scene = mesh.scene;\n const hash = [\n scene.canvas.canvas.id,\n (scene.gammaInput ? \"gi;\" : \";\") + (scene.gammaOutput ? \"go\" : \"\"),\n scene._lightsState.getHash(),\n scene._sectionPlanesState.getHash(),\n mesh._geometry._state.hash,\n mesh._material._state.hash,\n mesh._state.drawHash\n ].join(\";\");\n let renderer = drawRenderers[hash];\n if (!renderer) {\n renderer = new DrawRenderer(hash, mesh);\n if (renderer.errors) {\n console.log(renderer.errors.join(\"\\n\"));\n return null;\n }\n drawRenderers[hash] = renderer;\n stats.memory.programs++;\n }\n renderer._useCount++;\n return renderer;\n};\n\nDrawRenderer.prototype.put = function () {\n if (--this._useCount === 0) {\n ids$2.removeItem(this.id);\n if (this._program) {\n this._program.destroy();\n }\n delete drawRenderers[this._hash];\n stats.memory.programs--;\n }\n};\n\nDrawRenderer.prototype.webglContextRestored = function () {\n this._program = null;\n};\n\nDrawRenderer.prototype.drawMesh = function (frameCtx, mesh) {\n\n if (!this._program) {\n this._allocate(mesh);\n }\n\n const maxTextureUnits = WEBGL_INFO.MAX_TEXTURE_UNITS;\n const scene = mesh.scene;\n const material = mesh._material;\n const gl = scene.canvas.gl;\n const program = this._program;\n const meshState = mesh._state;\n const materialState = mesh._material._state;\n const geometryState = mesh._geometry._state;\n const camera = scene.camera;\n const origin = mesh.origin;\n const background = meshState.background;\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n if (background) {\n gl.depthFunc(gl.LEQUAL);\n }\n this._bindProgram(frameCtx);\n }\n\n gl.uniformMatrix4fv(this._uViewMatrix, false, origin ? frameCtx.getRTCViewMatrix(meshState.originHash, origin) : camera.viewMatrix);\n gl.uniformMatrix4fv(this._uViewNormalMatrix, false, camera.viewNormalMatrix);\n\n if (meshState.clippable) {\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const renderFlags = mesh.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$I);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n }\n\n if (materialState.id !== this._lastMaterialId) {\n\n frameCtx.textureUnit = this._baseTextureUnit;\n\n const backfaces = materialState.backfaces;\n if (frameCtx.backfaces !== backfaces) {\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n\n const frontface = materialState.frontface;\n if (frameCtx.frontface !== frontface) {\n if (frontface) {\n gl.frontFace(gl.CCW);\n } else {\n gl.frontFace(gl.CW);\n }\n frameCtx.frontface = frontface;\n }\n\n if (frameCtx.lineWidth !== materialState.lineWidth) {\n gl.lineWidth(materialState.lineWidth);\n frameCtx.lineWidth = materialState.lineWidth;\n }\n\n if (this._uPointSize) {\n gl.uniform1f(this._uPointSize, materialState.pointSize);\n }\n\n switch (materialState.type) {\n case \"LambertMaterial\":\n if (this._uMaterialAmbient) {\n gl.uniform3fv(this._uMaterialAmbient, materialState.ambient);\n }\n if (this._uMaterialColor) {\n gl.uniform4f(this._uMaterialColor, materialState.color[0], materialState.color[1], materialState.color[2], materialState.alpha);\n }\n if (this._uMaterialEmissive) {\n gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);\n }\n break;\n\n case \"PhongMaterial\":\n if (this._uMaterialShininess) {\n gl.uniform1f(this._uMaterialShininess, materialState.shininess);\n }\n if (this._uMaterialAmbient) {\n gl.uniform3fv(this._uMaterialAmbient, materialState.ambient);\n }\n if (this._uMaterialDiffuse) {\n gl.uniform3fv(this._uMaterialDiffuse, materialState.diffuse);\n }\n if (this._uMaterialSpecular) {\n gl.uniform3fv(this._uMaterialSpecular, materialState.specular);\n }\n if (this._uMaterialEmissive) {\n gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);\n }\n if (this._uAlphaModeCutoff) {\n gl.uniform4f(\n this._uAlphaModeCutoff,\n 1.0 * materialState.alpha,\n materialState.alphaMode === 1 ? 1.0 : 0.0,\n materialState.alphaCutoff,\n 0);\n }\n if (material._ambientMap && material._ambientMap._state.texture && this._uMaterialAmbientMap) {\n program.bindTexture(this._uMaterialAmbientMap, material._ambientMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uMaterialAmbientMapMatrix) {\n gl.uniformMatrix4fv(this._uMaterialAmbientMapMatrix, false, material._ambientMap._state.matrix);\n }\n }\n if (material._diffuseMap && material._diffuseMap._state.texture && this._uDiffuseMap) {\n program.bindTexture(this._uDiffuseMap, material._diffuseMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uDiffuseMapMatrix) {\n gl.uniformMatrix4fv(this._uDiffuseMapMatrix, false, material._diffuseMap._state.matrix);\n }\n }\n if (material._specularMap && material._specularMap._state.texture && this._uSpecularMap) {\n program.bindTexture(this._uSpecularMap, material._specularMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uSpecularMapMatrix) {\n gl.uniformMatrix4fv(this._uSpecularMapMatrix, false, material._specularMap._state.matrix);\n }\n }\n if (material._emissiveMap && material._emissiveMap._state.texture && this._uEmissiveMap) {\n program.bindTexture(this._uEmissiveMap, material._emissiveMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uEmissiveMapMatrix) {\n gl.uniformMatrix4fv(this._uEmissiveMapMatrix, false, material._emissiveMap._state.matrix);\n }\n }\n if (material._alphaMap && material._alphaMap._state.texture && this._uAlphaMap) {\n program.bindTexture(this._uAlphaMap, material._alphaMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uAlphaMapMatrix) {\n gl.uniformMatrix4fv(this._uAlphaMapMatrix, false, material._alphaMap._state.matrix);\n }\n }\n if (material._reflectivityMap && material._reflectivityMap._state.texture && this._uReflectivityMap) {\n program.bindTexture(this._uReflectivityMap, material._reflectivityMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n if (this._uReflectivityMapMatrix) {\n gl.uniformMatrix4fv(this._uReflectivityMapMatrix, false, material._reflectivityMap._state.matrix);\n }\n }\n if (material._normalMap && material._normalMap._state.texture && this._uNormalMap) {\n program.bindTexture(this._uNormalMap, material._normalMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uNormalMapMatrix) {\n gl.uniformMatrix4fv(this._uNormalMapMatrix, false, material._normalMap._state.matrix);\n }\n }\n if (material._occlusionMap && material._occlusionMap._state.texture && this._uOcclusionMap) {\n program.bindTexture(this._uOcclusionMap, material._occlusionMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uOcclusionMapMatrix) {\n gl.uniformMatrix4fv(this._uOcclusionMapMatrix, false, material._occlusionMap._state.matrix);\n }\n }\n if (material._diffuseFresnel) {\n if (this._uDiffuseFresnelEdgeBias) {\n gl.uniform1f(this._uDiffuseFresnelEdgeBias, material._diffuseFresnel.edgeBias);\n }\n if (this._uDiffuseFresnelCenterBias) {\n gl.uniform1f(this._uDiffuseFresnelCenterBias, material._diffuseFresnel.centerBias);\n }\n if (this._uDiffuseFresnelEdgeColor) {\n gl.uniform3fv(this._uDiffuseFresnelEdgeColor, material._diffuseFresnel.edgeColor);\n }\n if (this._uDiffuseFresnelCenterColor) {\n gl.uniform3fv(this._uDiffuseFresnelCenterColor, material._diffuseFresnel.centerColor);\n }\n if (this._uDiffuseFresnelPower) {\n gl.uniform1f(this._uDiffuseFresnelPower, material._diffuseFresnel.power);\n }\n }\n if (material._specularFresnel) {\n if (this._uSpecularFresnelEdgeBias) {\n gl.uniform1f(this._uSpecularFresnelEdgeBias, material._specularFresnel.edgeBias);\n }\n if (this._uSpecularFresnelCenterBias) {\n gl.uniform1f(this._uSpecularFresnelCenterBias, material._specularFresnel.centerBias);\n }\n if (this._uSpecularFresnelEdgeColor) {\n gl.uniform3fv(this._uSpecularFresnelEdgeColor, material._specularFresnel.edgeColor);\n }\n if (this._uSpecularFresnelCenterColor) {\n gl.uniform3fv(this._uSpecularFresnelCenterColor, material._specularFresnel.centerColor);\n }\n if (this._uSpecularFresnelPower) {\n gl.uniform1f(this._uSpecularFresnelPower, material._specularFresnel.power);\n }\n }\n if (material._alphaFresnel) {\n if (this._uAlphaFresnelEdgeBias) {\n gl.uniform1f(this._uAlphaFresnelEdgeBias, material._alphaFresnel.edgeBias);\n }\n if (this._uAlphaFresnelCenterBias) {\n gl.uniform1f(this._uAlphaFresnelCenterBias, material._alphaFresnel.centerBias);\n }\n if (this._uAlphaFresnelEdgeColor) {\n gl.uniform3fv(this._uAlphaFresnelEdgeColor, material._alphaFresnel.edgeColor);\n }\n if (this._uAlphaFresnelCenterColor) {\n gl.uniform3fv(this._uAlphaFresnelCenterColor, material._alphaFresnel.centerColor);\n }\n if (this._uAlphaFresnelPower) {\n gl.uniform1f(this._uAlphaFresnelPower, material._alphaFresnel.power);\n }\n }\n if (material._reflectivityFresnel) {\n if (this._uReflectivityFresnelEdgeBias) {\n gl.uniform1f(this._uReflectivityFresnelEdgeBias, material._reflectivityFresnel.edgeBias);\n }\n if (this._uReflectivityFresnelCenterBias) {\n gl.uniform1f(this._uReflectivityFresnelCenterBias, material._reflectivityFresnel.centerBias);\n }\n if (this._uReflectivityFresnelEdgeColor) {\n gl.uniform3fv(this._uReflectivityFresnelEdgeColor, material._reflectivityFresnel.edgeColor);\n }\n if (this._uReflectivityFresnelCenterColor) {\n gl.uniform3fv(this._uReflectivityFresnelCenterColor, material._reflectivityFresnel.centerColor);\n }\n if (this._uReflectivityFresnelPower) {\n gl.uniform1f(this._uReflectivityFresnelPower, material._reflectivityFresnel.power);\n }\n }\n if (material._emissiveFresnel) {\n if (this._uEmissiveFresnelEdgeBias) {\n gl.uniform1f(this._uEmissiveFresnelEdgeBias, material._emissiveFresnel.edgeBias);\n }\n if (this._uEmissiveFresnelCenterBias) {\n gl.uniform1f(this._uEmissiveFresnelCenterBias, material._emissiveFresnel.centerBias);\n }\n if (this._uEmissiveFresnelEdgeColor) {\n gl.uniform3fv(this._uEmissiveFresnelEdgeColor, material._emissiveFresnel.edgeColor);\n }\n if (this._uEmissiveFresnelCenterColor) {\n gl.uniform3fv(this._uEmissiveFresnelCenterColor, material._emissiveFresnel.centerColor);\n }\n if (this._uEmissiveFresnelPower) {\n gl.uniform1f(this._uEmissiveFresnelPower, material._emissiveFresnel.power);\n }\n }\n break;\n\n case \"MetallicMaterial\":\n if (this._uBaseColor) {\n gl.uniform3fv(this._uBaseColor, materialState.baseColor);\n }\n if (this._uMaterialMetallic) {\n gl.uniform1f(this._uMaterialMetallic, materialState.metallic);\n }\n if (this._uMaterialRoughness) {\n gl.uniform1f(this._uMaterialRoughness, materialState.roughness);\n }\n if (this._uMaterialSpecularF0) {\n gl.uniform1f(this._uMaterialSpecularF0, materialState.specularF0);\n }\n if (this._uMaterialEmissive) {\n gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);\n }\n if (this._uAlphaModeCutoff) {\n gl.uniform4f(\n this._uAlphaModeCutoff,\n 1.0 * materialState.alpha,\n materialState.alphaMode === 1 ? 1.0 : 0.0,\n materialState.alphaCutoff,\n 0.0);\n }\n const baseColorMap = material._baseColorMap;\n if (baseColorMap && baseColorMap._state.texture && this._uBaseColorMap) {\n program.bindTexture(this._uBaseColorMap, baseColorMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uBaseColorMapMatrix) {\n gl.uniformMatrix4fv(this._uBaseColorMapMatrix, false, baseColorMap._state.matrix);\n }\n }\n const metallicMap = material._metallicMap;\n if (metallicMap && metallicMap._state.texture && this._uMetallicMap) {\n program.bindTexture(this._uMetallicMap, metallicMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uMetallicMapMatrix) {\n gl.uniformMatrix4fv(this._uMetallicMapMatrix, false, metallicMap._state.matrix);\n }\n }\n const roughnessMap = material._roughnessMap;\n if (roughnessMap && roughnessMap._state.texture && this._uRoughnessMap) {\n program.bindTexture(this._uRoughnessMap, roughnessMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uRoughnessMapMatrix) {\n gl.uniformMatrix4fv(this._uRoughnessMapMatrix, false, roughnessMap._state.matrix);\n }\n }\n const metallicRoughnessMap = material._metallicRoughnessMap;\n if (metallicRoughnessMap && metallicRoughnessMap._state.texture && this._uMetallicRoughnessMap) {\n program.bindTexture(this._uMetallicRoughnessMap, metallicRoughnessMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uMetallicRoughnessMapMatrix) {\n gl.uniformMatrix4fv(this._uMetallicRoughnessMapMatrix, false, metallicRoughnessMap._state.matrix);\n }\n }\n var emissiveMap = material._emissiveMap;\n if (emissiveMap && emissiveMap._state.texture && this._uEmissiveMap) {\n program.bindTexture(this._uEmissiveMap, emissiveMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uEmissiveMapMatrix) {\n gl.uniformMatrix4fv(this._uEmissiveMapMatrix, false, emissiveMap._state.matrix);\n }\n }\n var occlusionMap = material._occlusionMap;\n if (occlusionMap && material._occlusionMap._state.texture && this._uOcclusionMap) {\n program.bindTexture(this._uOcclusionMap, occlusionMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uOcclusionMapMatrix) {\n gl.uniformMatrix4fv(this._uOcclusionMapMatrix, false, occlusionMap._state.matrix);\n }\n }\n var alphaMap = material._alphaMap;\n if (alphaMap && alphaMap._state.texture && this._uAlphaMap) {\n program.bindTexture(this._uAlphaMap, alphaMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uAlphaMapMatrix) {\n gl.uniformMatrix4fv(this._uAlphaMapMatrix, false, alphaMap._state.matrix);\n }\n }\n var normalMap = material._normalMap;\n if (normalMap && normalMap._state.texture && this._uNormalMap) {\n program.bindTexture(this._uNormalMap, normalMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uNormalMapMatrix) {\n gl.uniformMatrix4fv(this._uNormalMapMatrix, false, normalMap._state.matrix);\n }\n }\n break;\n\n case \"SpecularMaterial\":\n if (this._uMaterialDiffuse) {\n gl.uniform3fv(this._uMaterialDiffuse, materialState.diffuse);\n }\n if (this._uMaterialSpecular) {\n gl.uniform3fv(this._uMaterialSpecular, materialState.specular);\n }\n if (this._uMaterialGlossiness) {\n gl.uniform1f(this._uMaterialGlossiness, materialState.glossiness);\n }\n if (this._uMaterialReflectivity) {\n gl.uniform1f(this._uMaterialReflectivity, materialState.reflectivity);\n }\n if (this._uMaterialEmissive) {\n gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);\n }\n if (this._uAlphaModeCutoff) {\n gl.uniform4f(\n this._uAlphaModeCutoff,\n 1.0 * materialState.alpha,\n materialState.alphaMode === 1 ? 1.0 : 0.0,\n materialState.alphaCutoff,\n 0.0);\n }\n const diffuseMap = material._diffuseMap;\n if (diffuseMap && diffuseMap._state.texture && this._uDiffuseMap) {\n program.bindTexture(this._uDiffuseMap, diffuseMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uDiffuseMapMatrix) {\n gl.uniformMatrix4fv(this._uDiffuseMapMatrix, false, diffuseMap._state.matrix);\n }\n }\n const specularMap = material._specularMap;\n if (specularMap && specularMap._state.texture && this._uSpecularMap) {\n program.bindTexture(this._uSpecularMap, specularMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uSpecularMapMatrix) {\n gl.uniformMatrix4fv(this._uSpecularMapMatrix, false, specularMap._state.matrix);\n }\n }\n const glossinessMap = material._glossinessMap;\n if (glossinessMap && glossinessMap._state.texture && this._uGlossinessMap) {\n program.bindTexture(this._uGlossinessMap, glossinessMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uGlossinessMapMatrix) {\n gl.uniformMatrix4fv(this._uGlossinessMapMatrix, false, glossinessMap._state.matrix);\n }\n }\n const specularGlossinessMap = material._specularGlossinessMap;\n if (specularGlossinessMap && specularGlossinessMap._state.texture && this._uSpecularGlossinessMap) {\n program.bindTexture(this._uSpecularGlossinessMap, specularGlossinessMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uSpecularGlossinessMapMatrix) {\n gl.uniformMatrix4fv(this._uSpecularGlossinessMapMatrix, false, specularGlossinessMap._state.matrix);\n }\n }\n var emissiveMap = material._emissiveMap;\n if (emissiveMap && emissiveMap._state.texture && this._uEmissiveMap) {\n program.bindTexture(this._uEmissiveMap, emissiveMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uEmissiveMapMatrix) {\n gl.uniformMatrix4fv(this._uEmissiveMapMatrix, false, emissiveMap._state.matrix);\n }\n }\n var occlusionMap = material._occlusionMap;\n if (occlusionMap && occlusionMap._state.texture && this._uOcclusionMap) {\n program.bindTexture(this._uOcclusionMap, occlusionMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uOcclusionMapMatrix) {\n gl.uniformMatrix4fv(this._uOcclusionMapMatrix, false, occlusionMap._state.matrix);\n }\n }\n var alphaMap = material._alphaMap;\n if (alphaMap && alphaMap._state.texture && this._uAlphaMap) {\n program.bindTexture(this._uAlphaMap, alphaMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uAlphaMapMatrix) {\n gl.uniformMatrix4fv(this._uAlphaMapMatrix, false, alphaMap._state.matrix);\n }\n }\n var normalMap = material._normalMap;\n if (normalMap && normalMap._state.texture && this._uNormalMap) {\n program.bindTexture(this._uNormalMap, normalMap._state.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n if (this._uNormalMapMatrix) {\n gl.uniformMatrix4fv(this._uNormalMapMatrix, false, normalMap._state.matrix);\n }\n }\n break;\n }\n this._lastMaterialId = materialState.id;\n }\n\n gl.uniformMatrix4fv(this._uModelMatrix, gl.FALSE, mesh.worldMatrix);\n if (this._uModelNormalMatrix) {\n gl.uniformMatrix4fv(this._uModelNormalMatrix, gl.FALSE, mesh.worldNormalMatrix);\n }\n\n if (this._uClippable) {\n gl.uniform1i(this._uClippable, meshState.clippable);\n }\n\n if (this._uColorize) {\n const colorize = meshState.colorize;\n const lastColorize = this._lastColorize;\n if (lastColorize[0] !== colorize[0] ||\n lastColorize[1] !== colorize[1] ||\n lastColorize[2] !== colorize[2] ||\n lastColorize[3] !== colorize[3]) {\n gl.uniform4fv(this._uColorize, colorize);\n lastColorize[0] = colorize[0];\n lastColorize[1] = colorize[1];\n lastColorize[2] = colorize[2];\n lastColorize[3] = colorize[3];\n }\n }\n\n gl.uniform3fv(this._uOffset, meshState.offset);\n\n // Bind VBOs\n\n if (geometryState.id !== this._lastGeometryId) {\n if (this._uPositionsDecodeMatrix) {\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);\n }\n if (this._uUVDecodeMatrix) {\n gl.uniformMatrix3fv(this._uUVDecodeMatrix, false, geometryState.uvDecodeMatrix);\n }\n if (this._aPosition) {\n this._aPosition.bindArrayBuffer(geometryState.positionsBuf);\n frameCtx.bindArray++;\n }\n if (this._aNormal) {\n this._aNormal.bindArrayBuffer(geometryState.normalsBuf);\n frameCtx.bindArray++;\n }\n if (this._aUV) {\n this._aUV.bindArrayBuffer(geometryState.uvBuf);\n frameCtx.bindArray++;\n }\n if (this._aColor) {\n this._aColor.bindArrayBuffer(geometryState.colorsBuf);\n frameCtx.bindArray++;\n }\n if (this._aFlags) {\n this._aFlags.bindArrayBuffer(geometryState.flagsBuf);\n frameCtx.bindArray++;\n }\n if (geometryState.indicesBuf) {\n geometryState.indicesBuf.bind();\n frameCtx.bindArray++;\n }\n this._lastGeometryId = geometryState.id;\n }\n\n // Draw (indices bound in prev step)\n\n if (geometryState.indicesBuf) {\n gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);\n frameCtx.drawElements++;\n } else if (geometryState.positions) {\n gl.drawArrays(gl.TRIANGLES, 0, geometryState.positions.numItems);\n frameCtx.drawArrays++;\n }\n\n if (background) {\n gl.depthFunc(gl.LESS);\n }\n};\n\nDrawRenderer.prototype._allocate = function (mesh) {\n const scene = mesh.scene;\n const gl = scene.canvas.gl;\n const material = mesh._material;\n const lightsState = scene._lightsState;\n const sectionPlanesState = scene._sectionPlanesState;\n const materialState = mesh._material._state;\n\n this._program = new Program(gl, this._shaderSource);\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uPositionsDecodeMatrix = program.getLocation(\"positionsDecodeMatrix\");\n this._uUVDecodeMatrix = program.getLocation(\"uvDecodeMatrix\");\n this._uModelMatrix = program.getLocation(\"modelMatrix\");\n this._uModelNormalMatrix = program.getLocation(\"modelNormalMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uViewNormalMatrix = program.getLocation(\"viewNormalMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uGammaFactor = program.getLocation(\"gammaFactor\");\n this._uLightAmbient = [];\n this._uLightColor = [];\n this._uLightDir = [];\n this._uLightPos = [];\n this._uLightAttenuation = [];\n this._uShadowViewMatrix = [];\n this._uShadowProjMatrix = [];\n\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n const lights = lightsState.lights;\n let light;\n\n for (var i = 0, len = lights.length; i < len; i++) {\n light = lights[i];\n switch (light.type) {\n\n case \"ambient\":\n this._uLightAmbient[i] = program.getLocation(\"lightAmbient\");\n break;\n\n case \"dir\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = null;\n this._uLightDir[i] = program.getLocation(\"lightDir\" + i);\n break;\n\n case \"point\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = program.getLocation(\"lightPos\" + i);\n this._uLightDir[i] = null;\n this._uLightAttenuation[i] = program.getLocation(\"lightAttenuation\" + i);\n break;\n\n case \"spot\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = program.getLocation(\"lightPos\" + i);\n this._uLightDir[i] = program.getLocation(\"lightDir\" + i);\n this._uLightAttenuation[i] = program.getLocation(\"lightAttenuation\" + i);\n break;\n }\n\n if (light.castsShadow) {\n this._uShadowViewMatrix[i] = program.getLocation(\"shadowViewMatrix\" + i);\n this._uShadowProjMatrix[i] = program.getLocation(\"shadowProjMatrix\" + i);\n }\n }\n\n if (lightsState.lightMaps.length > 0) {\n this._uLightMap = \"lightMap\";\n }\n\n if (lightsState.reflectionMaps.length > 0) {\n this._uReflectionMap = \"reflectionMap\";\n }\n\n this._uSectionPlanes = [];\n const sectionPlanes = sectionPlanesState.sectionPlanes;\n for (var i = 0, len = sectionPlanes.length; i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n\n this._uPointSize = program.getLocation(\"pointSize\");\n\n switch (materialState.type) {\n case \"LambertMaterial\":\n this._uMaterialColor = program.getLocation(\"materialColor\");\n this._uMaterialEmissive = program.getLocation(\"materialEmissive\");\n this._uAlphaModeCutoff = program.getLocation(\"materialAlphaModeCutoff\");\n break;\n\n case \"PhongMaterial\":\n this._uMaterialAmbient = program.getLocation(\"materialAmbient\");\n this._uMaterialDiffuse = program.getLocation(\"materialDiffuse\");\n this._uMaterialSpecular = program.getLocation(\"materialSpecular\");\n this._uMaterialEmissive = program.getLocation(\"materialEmissive\");\n this._uAlphaModeCutoff = program.getLocation(\"materialAlphaModeCutoff\");\n this._uMaterialShininess = program.getLocation(\"materialShininess\");\n if (material._ambientMap) {\n this._uMaterialAmbientMap = \"ambientMap\";\n this._uMaterialAmbientMapMatrix = program.getLocation(\"ambientMapMatrix\");\n }\n if (material._diffuseMap) {\n this._uDiffuseMap = \"diffuseMap\";\n this._uDiffuseMapMatrix = program.getLocation(\"diffuseMapMatrix\");\n }\n if (material._specularMap) {\n this._uSpecularMap = \"specularMap\";\n this._uSpecularMapMatrix = program.getLocation(\"specularMapMatrix\");\n }\n if (material._emissiveMap) {\n this._uEmissiveMap = \"emissiveMap\";\n this._uEmissiveMapMatrix = program.getLocation(\"emissiveMapMatrix\");\n }\n if (material._alphaMap) {\n this._uAlphaMap = \"alphaMap\";\n this._uAlphaMapMatrix = program.getLocation(\"alphaMapMatrix\");\n }\n if (material._reflectivityMap) {\n this._uReflectivityMap = \"reflectivityMap\";\n this._uReflectivityMapMatrix = program.getLocation(\"reflectivityMapMatrix\");\n }\n if (material._normalMap) {\n this._uNormalMap = \"normalMap\";\n this._uNormalMapMatrix = program.getLocation(\"normalMapMatrix\");\n }\n if (material._occlusionMap) {\n this._uOcclusionMap = \"occlusionMap\";\n this._uOcclusionMapMatrix = program.getLocation(\"occlusionMapMatrix\");\n }\n if (material._diffuseFresnel) {\n this._uDiffuseFresnelEdgeBias = program.getLocation(\"diffuseFresnelEdgeBias\");\n this._uDiffuseFresnelCenterBias = program.getLocation(\"diffuseFresnelCenterBias\");\n this._uDiffuseFresnelEdgeColor = program.getLocation(\"diffuseFresnelEdgeColor\");\n this._uDiffuseFresnelCenterColor = program.getLocation(\"diffuseFresnelCenterColor\");\n this._uDiffuseFresnelPower = program.getLocation(\"diffuseFresnelPower\");\n }\n if (material._specularFresnel) {\n this._uSpecularFresnelEdgeBias = program.getLocation(\"specularFresnelEdgeBias\");\n this._uSpecularFresnelCenterBias = program.getLocation(\"specularFresnelCenterBias\");\n this._uSpecularFresnelEdgeColor = program.getLocation(\"specularFresnelEdgeColor\");\n this._uSpecularFresnelCenterColor = program.getLocation(\"specularFresnelCenterColor\");\n this._uSpecularFresnelPower = program.getLocation(\"specularFresnelPower\");\n }\n if (material._alphaFresnel) {\n this._uAlphaFresnelEdgeBias = program.getLocation(\"alphaFresnelEdgeBias\");\n this._uAlphaFresnelCenterBias = program.getLocation(\"alphaFresnelCenterBias\");\n this._uAlphaFresnelEdgeColor = program.getLocation(\"alphaFresnelEdgeColor\");\n this._uAlphaFresnelCenterColor = program.getLocation(\"alphaFresnelCenterColor\");\n this._uAlphaFresnelPower = program.getLocation(\"alphaFresnelPower\");\n }\n if (material._reflectivityFresnel) {\n this._uReflectivityFresnelEdgeBias = program.getLocation(\"reflectivityFresnelEdgeBias\");\n this._uReflectivityFresnelCenterBias = program.getLocation(\"reflectivityFresnelCenterBias\");\n this._uReflectivityFresnelEdgeColor = program.getLocation(\"reflectivityFresnelEdgeColor\");\n this._uReflectivityFresnelCenterColor = program.getLocation(\"reflectivityFresnelCenterColor\");\n this._uReflectivityFresnelPower = program.getLocation(\"reflectivityFresnelPower\");\n }\n if (material._emissiveFresnel) {\n this._uEmissiveFresnelEdgeBias = program.getLocation(\"emissiveFresnelEdgeBias\");\n this._uEmissiveFresnelCenterBias = program.getLocation(\"emissiveFresnelCenterBias\");\n this._uEmissiveFresnelEdgeColor = program.getLocation(\"emissiveFresnelEdgeColor\");\n this._uEmissiveFresnelCenterColor = program.getLocation(\"emissiveFresnelCenterColor\");\n this._uEmissiveFresnelPower = program.getLocation(\"emissiveFresnelPower\");\n }\n break;\n\n case \"MetallicMaterial\":\n this._uBaseColor = program.getLocation(\"materialBaseColor\");\n this._uMaterialMetallic = program.getLocation(\"materialMetallic\");\n this._uMaterialRoughness = program.getLocation(\"materialRoughness\");\n this._uMaterialSpecularF0 = program.getLocation(\"materialSpecularF0\");\n this._uMaterialEmissive = program.getLocation(\"materialEmissive\");\n this._uAlphaModeCutoff = program.getLocation(\"materialAlphaModeCutoff\");\n if (material._baseColorMap) {\n this._uBaseColorMap = \"baseColorMap\";\n this._uBaseColorMapMatrix = program.getLocation(\"baseColorMapMatrix\");\n }\n if (material._metallicMap) {\n this._uMetallicMap = \"metallicMap\";\n this._uMetallicMapMatrix = program.getLocation(\"metallicMapMatrix\");\n }\n if (material._roughnessMap) {\n this._uRoughnessMap = \"roughnessMap\";\n this._uRoughnessMapMatrix = program.getLocation(\"roughnessMapMatrix\");\n }\n if (material._metallicRoughnessMap) {\n this._uMetallicRoughnessMap = \"metallicRoughnessMap\";\n this._uMetallicRoughnessMapMatrix = program.getLocation(\"metallicRoughnessMapMatrix\");\n }\n if (material._emissiveMap) {\n this._uEmissiveMap = \"emissiveMap\";\n this._uEmissiveMapMatrix = program.getLocation(\"emissiveMapMatrix\");\n }\n if (material._occlusionMap) {\n this._uOcclusionMap = \"occlusionMap\";\n this._uOcclusionMapMatrix = program.getLocation(\"occlusionMapMatrix\");\n }\n if (material._alphaMap) {\n this._uAlphaMap = \"alphaMap\";\n this._uAlphaMapMatrix = program.getLocation(\"alphaMapMatrix\");\n }\n if (material._normalMap) {\n this._uNormalMap = \"normalMap\";\n this._uNormalMapMatrix = program.getLocation(\"normalMapMatrix\");\n }\n break;\n\n case \"SpecularMaterial\":\n this._uMaterialDiffuse = program.getLocation(\"materialDiffuse\");\n this._uMaterialSpecular = program.getLocation(\"materialSpecular\");\n this._uMaterialGlossiness = program.getLocation(\"materialGlossiness\");\n this._uMaterialReflectivity = program.getLocation(\"reflectivityFresnel\");\n this._uMaterialEmissive = program.getLocation(\"materialEmissive\");\n this._uAlphaModeCutoff = program.getLocation(\"materialAlphaModeCutoff\");\n if (material._diffuseMap) {\n this._uDiffuseMap = \"diffuseMap\";\n this._uDiffuseMapMatrix = program.getLocation(\"diffuseMapMatrix\");\n }\n if (material._specularMap) {\n this._uSpecularMap = \"specularMap\";\n this._uSpecularMapMatrix = program.getLocation(\"specularMapMatrix\");\n }\n if (material._glossinessMap) {\n this._uGlossinessMap = \"glossinessMap\";\n this._uGlossinessMapMatrix = program.getLocation(\"glossinessMapMatrix\");\n }\n if (material._specularGlossinessMap) {\n this._uSpecularGlossinessMap = \"materialSpecularGlossinessMap\";\n this._uSpecularGlossinessMapMatrix = program.getLocation(\"materialSpecularGlossinessMapMatrix\");\n }\n if (material._emissiveMap) {\n this._uEmissiveMap = \"emissiveMap\";\n this._uEmissiveMapMatrix = program.getLocation(\"emissiveMapMatrix\");\n }\n if (material._occlusionMap) {\n this._uOcclusionMap = \"occlusionMap\";\n this._uOcclusionMapMatrix = program.getLocation(\"occlusionMapMatrix\");\n }\n if (material._alphaMap) {\n this._uAlphaMap = \"alphaMap\";\n this._uAlphaMapMatrix = program.getLocation(\"alphaMapMatrix\");\n }\n if (material._normalMap) {\n this._uNormalMap = \"normalMap\";\n this._uNormalMapMatrix = program.getLocation(\"normalMapMatrix\");\n }\n break;\n }\n\n this._aPosition = program.getAttribute(\"position\");\n this._aNormal = program.getAttribute(\"normal\");\n this._aUV = program.getAttribute(\"uv\");\n this._aColor = program.getAttribute(\"color\");\n this._aFlags = program.getAttribute(\"flags\");\n\n this._uClippable = program.getLocation(\"clippable\");\n this._uColorize = program.getLocation(\"colorize\");\n this._uOffset = program.getLocation(\"offset\");\n\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n\n this._lastColorize = new Float32Array(4);\n\n this._baseTextureUnit = 0;\n\n};\n\nDrawRenderer.prototype._bindProgram = function (frameCtx) {\n\n const maxTextureUnits = WEBGL_INFO.MAX_TEXTURE_UNITS;\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const lightsState = scene._lightsState;\n const project = scene.camera.project;\n let light;\n\n const program = this._program;\n\n program.bind();\n\n frameCtx.useProgram++;\n frameCtx.textureUnit = 0;\n\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n\n this._lastColorize[0] = -1;\n this._lastColorize[1] = -1;\n this._lastColorize[2] = -1;\n this._lastColorize[3] = -1;\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, project.matrix);\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n for (var i = 0, len = lightsState.lights.length; i < len; i++) {\n\n light = lightsState.lights[i];\n\n if (this._uLightAmbient[i]) {\n gl.uniform4f(this._uLightAmbient[i], light.color[0], light.color[1], light.color[2], light.intensity);\n\n } else {\n\n if (this._uLightColor[i]) {\n gl.uniform4f(this._uLightColor[i], light.color[0], light.color[1], light.color[2], light.intensity);\n }\n\n if (this._uLightPos[i]) {\n gl.uniform3fv(this._uLightPos[i], light.pos);\n if (this._uLightAttenuation[i]) {\n gl.uniform1f(this._uLightAttenuation[i], light.attenuation);\n }\n }\n\n if (this._uLightDir[i]) {\n gl.uniform3fv(this._uLightDir[i], light.dir);\n }\n\n if (light.castsShadow) {\n if (this._uShadowViewMatrix[i]) {\n gl.uniformMatrix4fv(this._uShadowViewMatrix[i], false, light.getShadowViewMatrix());\n }\n if (this._uShadowProjMatrix[i]) {\n gl.uniformMatrix4fv(this._uShadowProjMatrix[i], false, light.getShadowProjMatrix());\n }\n const shadowRenderBuf = light.getShadowRenderBuf();\n if (shadowRenderBuf) {\n program.bindTexture(\"shadowMap\" + i, shadowRenderBuf.getTexture(), frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n }\n }\n }\n }\n\n if (lightsState.lightMaps.length > 0 && lightsState.lightMaps[0].texture && this._uLightMap) {\n program.bindTexture(this._uLightMap, lightsState.lightMaps[0].texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n }\n\n if (lightsState.reflectionMaps.length > 0 && lightsState.reflectionMaps[0].texture && this._uReflectionMap) {\n program.bindTexture(this._uReflectionMap, lightsState.reflectionMaps[0].texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n }\n\n if (this._uGammaFactor) {\n gl.uniform1f(this._uGammaFactor, scene.gammaFactor);\n }\n\n this._baseTextureUnit = frameCtx.textureUnit;\n};\n\n/**\n * @private\n */\nclass EmphasisFillShaderSource {\n constructor(mesh) {\n this.vertex = buildVertex$5(mesh);\n this.fragment = buildFragment$5(mesh);\n }\n}\n\nfunction buildVertex$5(mesh) {\n\n const scene = mesh.scene;\n const lightsState = scene._lightsState;\n const normals = hasNormals(mesh);\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const quantizedGeometry = !!mesh._geometry._state.compressGeometry;\n const billboard = mesh._state.billboard;\n const stationary = mesh._state.stationary;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// EmphasisFillShaderSource vertex shader\");\n src.push(\"in vec3 position;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"uniform vec4 colorize;\");\n src.push(\"uniform vec3 offset;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n src.push(\"uniform vec4 lightAmbient;\");\n src.push(\"uniform vec4 fillColor;\");\n if (normals) {\n src.push(\"in vec3 normal;\");\n src.push(\"uniform mat4 modelNormalMatrix;\");\n src.push(\"uniform mat4 viewNormalMatrix;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n }\n if (quantizedGeometry) {\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n }\n }\n src.push(\"out vec4 vColor;\");\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"void billboard(inout mat4 mat) {\");\n src.push(\" mat[0][0] = 1.0;\");\n src.push(\" mat[0][1] = 0.0;\");\n src.push(\" mat[0][2] = 0.0;\");\n if (billboard === \"spherical\") {\n src.push(\" mat[1][0] = 0.0;\");\n src.push(\" mat[1][1] = 1.0;\");\n src.push(\" mat[1][2] = 0.0;\");\n }\n src.push(\" mat[2][0] = 0.0;\");\n src.push(\" mat[2][1] = 0.0;\");\n src.push(\" mat[2][2] =1.0;\");\n src.push(\"}\");\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n src.push(\"vec4 worldPosition;\");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n if (normals) {\n if (quantizedGeometry) {\n src.push(\"vec4 localNormal = vec4(octDecode(normal.xy), 0.0); \");\n } else {\n src.push(\"vec4 localNormal = vec4(normal, 0.0); \");\n }\n src.push(\"mat4 modelNormalMatrix2 = modelNormalMatrix;\");\n src.push(\"mat4 viewNormalMatrix2 = viewNormalMatrix;\");\n }\n src.push(\"mat4 viewMatrix2 = viewMatrix;\");\n src.push(\"mat4 modelMatrix2 = modelMatrix;\");\n if (stationary) {\n src.push(\"viewMatrix2[3][0] = viewMatrix2[3][1] = viewMatrix2[3][2] = 0.0;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"mat4 modelViewMatrix = viewMatrix2 * modelMatrix2;\");\n src.push(\"billboard(modelMatrix2);\");\n src.push(\"billboard(viewMatrix2);\");\n src.push(\"billboard(modelViewMatrix);\");\n if (normals) {\n src.push(\"mat4 modelViewNormalMatrix = viewNormalMatrix2 * modelNormalMatrix2;\");\n src.push(\"billboard(modelNormalMatrix2);\");\n src.push(\"billboard(viewNormalMatrix2);\");\n src.push(\"billboard(modelViewNormalMatrix);\");\n }\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"vec4 viewPosition = modelViewMatrix * localPosition;\");\n } else {\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = viewMatrix2 * worldPosition; \");\n }\n if (normals) {\n src.push(\"vec3 viewNormal = normalize((viewNormalMatrix2 * modelNormalMatrix2 * localNormal).xyz);\");\n }\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n src.push(\"float lambertian = 1.0;\");\n if (normals) {\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix2 * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix2 * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n }\n // TODO: A blending mode for emphasis materials, to select add/multiply/mix\n //src.push(\"vColor = vec4((mix(reflectedColor, fillColor.rgb, 0.7)), fillColor.a);\");\n src.push(\"vColor = vec4(reflectedColor * fillColor.rgb, fillColor.a);\");\n //src.push(\"vColor = vec4(reflectedColor + fillColor.rgb, fillColor.a);\");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n }\n if (mesh._geometry._state.primitiveName === \"points\") {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n return src;\n}\n\nfunction hasNormals(mesh) {\n const primitive = mesh._geometry._state.primitiveName;\n if ((mesh._geometry._state.autoVertexNormals || mesh._geometry._state.normalsBuf) && (primitive === \"triangles\" || primitive === \"triangle-strip\" || primitive === \"triangle-fan\")) {\n return true;\n }\n return false;\n}\n\nfunction buildFragment$5(mesh) {\n\n const scene = mesh.scene;\n const sectionPlanesState = mesh.scene._sectionPlanesState;\n const gammaOutput = mesh.scene.gammaOutput;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Lambertian drawing fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (gammaOutput) {\n src.push(\"uniform float gammaFactor;\");\n src.push(\"vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"uniform bool clippable;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (mesh._geometry._state.primitiveName === \"points\") {\n src.push(\"vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\"float r = dot(cxy, cxy);\");\n src.push(\"if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n if (gammaOutput) {\n src.push(\"outColor = linearToGamma(vColor, gammaFactor);\");\n } else {\n src.push(\"outColor = vColor;\");\n }\n src.push(\"}\");\n return src;\n}\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\nconst ids$1 = new Map$1({});\n\nconst tempVec3a$H = math.vec3();\n\n/**\n * @private\n */\nconst EmphasisFillRenderer = function (hash, mesh) {\n this.id = ids$1.addItem({});\n this._hash = hash;\n this._scene = mesh.scene;\n this._useCount = 0;\n this._shaderSource = new EmphasisFillShaderSource(mesh);\n this._allocate(mesh);\n};\n\nconst xrayFillRenderers = {};\n\nEmphasisFillRenderer.get = function (mesh) {\n const hash = [\n mesh.scene.id,\n mesh.scene.gammaOutput ? \"go\" : \"\", // Gamma input not needed\n mesh.scene._sectionPlanesState.getHash(),\n !!mesh._geometry._state.normalsBuf ? \"n\" : \"\",\n mesh._geometry._state.compressGeometry ? \"cp\" : \"\",\n mesh._state.hash\n ].join(\";\");\n let renderer = xrayFillRenderers[hash];\n if (!renderer) {\n renderer = new EmphasisFillRenderer(hash, mesh);\n xrayFillRenderers[hash] = renderer;\n stats.memory.programs++;\n }\n renderer._useCount++;\n return renderer;\n};\n\nEmphasisFillRenderer.prototype.put = function () {\n if (--this._useCount === 0) {\n ids$1.removeItem(this.id);\n if (this._program) {\n this._program.destroy();\n }\n delete xrayFillRenderers[this._hash];\n stats.memory.programs--;\n }\n};\n\nEmphasisFillRenderer.prototype.webglContextRestored = function () {\n this._program = null;\n};\n\nEmphasisFillRenderer.prototype.drawMesh = function (frameCtx, mesh, mode) {\n\n if (!this._program) {\n this._allocate(mesh);\n }\n\n const scene = this._scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const materialState = mode === 0 ? mesh._xrayMaterial._state : (mode === 1 ? mesh._highlightMaterial._state : mesh._selectedMaterial._state);\n const meshState = mesh._state;\n const geometryState = mesh._geometry._state;\n const origin = mesh.origin;\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx);\n }\n\n gl.uniformMatrix4fv(this._uViewMatrix, false, origin ? frameCtx.getRTCViewMatrix(meshState.originHash, origin) : camera.viewMatrix);\n gl.uniformMatrix4fv(this._uViewNormalMatrix, false, camera.viewNormalMatrix);\n\n if (meshState.clippable) {\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const renderFlags = mesh.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$H);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n }\n\n if (materialState.id !== this._lastMaterialId) {\n const fillColor = materialState.fillColor;\n const backfaces = materialState.backfaces;\n if (frameCtx.backfaces !== backfaces) {\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n gl.uniform4f(this._uFillColor, fillColor[0], fillColor[1], fillColor[2], materialState.fillAlpha);\n this._lastMaterialId = materialState.id;\n }\n\n gl.uniformMatrix4fv(this._uModelMatrix, gl.FALSE, mesh.worldMatrix);\n if (this._uModelNormalMatrix) {\n gl.uniformMatrix4fv(this._uModelNormalMatrix, gl.FALSE, mesh.worldNormalMatrix);\n }\n\n if (this._uClippable) {\n gl.uniform1i(this._uClippable, meshState.clippable);\n }\n\n gl.uniform3fv(this._uOffset, meshState.offset);\n\n // Bind VBOs\n if (geometryState.id !== this._lastGeometryId) {\n if (this._uPositionsDecodeMatrix) {\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);\n }\n if (this._uUVDecodeMatrix) {\n gl.uniformMatrix3fv(this._uUVDecodeMatrix, false, geometryState.uvDecodeMatrix);\n }\n if (this._aPosition) {\n this._aPosition.bindArrayBuffer(geometryState.positionsBuf);\n frameCtx.bindArray++;\n }\n if (this._aNormal) {\n this._aNormal.bindArrayBuffer(geometryState.normalsBuf);\n frameCtx.bindArray++;\n }\n if (geometryState.indicesBuf) {\n geometryState.indicesBuf.bind();\n frameCtx.bindArray++;\n // gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);\n // frameCtx.drawElements++;\n } else if (geometryState.positionsBuf) ;\n this._lastGeometryId = geometryState.id;\n }\n\n if (geometryState.indicesBuf) {\n gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);\n frameCtx.drawElements++;\n } else if (geometryState.positionsBuf) {\n gl.drawArrays(gl.TRIANGLES, 0, geometryState.positionsBuf.numItems);\n frameCtx.drawArrays++;\n }\n};\n\nEmphasisFillRenderer.prototype._allocate = function (mesh) {\n const scene = mesh.scene;\n const lightsState = scene._lightsState;\n const sectionPlanesState = scene._sectionPlanesState;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._shaderSource);\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uPositionsDecodeMatrix = program.getLocation(\"positionsDecodeMatrix\");\n this._uModelMatrix = program.getLocation(\"modelMatrix\");\n this._uModelNormalMatrix = program.getLocation(\"modelNormalMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uViewNormalMatrix = program.getLocation(\"viewNormalMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uLightAmbient = [];\n this._uLightColor = [];\n this._uLightDir = [];\n this._uLightPos = [];\n this._uLightAttenuation = [];\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n switch (light.type) {\n case \"ambient\":\n this._uLightAmbient[i] = program.getLocation(\"lightAmbient\");\n break;\n case \"dir\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = null;\n this._uLightDir[i] = program.getLocation(\"lightDir\" + i);\n break;\n case \"point\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = program.getLocation(\"lightPos\" + i);\n this._uLightDir[i] = null;\n this._uLightAttenuation[i] = program.getLocation(\"lightAttenuation\" + i);\n break;\n }\n }\n this._uSectionPlanes = [];\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._uFillColor = program.getLocation(\"fillColor\");\n this._aPosition = program.getAttribute(\"position\");\n this._aNormal = program.getAttribute(\"normal\");\n this._uClippable = program.getLocation(\"clippable\");\n this._uGammaFactor = program.getLocation(\"gammaFactor\");\n this._uOffset = program.getLocation(\"offset\");\n if (scene.logarithmicDepthBufferEnabled ) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n};\n\nEmphasisFillRenderer.prototype._bindProgram = function (frameCtx) {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const lightsState = scene._lightsState;\n const camera = scene.camera;\n const project = camera.project;\n const program = this._program;\n program.bind();\n frameCtx.useProgram++;\n frameCtx.textureUnit = 0;\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n this._lastIndicesBufId = null;\n gl.uniformMatrix4fv(this._uViewNormalMatrix, false, camera.normalMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, project.matrix);\n if (scene.logarithmicDepthBufferEnabled ) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (this._uLightAmbient[i]) {\n gl.uniform4f(this._uLightAmbient[i], light.color[0], light.color[1], light.color[2], light.intensity);\n } else {\n if (this._uLightColor[i]) {\n gl.uniform4f(this._uLightColor[i], light.color[0], light.color[1], light.color[2], light.intensity);\n }\n if (this._uLightPos[i]) {\n gl.uniform3fv(this._uLightPos[i], light.pos);\n if (this._uLightAttenuation[i]) {\n gl.uniform1f(this._uLightAttenuation[i], light.attenuation);\n }\n }\n if (this._uLightDir[i]) {\n gl.uniform3fv(this._uLightDir[i], light.dir);\n }\n }\n }\n if (this._uGammaFactor) {\n gl.uniform1f(this._uGammaFactor, scene.gammaFactor);\n }\n};\n\n/**\n * @private\n */\nclass EmphasisEdgesShaderSource {\n constructor(mesh) {\n this.vertex = buildVertex$4(mesh);\n this.fragment = buildFragment$4(mesh);\n }\n}\n\nfunction buildVertex$4(mesh) {\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const quantizedGeometry = !!mesh._geometry._state.compressGeometry;\n const billboard = mesh._state.billboard;\n const stationary = mesh._state.stationary;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Edges drawing vertex shader\");\n src.push(\"in vec3 position;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"uniform vec4 edgeColor;\");\n src.push(\"uniform vec3 offset;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n src.push(\"out vec4 vColor;\");\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"void billboard(inout mat4 mat) {\");\n src.push(\" mat[0][0] = 1.0;\");\n src.push(\" mat[0][1] = 0.0;\");\n src.push(\" mat[0][2] = 0.0;\");\n if (billboard === \"spherical\") {\n src.push(\" mat[1][0] = 0.0;\");\n src.push(\" mat[1][1] = 1.0;\");\n src.push(\" mat[1][2] = 0.0;\");\n }\n src.push(\" mat[2][0] = 0.0;\");\n src.push(\" mat[2][1] = 0.0;\");\n src.push(\" mat[2][2] =1.0;\");\n src.push(\"}\");\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n src.push(\"vec4 worldPosition;\");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n src.push(\"mat4 viewMatrix2 = viewMatrix;\");\n src.push(\"mat4 modelMatrix2 = modelMatrix;\");\n if (stationary) {\n src.push(\"viewMatrix2[3][0] = viewMatrix2[3][1] = viewMatrix2[3][2] = 0.0;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"mat4 modelViewMatrix = viewMatrix2 * modelMatrix2;\");\n src.push(\"billboard(modelMatrix2);\");\n src.push(\"billboard(viewMatrix2);\");\n src.push(\"billboard(modelViewMatrix);\");\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = modelViewMatrix * localPosition;\");\n } else {\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = viewMatrix2 * worldPosition; \");\n }\n src.push(\"vColor = edgeColor;\");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n return src;\n}\n\nfunction buildFragment$4(mesh) {\n\n const scene = mesh.scene;\n const sectionPlanesState = mesh.scene._sectionPlanesState;\n const gammaOutput = mesh.scene.gammaOutput;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Edges drawing fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (gammaOutput) {\n src.push(\"uniform float gammaFactor;\");\n src.push(\"vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"uniform bool clippable;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n if (gammaOutput) {\n src.push(\"outColor = linearToGamma(vColor, gammaFactor);\");\n } else {\n src.push(\"outColor = vColor;\");\n }\n src.push(\"}\");\n return src;\n}\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\nconst ids = new Map$1({});\n\nconst tempVec3a$G = math.vec3();\n\n/**\n * @private\n */\nconst EmphasisEdgesRenderer = function (hash, mesh) {\n this.id = ids.addItem({});\n this._hash = hash;\n this._scene = mesh.scene;\n this._useCount = 0;\n this._shaderSource = new EmphasisEdgesShaderSource(mesh);\n this._allocate(mesh);\n};\n\nconst renderers$4 = {};\n\nEmphasisEdgesRenderer.get = function (mesh) {\n const hash = [\n mesh.scene.id,\n mesh.scene.gammaOutput ? \"go\" : \"\", // Gamma input not needed\n mesh.scene._sectionPlanesState.getHash(),\n mesh._geometry._state.compressGeometry ? \"cp\" : \"\",\n mesh._state.hash\n ].join(\";\");\n let renderer = renderers$4[hash];\n if (!renderer) {\n renderer = new EmphasisEdgesRenderer(hash, mesh);\n renderers$4[hash] = renderer;\n stats.memory.programs++;\n }\n renderer._useCount++;\n return renderer;\n};\n\nEmphasisEdgesRenderer.prototype.put = function () {\n if (--this._useCount === 0) {\n ids.removeItem(this.id);\n if (this._program) {\n this._program.destroy();\n }\n delete renderers$4[this._hash];\n stats.memory.programs--;\n }\n};\n\nEmphasisEdgesRenderer.prototype.webglContextRestored = function () {\n this._program = null;\n};\n\nEmphasisEdgesRenderer.prototype.drawMesh = function (frameCtx, mesh, mode) {\n\n if (!this._program) {\n this._allocate(mesh);\n }\n\n const scene = this._scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n let materialState;\n const meshState = mesh._state;\n const geometry = mesh._geometry;\n const geometryState = geometry._state;\n const origin = mesh.origin;\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx);\n }\n\n gl.uniformMatrix4fv(this._uViewMatrix, false, origin ? frameCtx.getRTCViewMatrix(meshState.originHash, origin) : camera.viewMatrix);\n\n if (meshState.clippable) {\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const renderFlags = mesh.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$G);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n }\n\n switch (mode) {\n case 0:\n materialState = mesh._xrayMaterial._state;\n break;\n case 1:\n materialState = mesh._highlightMaterial._state;\n break;\n case 2:\n materialState = mesh._selectedMaterial._state;\n break;\n case 3:\n default:\n materialState = mesh._edgeMaterial._state;\n break;\n }\n\n if (materialState.id !== this._lastMaterialId) {\n const backfaces = materialState.backfaces;\n if (frameCtx.backfaces !== backfaces) {\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n if (frameCtx.lineWidth !== materialState.edgeWidth) {\n gl.lineWidth(materialState.edgeWidth);\n frameCtx.lineWidth = materialState.edgeWidth;\n }\n if (this._uEdgeColor) {\n const edgeColor = materialState.edgeColor;\n const edgeAlpha = materialState.edgeAlpha;\n gl.uniform4f(this._uEdgeColor, edgeColor[0], edgeColor[1], edgeColor[2], edgeAlpha);\n }\n this._lastMaterialId = materialState.id;\n }\n\n gl.uniformMatrix4fv(this._uModelMatrix, gl.FALSE, mesh.worldMatrix);\n\n if (this._uClippable) {\n gl.uniform1i(this._uClippable, meshState.clippable);\n }\n\n gl.uniform3fv(this._uOffset, meshState.offset);\n\n // Bind VBOs\n let indicesBuf;\n if (geometryState.primitive === gl.TRIANGLES) {\n indicesBuf = geometry._getEdgeIndices();\n } else if (geometryState.primitive === gl.LINES) {\n indicesBuf = geometryState.indicesBuf;\n }\n\n if (indicesBuf) {\n if (geometryState.id !== this._lastGeometryId) {\n if (this._uPositionsDecodeMatrix) {\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);\n }\n if (this._aPosition) {\n this._aPosition.bindArrayBuffer(geometryState.positionsBuf, geometryState.compressGeometry ? gl.UNSIGNED_SHORT : gl.FLOAT);\n frameCtx.bindArray++;\n }\n indicesBuf.bind();\n frameCtx.bindArray++;\n this._lastGeometryId = geometryState.id;\n }\n\n gl.drawElements(gl.LINES, indicesBuf.numItems, indicesBuf.itemType, 0);\n\n frameCtx.drawElements++;\n }\n};\n\nEmphasisEdgesRenderer.prototype._allocate = function (mesh) {\n\n const scene = mesh.scene;\n const gl = scene.canvas.gl;\n const sectionPlanesState = scene._sectionPlanesState;\n\n this._program = new Program(gl, this._shaderSource);\n\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n\n const program = this._program;\n\n this._uPositionsDecodeMatrix = program.getLocation(\"positionsDecodeMatrix\");\n this._uModelMatrix = program.getLocation(\"modelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._uEdgeColor = program.getLocation(\"edgeColor\");\n this._aPosition = program.getAttribute(\"position\");\n this._uClippable = program.getLocation(\"clippable\");\n this._uGammaFactor = program.getLocation(\"gammaFactor\");\n this._uOffset = program.getLocation(\"offset\");\n\n if (scene.logarithmicDepthBufferEnabled ) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n};\n\nEmphasisEdgesRenderer.prototype._bindProgram = function (frameCtx) {\n\n const program = this._program;\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const camera = scene.camera;\n const project = camera.project;\n\n program.bind();\n\n frameCtx.useProgram++;\n\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, project.matrix);\n\n if (scene.logarithmicDepthBufferEnabled ) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n if (this._uGammaFactor) {\n gl.uniform1f(this._uGammaFactor, scene.gammaFactor);\n }\n};\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\n/**\n * @private\n */\nclass PickMeshShaderSource {\n constructor(mesh) {\n this.vertex = buildVertex$3(mesh);\n this.fragment = buildFragment$3(mesh);\n }\n}\n\nfunction buildVertex$3(mesh) {\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const quantizedGeometry = !!mesh._geometry._state.compressGeometry;\n const billboard = mesh._state.billboard;\n const stationary = mesh._state.stationary;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Mesh picking vertex shader\");\n src.push(\"in vec3 position;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"uniform vec3 offset;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"void billboard(inout mat4 mat) {\");\n src.push(\" mat[0][0] = 1.0;\");\n src.push(\" mat[0][1] = 0.0;\");\n src.push(\" mat[0][2] = 0.0;\");\n if (billboard === \"spherical\") {\n src.push(\" mat[1][0] = 0.0;\");\n src.push(\" mat[1][1] = 1.0;\");\n src.push(\" mat[1][2] = 0.0;\");\n }\n src.push(\" mat[2][0] = 0.0;\");\n src.push(\" mat[2][1] = 0.0;\");\n src.push(\" mat[2][2] =1.0;\");\n src.push(\"}\");\n }\n\n src.push(\"uniform vec2 pickClipPos;\");\n\n src.push(\"vec4 remapClipPos(vec4 clipPos) {\");\n src.push(\" clipPos.xy /= clipPos.w;\");\n src.push(\" clipPos.xy -= pickClipPos;\");\n src.push(\" clipPos.xy *= clipPos.w;\");\n src.push(\" return clipPos;\");\n src.push(\"}\");\n\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n src.push(\"mat4 viewMatrix2 = viewMatrix;\");\n src.push(\"mat4 modelMatrix2 = modelMatrix;\");\n if (stationary) {\n src.push(\"viewMatrix2[3][0] = viewMatrix2[3][1] = viewMatrix2[3][2] = 0.0;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"mat4 modelViewMatrix = viewMatrix2 * modelMatrix2;\");\n src.push(\"billboard(modelMatrix2);\");\n src.push(\"billboard(viewMatrix2);\");\n }\n src.push(\" vec4 worldPosition = modelMatrix2 * localPosition;\");\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\" vec4 viewPosition = viewMatrix2 * worldPosition;\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\"}\");\n return src;\n}\n\nfunction buildFragment$3(mesh) {\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Mesh picking fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform vec4 pickColor;\");\n if (clipping) {\n src.push(\"uniform bool clippable;\");\n src.push(\"in vec4 vWorldPosition;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = pickColor; \");\n src.push(\"}\");\n return src;\n}\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\nconst tempVec3a$F = math.vec3();\n\n// No ID, because there is exactly one PickMeshRenderer per scene\n\n/**\n * @private\n */\nconst PickMeshRenderer = function (hash, mesh) {\n this._hash = hash;\n this._shaderSource = new PickMeshShaderSource(mesh);\n this._scene = mesh.scene;\n this._useCount = 0;\n this._allocate(mesh);\n};\n\nconst renderers$3 = {};\n\nPickMeshRenderer.get = function (mesh) {\n const hash = [\n mesh.scene.canvas.canvas.id,\n mesh.scene._sectionPlanesState.getHash(),\n mesh._geometry._state.hash,\n mesh._state.hash\n ].join(\";\");\n let renderer = renderers$3[hash];\n if (!renderer) {\n renderer = new PickMeshRenderer(hash, mesh);\n if (renderer.errors) {\n console.log(renderer.errors.join(\"\\n\"));\n return null;\n }\n renderers$3[hash] = renderer;\n stats.memory.programs++;\n }\n renderer._useCount++;\n return renderer;\n};\n\nPickMeshRenderer.prototype.put = function () {\n if (--this._useCount === 0) {\n if (this._program) {\n this._program.destroy();\n }\n delete renderers$3[this._hash];\n stats.memory.programs--;\n }\n};\n\nPickMeshRenderer.prototype.webglContextRestored = function () {\n this._program = null;\n};\n\nPickMeshRenderer.prototype.drawMesh = function (frameCtx, mesh) {\n\n if (!this._program) {\n this._allocate(mesh);\n }\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const meshState = mesh._state;\n const materialState = mesh._material._state;\n const geometryState = mesh._geometry._state;\n const origin = mesh.origin;\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx);\n }\n\n gl.uniformMatrix4fv(this._uViewMatrix, false, origin ? frameCtx.getRTCPickViewMatrix(meshState.originHash, origin) : frameCtx.pickViewMatrix);\n\n if (meshState.clippable) {\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const renderFlags = mesh.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$F);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n }\n\n if (materialState.id !== this._lastMaterialId) {\n const backfaces = materialState.backfaces;\n if (frameCtx.backfaces !== backfaces) {\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n const frontface = materialState.frontface;\n if (frameCtx.frontface !== frontface) {\n if (frontface) {\n gl.frontFace(gl.CCW);\n } else {\n gl.frontFace(gl.CW);\n }\n frameCtx.frontface = frontface;\n }\n this._lastMaterialId = materialState.id;\n }\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, frameCtx.pickProjMatrix);\n gl.uniformMatrix4fv(this._uModelMatrix, false, mesh.worldMatrix);\n if (this._uClippable) {\n gl.uniform1i(this._uClippable, mesh._state.clippable);\n }\n gl.uniform3fv(this._uOffset, mesh._state.offset);\n\n if (geometryState.id !== this._lastGeometryId) {\n if (this._uPositionsDecodeMatrix) {\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);\n }\n if (this._aPosition) {\n this._aPosition.bindArrayBuffer(geometryState.positionsBuf, geometryState.compressGeometry ? gl.UNSIGNED_SHORT : gl.FLOAT);\n frameCtx.bindArray++;\n }\n if (geometryState.indicesBuf) {\n geometryState.indicesBuf.bind();\n frameCtx.bindArray++;\n }\n this._lastGeometryId = geometryState.id;\n }\n\n // Mesh-indexed color\n var pickID = mesh._state.pickID;\n const a = pickID >> 24 & 0xFF;\n const b = pickID >> 16 & 0xFF;\n const g = pickID >> 8 & 0xFF;\n const r = pickID & 0xFF;\n gl.uniform4f(this._uPickColor, r / 255, g / 255, b / 255, a / 255);\n\n gl.uniform2fv(this._uPickClipPos, frameCtx.pickClipPos);\n\n if (geometryState.indicesBuf) {\n gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);\n frameCtx.drawElements++;\n } else if (geometryState.positions) {\n gl.drawArrays(gl.TRIANGLES, 0, geometryState.positions.numItems);\n }\n};\n\nPickMeshRenderer.prototype._allocate = function (mesh) {\n const scene = mesh.scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._shaderSource);\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uPositionsDecodeMatrix = program.getLocation(\"positionsDecodeMatrix\");\n this._uModelMatrix = program.getLocation(\"modelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n const clips = scene._sectionPlanesState.sectionPlanes;\n for (let i = 0, len = clips.length; i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._aPosition = program.getAttribute(\"position\");\n this._uClippable = program.getLocation(\"clippable\");\n this._uPickColor = program.getLocation(\"pickColor\");\n this._uPickClipPos = program.getLocation(\"pickClipPos\");\n this._uOffset = program.getLocation(\"offset\");\n if (scene.logarithmicDepthBufferEnabled ) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._lastMaterialId = null;\n this._lastGeometryId = null;\n};\n\nPickMeshRenderer.prototype._bindProgram = function (frameCtx) {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const project = scene.camera.project;\n this._program.bind();\n frameCtx.useProgram++;\n if (scene.logarithmicDepthBufferEnabled ) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n this._lastMaterialId = null;\n this._lastGeometryId = null;\n};\n\n/**\n * @private\n */\n\nclass PickTriangleShaderSource {\n constructor(mesh) {\n this.vertex = buildVertex$2(mesh);\n this.fragment = buildFragment$2(mesh);\n }\n}\n\nfunction buildVertex$2(mesh) {\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const quantizedGeometry = !!mesh._geometry._state.compressGeometry;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Surface picking vertex shader\");\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"uniform vec3 offset;\");\n if (clipping) {\n src.push(\"uniform bool clippable;\");\n src.push(\"out vec4 vWorldPosition;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"uniform vec2 pickClipPos;\");\n\n src.push(\"vec4 remapClipPos(vec4 clipPos) {\");\n src.push(\" clipPos.xy /= clipPos.w;\");\n src.push(\" clipPos.xy -= pickClipPos;\");\n src.push(\" clipPos.xy *= clipPos.w;\");\n src.push(\" return clipPos;\");\n src.push(\"}\");\n\n src.push(\"out vec4 vColor;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n src.push(\" vec4 worldPosition = modelMatrix * localPosition; \");\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition;\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n }\n src.push(\" vColor = color;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\"}\");\n return src;\n}\n\nfunction buildFragment$2(mesh) {\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Surface picking fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n src.push(\"in vec4 vColor;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"uniform bool clippable;\");\n src.push(\"in vec4 vWorldPosition;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vColor;\");\n src.push(\"}\");\n return src;\n}\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\nconst tempVec3a$E = math.vec3();\n\n/**\n * @private\n */\nconst PickTriangleRenderer = function (hash, mesh) {\n this._hash = hash;\n this._scene = mesh.scene;\n this._useCount = 0;\n this._shaderSource = new PickTriangleShaderSource(mesh);\n this._allocate(mesh);\n};\n\nconst renderers$2 = {};\n\nPickTriangleRenderer.get = function (mesh) {\n const hash = [\n mesh.scene.canvas.canvas.id,\n mesh.scene._sectionPlanesState.getHash(),\n mesh._geometry._state.compressGeometry ? \"cp\" : \"\",\n mesh._state.hash\n ].join(\";\");\n let renderer = renderers$2[hash];\n if (!renderer) {\n renderer = new PickTriangleRenderer(hash, mesh);\n if (renderer.errors) {\n console.log(renderer.errors.join(\"\\n\"));\n return null;\n }\n renderers$2[hash] = renderer;\n stats.memory.programs++;\n }\n renderer._useCount++;\n return renderer;\n};\n\nPickTriangleRenderer.prototype.put = function () {\n if (--this._useCount === 0) {\n if (this._program) {\n this._program.destroy();\n }\n delete renderers$2[this._hash];\n stats.memory.programs--;\n }\n};\n\nPickTriangleRenderer.prototype.webglContextRestored = function () {\n this._program = null;\n};\n\nPickTriangleRenderer.prototype.drawMesh = function (frameCtx, mesh) {\n\n if (!this._program) {\n this._allocate(mesh);\n }\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const meshState = mesh._state;\n const materialState = mesh._material._state;\n const geometry = mesh._geometry;\n const geometryState = mesh._geometry._state;\n const origin = mesh.origin;\n const backfaces = materialState.backfaces;\n const frontface = materialState.frontface;\n const project = scene.camera.project;\n const positionsBuf = geometry._getPickTrianglePositions();\n const pickColorsBuf = geometry._getPickTriangleColors();\n\n this._program.bind();\n\n frameCtx.useProgram++;\n\n if (scene.logarithmicDepthBufferEnabled ) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n gl.uniformMatrix4fv(this._uViewMatrix, false, origin ? frameCtx.getRTCPickViewMatrix(meshState.originHash, origin) : frameCtx.pickViewMatrix);\n\n if (meshState.clippable) {\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const renderFlags = mesh.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$E);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n }\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, frameCtx.pickProjMatrix);\n\n if (scene.logarithmicDepthBufferEnabled) {\n gl.uniform1f(this._uZFar, scene.camera.project.far);\n }\n\n if (frameCtx.backfaces !== backfaces) {\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n if (frameCtx.frontface !== frontface) {\n if (frontface) {\n gl.frontFace(gl.CCW);\n } else {\n gl.frontFace(gl.CW);\n }\n frameCtx.frontface = frontface;\n }\n\n gl.uniformMatrix4fv(this._uModelMatrix, false, mesh.worldMatrix);\n if (this._uClippable) {\n gl.uniform1i(this._uClippable, mesh._state.clippable);\n }\n gl.uniform3fv(this._uOffset, mesh._state.offset);\n if (this._uPositionsDecodeMatrix) {\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);\n this._aPosition.bindArrayBuffer(positionsBuf, geometryState.compressGeometry ? gl.UNSIGNED_SHORT : gl.FLOAT);\n } else {\n this._aPosition.bindArrayBuffer(positionsBuf);\n }\n\n gl.uniform2fv(this._uPickClipPos, frameCtx.pickClipPos);\n\n pickColorsBuf.bind();\n gl.enableVertexAttribArray(this._aColor.location);\n gl.vertexAttribPointer(this._aColor.location, pickColorsBuf.itemSize, pickColorsBuf.itemType, true, 0, 0); // Normalize\n gl.drawArrays(geometryState.primitive, 0, positionsBuf.numItems / 3);\n};\n\nPickTriangleRenderer.prototype._allocate = function (mesh) {\n const scene = mesh.scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._shaderSource);\n this._useCount = 0;\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uPositionsDecodeMatrix = program.getLocation(\"positionsDecodeMatrix\");\n this._uModelMatrix = program.getLocation(\"modelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n for (let i = 0, len = sectionPlanes.length; i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._aPosition = program.getAttribute(\"position\");\n this._aColor = program.getAttribute(\"color\");\n this._uPickClipPos = program.getLocation(\"pickClipPos\");\n this._uClippable = program.getLocation(\"clippable\");\n this._uOffset = program.getLocation(\"offset\");\n if (scene.logarithmicDepthBufferEnabled ) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n};\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\n/**\n * @private\n */\nclass OcclusionShaderSource {\n constructor(mesh) {\n this.vertex = buildVertex$1(mesh);\n this.fragment = buildFragment$1(mesh);\n }\n}\n\nfunction buildVertex$1(mesh) {\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const quantizedGeometry = !!mesh._geometry._state.compressGeometry;\n const billboard = mesh._state.billboard;\n const stationary = mesh._state.stationary;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Mesh occlusion vertex shader\");\n src.push(\"in vec3 position;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"uniform vec3 offset;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"void billboard(inout mat4 mat) {\");\n src.push(\" mat[0][0] = 1.0;\");\n src.push(\" mat[0][1] = 0.0;\");\n src.push(\" mat[0][2] = 0.0;\");\n if (billboard === \"spherical\") {\n src.push(\" mat[1][0] = 0.0;\");\n src.push(\" mat[1][1] = 1.0;\");\n src.push(\" mat[1][2] = 0.0;\");\n }\n src.push(\" mat[2][0] = 0.0;\");\n src.push(\" mat[2][1] = 0.0;\");\n src.push(\" mat[2][2] =1.0;\");\n src.push(\"}\");\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n src.push(\"vec4 worldPosition;\");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n src.push(\"mat4 viewMatrix2 = viewMatrix;\");\n src.push(\"mat4 modelMatrix2 = modelMatrix;\");\n if (stationary) {\n src.push(\"viewMatrix2[3][0] = viewMatrix2[3][1] = viewMatrix2[3][2] = 0.0;\");\n }\n if (billboard === \"spherical\" || billboard === \"cylindrical\") {\n src.push(\"mat4 modelViewMatrix = viewMatrix2 * modelMatrix2;\");\n src.push(\"billboard(modelMatrix2);\");\n src.push(\"billboard(viewMatrix2);\");\n src.push(\"billboard(modelViewMatrix);\");\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = modelViewMatrix * localPosition;\");\n } else {\n src.push(\"worldPosition = modelMatrix2 * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = viewMatrix2 * worldPosition; \");\n }\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n return src;\n}\n\nfunction buildFragment$1(mesh) {\n\n const scene = mesh.scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Mesh occlusion fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"uniform bool clippable;\");\n src.push(\"in vec4 vWorldPosition;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n\n src.push(\" outColor = vec4(0.0, 0.0, 1.0, 1.0); \");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n\n return src;\n}\n\n/**\n * @author xeolabs / https://github.com/xeolabs\n */\n\nconst tempVec3a$D = math.vec3();\n\n// No ID, because there is exactly one PickMeshRenderer per scene\n\n/**\n * @private\n */\nconst OcclusionRenderer = function (hash, mesh) {\n this._hash = hash;\n this._shaderSource = new OcclusionShaderSource(mesh);\n this._scene = mesh.scene;\n this._useCount = 0;\n this._allocate(mesh);\n};\n\nconst renderers$1 = {};\n\nOcclusionRenderer.get = function (mesh) {\n const hash = [\n mesh.scene.canvas.canvas.id,\n mesh.scene._sectionPlanesState.getHash(),\n mesh._geometry._state.hash,\n mesh._state.occlusionHash\n ].join(\";\");\n let renderer = renderers$1[hash];\n if (!renderer) {\n renderer = new OcclusionRenderer(hash, mesh);\n if (renderer.errors) {\n console.log(renderer.errors.join(\"\\n\"));\n return null;\n }\n renderers$1[hash] = renderer;\n stats.memory.programs++;\n }\n renderer._useCount++;\n return renderer;\n};\n\nOcclusionRenderer.prototype.put = function () {\n if (--this._useCount === 0) {\n if (this._program) {\n this._program.destroy();\n }\n delete renderers$1[this._hash];\n stats.memory.programs--;\n }\n};\n\nOcclusionRenderer.prototype.webglContextRestored = function () {\n this._program = null;\n};\n\nOcclusionRenderer.prototype.drawMesh = function (frameCtx, mesh) {\n\n if (!this._program) {\n this._allocate(mesh);\n }\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const materialState = mesh._material._state;\n const meshState = mesh._state;\n const geometryState = mesh._geometry._state;\n const origin = mesh.origin;\n\n if (materialState.alpha < 1.0) {\n return;\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx);\n }\n\n if (materialState.id !== this._lastMaterialId) {\n const backfaces = materialState.backfaces;\n if (frameCtx.backfaces !== backfaces) {\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n const frontface = materialState.frontface;\n if (frameCtx.frontface !== frontface) {\n if (frontface) {\n gl.frontFace(gl.CCW);\n } else {\n gl.frontFace(gl.CW);\n }\n frameCtx.frontface = frontface;\n }\n this._lastMaterialId = materialState.id;\n }\n\n const camera = scene.camera;\n\n gl.uniformMatrix4fv(this._uViewMatrix, false, origin ? frameCtx.getRTCViewMatrix(meshState.originHash, origin) : camera.viewMatrix);\n\n if (meshState.clippable) {\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const renderFlags = mesh.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$D);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n }\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera._project._state.matrix);\n gl.uniformMatrix4fv(this._uModelMatrix, gl.FALSE, mesh.worldMatrix);\n\n if (this._uClippable) {\n gl.uniform1i(this._uClippable, mesh._state.clippable);\n }\n\n gl.uniform3fv(this._uOffset, mesh._state.offset);\n\n if (geometryState.id !== this._lastGeometryId) {\n if (this._uPositionsDecodeMatrix) {\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);\n }\n if (this._aPosition) {\n this._aPosition.bindArrayBuffer(geometryState.positionsBuf, geometryState.compressGeometry ? gl.UNSIGNED_SHORT : gl.FLOAT);\n frameCtx.bindArray++;\n }\n if (geometryState.indicesBuf) {\n geometryState.indicesBuf.bind();\n frameCtx.bindArray++;\n }\n this._lastGeometryId = geometryState.id;\n }\n if (geometryState.indicesBuf) {\n gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);\n frameCtx.drawElements++;\n } else if (geometryState.positions) {\n gl.drawArrays(gl.TRIANGLES, 0, geometryState.positions.numItems);\n }\n};\n\nOcclusionRenderer.prototype._allocate = function (mesh) {\n const scene = mesh.scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._shaderSource);\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uPositionsDecodeMatrix = program.getLocation(\"positionsDecodeMatrix\");\n this._uModelMatrix = program.getLocation(\"modelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n const clips = scene._sectionPlanesState.sectionPlanes;\n for (let i = 0, len = clips.length; i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._aPosition = program.getAttribute(\"position\");\n this._uClippable = program.getLocation(\"clippable\");\n this._uOffset = program.getLocation(\"offset\");\n if (scene.logarithmicDepthBufferEnabled ) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n};\n\nOcclusionRenderer.prototype._bindProgram = function (frameCtx) {\n const scene = this._scene;\n const project = scene.camera.project;\n const gl = scene.canvas.gl;\n this._program.bind();\n frameCtx.useProgram++;\n gl.uniformMatrix4fv(this._uProjMatrix, false, project.matrix);\n if (scene.logarithmicDepthBufferEnabled ) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n};\n\n/**\n * @private\n */\nclass ShadowShaderSource {\n constructor(mesh) {\n this.vertex = buildVertex(mesh);\n this.fragment = buildFragment(mesh);\n }\n}\n\nfunction buildVertex(mesh) {\n const scene = mesh.scene;\n const clipping = scene._sectionPlanesState.sectionPlanes.length > 0;\n const quantizedGeometry = !!mesh._geometry._state.compressGeometry;\n const src = [];\n src.push(\"// Mesh shadow vertex shader\");\n src.push(\"in vec3 position;\");\n src.push(\"uniform mat4 modelMatrix;\");\n src.push(\"uniform mat4 shadowViewMatrix;\");\n src.push(\"uniform mat4 shadowProjMatrix;\");\n src.push(\"uniform vec3 offset;\");\n if (quantizedGeometry) {\n src.push(\"uniform mat4 positionsDecodeMatrix;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n }\n src.push(\"void main(void) {\");\n src.push(\"vec4 localPosition = vec4(position, 1.0); \");\n src.push(\"vec4 worldPosition;\");\n if (quantizedGeometry) {\n src.push(\"localPosition = positionsDecodeMatrix * localPosition;\");\n }\n src.push(\"worldPosition = modelMatrix * localPosition;\");\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n src.push(\"vec4 viewPosition = shadowViewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n }\n src.push(\" gl_Position = shadowProjMatrix * viewPosition;\");\n src.push(\"}\");\n return src;\n}\n\nfunction buildFragment(mesh) {\n const scene = mesh.scene;\n scene.canvas.gl;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"// Mesh shadow fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (clipping) {\n src.push(\"uniform bool clippable;\");\n src.push(\"in vec4 vWorldPosition;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n\n src.push(\"vec4 encodeFloat( const in float depth ) {\");\n src.push(\" const vec4 bitShift = vec4(256 * 256 * 256, 256 * 256, 256, 1.0);\");\n src.push(\" const vec4 bitMask = vec4(0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\");\n src.push(\" vec4 comp = fract(depth * bitShift);\");\n src.push(\" comp -= comp.xxyz * bitMask;\");\n src.push(\" return comp;\");\n src.push(\"}\");\n\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\"if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n src.push(\"outColor = encodeFloat(gl_FragCoord.z);\");\n src.push(\"}\");\n return src;\n}\n\n/**\n * @private\n */\nconst ShadowRenderer = function (hash, mesh) {\n this._hash = hash;\n this._shaderSource = new ShadowShaderSource(mesh);\n this._scene = mesh.scene;\n this._useCount = 0;\n this._allocate(mesh);\n};\n\nconst renderers = {};\n\nShadowRenderer.get = function (mesh) {\n const scene = mesh.scene;\n const hash = [scene.canvas.canvas.id, scene._sectionPlanesState.getHash(), mesh._geometry._state.hash, mesh._state.hash].join(\";\");\n let renderer = renderers[hash];\n if (!renderer) {\n renderer = new ShadowRenderer(hash, mesh);\n if (renderer.errors) {\n console.log(renderer.errors.join(\"\\n\"));\n return null;\n }\n renderers[hash] = renderer;\n stats.memory.programs++;\n }\n renderer._useCount++;\n return renderer;\n};\n\nShadowRenderer.prototype.put = function () {\n if (--this._useCount === 0) {\n if (this._program) {\n this._program.destroy();\n }\n delete renderers[this._hash];\n stats.memory.programs--;\n }\n};\n\nShadowRenderer.prototype.webglContextRestored = function () {\n this._program = null;\n};\n\nShadowRenderer.prototype.drawMesh = function (frame, mesh) {\n if (!this._program) {\n this._allocate(mesh);\n }\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const materialState = mesh._material._state;\n const geometryState = mesh._geometry._state;\n if (frame.lastProgramId !== this._program.id) {\n frame.lastProgramId = this._program.id;\n this._bindProgram(frame);\n }\n if (materialState.id !== this._lastMaterialId) {\n const backfaces = materialState.backfaces;\n if (frame.backfaces !== backfaces) {\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frame.backfaces = backfaces;\n }\n const frontface = materialState.frontface;\n if (frame.frontface !== frontface) {\n if (frontface) {\n gl.frontFace(gl.CCW);\n } else {\n gl.frontFace(gl.CW);\n }\n frame.frontface = frontface;\n }\n if (frame.lineWidth !== materialState.lineWidth) {\n gl.lineWidth(materialState.lineWidth);\n frame.lineWidth = materialState.lineWidth;\n }\n if (this._uPointSize) {\n gl.uniform1i(this._uPointSize, materialState.pointSize);\n }\n this._lastMaterialId = materialState.id;\n }\n gl.uniformMatrix4fv(this._uModelMatrix, gl.FALSE, mesh.worldMatrix);\n if (geometryState.combineGeometry) {\n const vertexBufs = mesh.vertexBufs;\n if (vertexBufs.id !== this._lastVertexBufsId) {\n if (vertexBufs.positionsBuf && this._aPosition) {\n this._aPosition.bindArrayBuffer(vertexBufs.positionsBuf, vertexBufs.compressGeometry ? gl.UNSIGNED_SHORT : gl.FLOAT);\n frame.bindArray++;\n }\n this._lastVertexBufsId = vertexBufs.id;\n }\n }\n if (this._uClippable) {\n gl.uniform1i(this._uClippable, mesh._state.clippable);\n }\n gl.uniform3fv(this._uOffset, mesh._state.offset);\n if (geometryState.id !== this._lastGeometryId) {\n if (this._uPositionsDecodeMatrix) {\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);\n }\n if (geometryState.combineGeometry) { // VBOs were bound by the preceding VertexBufs chunk\n if (geometryState.indicesBufCombined) {\n geometryState.indicesBufCombined.bind();\n frame.bindArray++;\n }\n } else {\n if (this._aPosition) {\n this._aPosition.bindArrayBuffer(geometryState.positionsBuf, geometryState.compressGeometry ? gl.UNSIGNED_SHORT : gl.FLOAT);\n frame.bindArray++;\n }\n if (geometryState.indicesBuf) {\n geometryState.indicesBuf.bind();\n frame.bindArray++;\n }\n }\n this._lastGeometryId = geometryState.id;\n }\n if (geometryState.combineGeometry) {\n if (geometryState.indicesBufCombined) {\n gl.drawElements(geometryState.primitive, geometryState.indicesBufCombined.numItems, geometryState.indicesBufCombined.itemType, 0);\n frame.drawElements++;\n }\n } else {\n if (geometryState.indicesBuf) {\n gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);\n frame.drawElements++;\n } else if (geometryState.positions) {\n gl.drawArrays(gl.TRIANGLES, 0, geometryState.positions.numItems);\n frame.drawArrays++;\n }\n }\n};\n\nShadowRenderer.prototype._allocate = function (mesh) {\n const scene = mesh.scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._shaderSource);\n this._scene = scene;\n this._useCount = 0;\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uPositionsDecodeMatrix = program.getLocation(\"positionsDecodeMatrix\");\n this._uModelMatrix = program.getLocation(\"modelMatrix\");\n this._uShadowViewMatrix = program.getLocation(\"shadowViewMatrix\");\n this._uShadowProjMatrix = program.getLocation(\"shadowProjMatrix\");\n this._uSectionPlanes = {};\n const clips = scene._sectionPlanesState.sectionPlanes;\n for (let i = 0, len = clips.length; i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._aPosition = program.getAttribute(\"position\");\n this._uClippable = program.getLocation(\"clippable\");\n this._uOffset = program.getLocation(\"offset\");\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n};\n\nShadowRenderer.prototype._bindProgram = function (frame) {\n if (!this._program) {\n this._allocate(mesh);\n }\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const sectionPlanesState = scene._sectionPlanesState;\n this._program.bind();\n frame.useProgram++;\n gl.uniformMatrix4fv(this._uShadowViewMatrix, false, frame.shadowViewMatrix);\n gl.uniformMatrix4fv(this._uShadowProjMatrix, false, frame.shadowProjMatrix);\n this._lastMaterialId = null;\n this._lastVertexBufsId = null;\n this._lastGeometryId = null;\n if (sectionPlanesState.getNumAllocatedSectionPlanes() > 0) {\n let sectionPlaneUniforms;\n let uSectionPlaneActive;\n let sectionPlane;\n let uSectionPlanePos;\n let uSectionPlaneDir;\n for (let i = 0, len = this._uSectionPlanes.length; i < len; i++) {\n sectionPlaneUniforms = this._uSectionPlanes[i];\n uSectionPlaneActive = sectionPlaneUniforms.active;\n sectionPlane = sectionPlanesState.sectionPlanes[i];\n if (uSectionPlaneActive) {\n gl.uniform1i(uSectionPlaneActive, sectionPlane.active);\n }\n uSectionPlanePos = sectionPlaneUniforms.pos;\n if (uSectionPlanePos) {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n uSectionPlaneDir = sectionPlaneUniforms.dir;\n if (uSectionPlaneDir) {\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n }\n }\n};\n\n/**\n * Indicates what rendering needs to be done for the layers within a {@link Drawable}.\n *\n * Each Drawable has a RenderFlags in {@link Drawable#renderFlags}.\n *\n * Before rendering each frame, {@link Renderer} will call {@link Drawable#rebuildRenderFlags} on each {@link Drawable}.\n *\n * Then, when rendering a frame, Renderer will apply rendering passes to each Drawable according on what flags are set in {@link Drawable#renderFlags}.\n *\n * @private\n */\nclass RenderFlags {\n\n /**\n * @private\n */\n constructor() {\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate which layers are visible within the {@link Drawable}.\n *\n * This is a list of IDs of visible layers within the {@link Drawable}. The IDs will be whatever the\n * {@link Drawable} uses to identify its layers, usually integers.\n *\n * @property visibleLayers\n * @type {Number[]}\n */\n this.visibleLayers = [];\n\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate which {@link SectionPlane}s are active within each layer of the {@link Drawable}.\n *\n * Layout is as follows:\n *\n * ````[\n * false, false, true, // Layer 0, SectionPlanes 0, 1, 2\n * false, true, true, // Layer 1, SectionPlanes 0, 1, 2\n * true, false, true // Layer 2, SectionPlanes 0, 1, 2\n * ]````\n *\n * @property sectionPlanesActivePerLayer\n * @type {Boolean[]}\n */\n this.sectionPlanesActivePerLayer = [];\n\n this.reset();\n }\n\n /**\n * @private\n */\n reset() {\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate whether the {@link Drawable} is culled.\n * \n * When this is ````false````, then all of the other properties on ````RenderFlags```` will remain at their default values.\n * \n * @property culled\n * @type {Boolean}\n */\n this.culled = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate whether the {@link Drawable} is sliced by any {@link SectionPlane}s.\n *\n * @property sectioned\n * @type {Boolean}\n */\n this.sectioned = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the number of layers within the {@link Drawable}.\n *\n * @property numLayers\n * @type {Number}\n */\n this.numLayers = 0;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the number of visible layers within the {@link Drawable}.\n *\n * @property numVisibleLayers\n * @type {Number}\n */\n this.numVisibleLayers = 0;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs {@link Drawable#drawColorOpaque}.\n * @property colorOpaque\n * @type {boolean}\n */\n this.colorOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs {@link Drawable#drawColorTransparent}.\n * @property colorTransparent\n * @type {boolean}\n */\n this.colorTransparent = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs {@link Drawable#drawEdgesColorOpaque}.\n * @property edgesOpaque\n * @type {boolean}\n */\n this.edgesOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs {@link Drawable#drawEdgesColorTransparent}.\n * @property edgesTransparent\n * @type {boolean}\n */\n this.edgesTransparent = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs an opaque {@link Drawable#drawSilhouetteXRayed}.\n * @property xrayedSilhouetteOpaque\n * @type {boolean}\n */\n this.xrayedSilhouetteOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs an opaque {@link Drawable#drawEdgesXRayed}.\n * @property xrayedEdgesOpaque\n * @type {boolean}\n */\n this.xrayedEdgesOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs a transparent {@link Drawable#drawSilhouetteXRayed}.\n * @property xrayedSilhouetteTransparent\n * @type {boolean}\n */\n this.xrayedSilhouetteTransparent = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs a transparent {@link Drawable#drawEdgesXRayed}.\n * @property xrayedEdgesTransparent\n * @type {boolean}\n */\n this.xrayedEdgesTransparent = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs an opaque {@link Drawable#drawSilhouetteHighlighted}.\n * @property highlightedSilhouetteOpaque\n * @type {boolean}\n */\n this.highlightedSilhouetteOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs an opaque {@link Drawable#drawEdgesHighlighted}.\n * @property highlightedEdgesOpaque\n * @type {boolean}\n */\n this.highlightedEdgesOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs a transparent {@link Drawable#drawSilhouetteHighlighted}.\n * @property highlightedSilhouetteTransparent\n * @type {boolean}\n */\n this.highlightedSilhouetteTransparent = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs a transparent {@link Drawable#drawEdgesHighlighted}.\n * @property highlightedEdgesTransparent\n * @type {boolean}\n */\n this.highlightedEdgesTransparent = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs an opaque {@link Drawable#drawSilhouetteSelected}.\n * @property selectedSilhouetteOpaque\n * @type {boolean}\n */\n this.selectedSilhouetteOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs an opaque {@link Drawable#drawEdgesSelected}.\n * @property selectedEdgesOpaque\n * @type {boolean}\n */\n this.selectedEdgesOpaque = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs a transparent {@link Drawable#drawSilhouetteSelected}.\n * @property selectedSilhouetteTransparent\n * @type {boolean}\n */\n this.selectedSilhouetteTransparent = false;\n\n /**\n * Set by {@link Drawable#rebuildRenderFlags} to indicate the {@link Drawable} needs a transparent {@link Drawable#drawEdgesSelected}.\n * @property selectedEdgesTransparent\n * @type {boolean}\n */\n this.selectedEdgesTransparent = false;\n }\n}\n\n/**\n Fired when this Mesh is picked via a call to {@link Scene/pick:method\"}}Scene#pick(){{/crossLink}}.\n\n The event parameters will be the hit result returned by the {@link Scene/pick:method\"}}Scene#pick(){{/crossLink}} method.\n @event picked\n */\n\nconst obb = math.OBB3();\nconst angleAxis$2 = math.vec4();\nconst q1$2 = math.vec4();\nconst q2$2 = math.vec4();\nconst xAxis$2 = math.vec3([1, 0, 0]);\nconst yAxis$2 = math.vec3([0, 1, 0]);\nconst zAxis$2 = math.vec3([0, 0, 1]);\n\nconst veca$1 = math.vec3(3);\nconst vecb$1 = math.vec3(3);\n\nconst identityMat$2 = math.identityMat4();\n\n/**\n * @desc An {@link Entity} that is a drawable element, with a {@link Geometry} and a {@link Material}, that can be\n * connected into a scene graph using {@link Node}s.\n *\n * ## Usage\n *\n * The example below is the same as the one given for {@link Node}, since the two classes work together. In this example,\n * we'll create a scene graph in which a root {@link Node} represents a group and the Meshes are leaves.\n *\n * Since {@link Node} implements {@link Entity}, we can designate the root {@link Node} as a model, causing it to be registered by its\n * ID in {@link Scene#models}.\n *\n * Since Mesh also implements {@link Entity}, we can designate the leaf Meshes as objects, causing them to\n * be registered by their IDs in {@link Scene#objects}.\n *\n * We can then find those {@link Entity} types in {@link Scene#models} and {@link Scene#objects}.\n *\n * We can also update properties of our object-Meshes via calls to {@link Scene#setObjectsHighlighted} etc.\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#sceneGraph)]\n *\n * ````javascript\n * import {Viewer, Mesh, Node, PhongMaterial, buildBoxGeometry, ReadableGeometry} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * const boxGeometry = new ReadableGeometry(viewer.scene, buildBoxGeometry({\n * xSize: 1,\n * ySize: 1,\n * zSize: 1\n * }));\n *\n * new Node(viewer.scene, {\n * id: \"table\",\n * isModel: true, // <---------- Node represents a model, so is registered by ID in viewer.scene.models\n * rotation: [0, 50, 0],\n * position: [0, 0, 0],\n * scale: [1, 1, 1],\n *\n * children: [\n *\n * new Mesh(viewer.scene, { // Red table leg\n * id: \"redLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1, 0.3, 0.3]\n * }),\n * geometry: boxGeometry\n * }),\n *\n * new Mesh(viewer.scene, { // Green table leg\n * id: \"greenLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.3, 1.0, 0.3]\n * }),\n * geometry: boxGeometry\n * }),\n *\n * new Mesh(viewer.scene, {// Blue table leg\n * id: \"blueLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.3, 0.3, 1.0]\n * }),\n * geometry: boxGeometry\n * }),\n *\n * new Mesh(viewer.scene, { // Yellow table leg\n * id: \"yellowLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1.0, 1.0, 0.0]\n * }),\n * geometry: boxGeometry\n * }),\n *\n * new Mesh(viewer.scene, { // Purple table top\n * id: \"tableTop\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1.0, 0.3, 1.0]\n * }),\n * geometry: boxGeometry\n * })\n * ]\n * });\n *\n * // Find Nodes and Meshes by their IDs\n *\n * var table = viewer.scene.models[\"table\"]; // Since table Node has isModel == true\n *\n * var redLeg = viewer.scene.objects[\"redLeg\"]; // Since the Meshes have isObject == true\n * var greenLeg = viewer.scene.objects[\"greenLeg\"];\n * var blueLeg = viewer.scene.objects[\"blueLeg\"];\n *\n * // Highlight one of the table leg Meshes\n *\n * viewer.scene.setObjectsHighlighted([\"redLeg\"], true); // Since the Meshes have isObject == true\n *\n * // Periodically update transforms on our Nodes and Meshes\n *\n * viewer.scene.on(\"tick\", function () {\n *\n * // Rotate legs\n * redLeg.rotateY(0.5);\n * greenLeg.rotateY(0.5);\n * blueLeg.rotateY(0.5);\n *\n * // Rotate table\n * table.rotateY(0.5);\n * table.rotateX(0.3);\n * });\n * ````\n *\n * ## Metadata\n *\n * As mentioned, we can also associate {@link MetaModel}s and {@link MetaObject}s with our {@link Node}s and Meshes,\n * within a {@link MetaScene}. See {@link MetaScene} for an example.\n *\n * @implements {Entity}\n * @implements {Drawable}\n */\nclass Mesh extends Component {\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent scene, generated automatically when omitted.\n * @param {String} [cfg.originalSystemId] ID of the corresponding object within the originating system, if any.\n * @param {Boolean} [cfg.isModel] Specify ````true```` if this Mesh represents a model, in which case the Mesh will be registered by {@link Mesh#id} in {@link Scene#models} and may also have a corresponding {@link MetaModel} with matching {@link MetaModel#id}, registered by that ID in {@link MetaScene#metaModels}.\n * @param {Boolean} [cfg.isObject] Specify ````true```` if this Mesh represents an object, in which case the Mesh will be registered by {@link Mesh#id} in {@link Scene#objects} and may also have a corresponding {@link MetaObject} with matching {@link MetaObject#id}, registered by that ID in {@link MetaScene#metaObjects}.\n * @param {Node} [cfg.parent] The parent Node.\n * @param {Number[]} [cfg.origin] World-space origin for this Mesh. When this is given, then ````matrix````, ````position```` and ````geometry```` are all assumed to be relative to this center.\n * @param {Number[]} [cfg.rtcCenter] Deprecated - renamed to ````origin````.\n * @param {Number[]} [cfg.position=[0,0,0]] 3D position of this Mesh, relative to ````origin````.\n * @param {Number[]} [cfg.scale=[1,1,1]] Local scale.\n * @param {Number[]} [cfg.rotation=[0,0,0]] Local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] Local modelling transform matrix. Overrides the position, scale and rotation parameters.\n * @param {Number[]} [cfg.offset=[0,0,0]] World-space 3D translation offset. Translates the Mesh in World space, after modelling transforms.\n * @param {Boolean} [cfg.occluder=true] Indicates if the Mesh is able to occlude {@link Marker}s.\n * @param {Boolean} [cfg.visible=true] Indicates if the Mesh is initially visible.\n * @param {Boolean} [cfg.culled=false] Indicates if the Mesh is initially culled from view.\n * @param {Boolean} [cfg.pickable=true] Indicates if the Mesh is initially pickable.\n * @param {Boolean} [cfg.clippable=true] Indicates if the Mesh is initially clippable.\n * @param {Boolean} [cfg.collidable=true] Indicates if the Mesh is initially included in boundary calculations.\n * @param {Boolean} [cfg.castsShadow=true] Indicates if the Mesh initially casts shadows.\n * @param {Boolean} [cfg.receivesShadow=true] Indicates if the Mesh initially receives shadows.\n * @param {Boolean} [cfg.xrayed=false] Indicates if the Mesh is initially xrayed.\n * @param {Boolean} [cfg.highlighted=false] Indicates if the Mesh is initially highlighted.\n * @param {Boolean} [cfg.selected=false] Indicates if the Mesh is initially selected.\n * @param {Boolean} [cfg.edges=false] Indicates if the Mesh's edges are initially emphasized.\n * @param {Boolean} [cfg.background=false] Indicates if the Mesh should act as background, e.g., it can be used for a skybox.\n * @param {Number[]} [cfg.colorize=[1.0,1.0,1.0]] Mesh's initial RGB colorize color, multiplies by the rendered fragment colors.\n * @param {Number} [cfg.opacity=1.0] Mesh's initial opacity factor, multiplies by the rendered fragment alpha.\n * @param {String} [cfg.billboard=\"none\"] Mesh's billboarding behaviour. Options are \"none\" for no billboarding, \"spherical\" to always directly face {@link Camera.eye}, rotating both vertically and horizontally, or \"cylindrical\" to face the {@link Camera#eye} while rotating only about its vertically axis (use that mode for things like trees on a landscape).\n * @param {Geometry} [cfg.geometry] {@link Geometry} to define the shape of this Mesh. Inherits {@link Scene#geometry} by default.\n * @param {Material} [cfg.material] {@link Material} to define the normal rendered appearance for this Mesh. Inherits {@link Scene#material} by default.\n * @param {EmphasisMaterial} [cfg.xrayMaterial] {@link EmphasisMaterial} to define the xrayed appearance for this Mesh. Inherits {@link Scene#xrayMaterial} by default.\n * @param {EmphasisMaterial} [cfg.highlightMaterial] {@link EmphasisMaterial} to define the xrayed appearance for this Mesh. Inherits {@link Scene#highlightMaterial} by default.\n * @param {EmphasisMaterial} [cfg.selectedMaterial] {@link EmphasisMaterial} to define the selected appearance for this Mesh. Inherits {@link Scene#selectedMaterial} by default.\n * @param {EmphasisMaterial} [cfg.edgeMaterial] {@link EdgeMaterial} to define the appearance of enhanced edges for this Mesh. Inherits {@link Scene#edgeMaterial} by default.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n /**\n * ID of the corresponding object within the originating system, if any.\n *\n * @type {String}\n * @abstract\n */\n this.originalSystemId = (cfg.originalSystemId || this.id);\n\n /** @private **/\n this.renderFlags = new RenderFlags();\n\n this._state = new RenderState({ // NOTE: Renderer gets modeling and normal matrices from Mesh#matrix and Mesh.#normalWorldMatrix\n visible: true,\n culled: false,\n pickable: null,\n clippable: null,\n collidable: null,\n occluder: (cfg.occluder !== false),\n castsShadow: null,\n receivesShadow: null,\n xrayed: false,\n highlighted: false,\n selected: false,\n edges: false,\n stationary: !!cfg.stationary,\n background: !!cfg.background,\n billboard: this._checkBillboard(cfg.billboard),\n layer: null,\n colorize: null,\n pickID: this.scene._renderer.getPickID(this),\n drawHash: \"\",\n pickHash: \"\",\n offset: math.vec3(),\n origin: null,\n originHash: null\n });\n\n this._drawRenderer = null;\n this._shadowRenderer = null;\n this._emphasisFillRenderer = null;\n this._emphasisEdgesRenderer = null;\n this._pickMeshRenderer = null;\n this._pickTriangleRenderer = null;\n this._occlusionRenderer = null;\n\n this._geometry = cfg.geometry ? this._checkComponent2([\"ReadableGeometry\", \"VBOGeometry\"], cfg.geometry) : this.scene.geometry;\n this._material = cfg.material ? this._checkComponent2([\"PhongMaterial\", \"MetallicMaterial\", \"SpecularMaterial\", \"LambertMaterial\"], cfg.material) : this.scene.material;\n this._xrayMaterial = cfg.xrayMaterial ? this._checkComponent(\"EmphasisMaterial\", cfg.xrayMaterial) : this.scene.xrayMaterial;\n this._highlightMaterial = cfg.highlightMaterial ? this._checkComponent(\"EmphasisMaterial\", cfg.highlightMaterial) : this.scene.highlightMaterial;\n this._selectedMaterial = cfg.selectedMaterial ? this._checkComponent(\"EmphasisMaterial\", cfg.selectedMaterial) : this.scene.selectedMaterial;\n this._edgeMaterial = cfg.edgeMaterial ? this._checkComponent(\"EdgeMaterial\", cfg.edgeMaterial) : this.scene.edgeMaterial;\n\n this._parentNode = null;\n\n this._aabb = null;\n this._aabbDirty = true;\n\n this._numTriangles = (this._geometry ? this._geometry.numTriangles : 0);\n\n this.scene._aabbDirty = true;\n\n this._scale = math.vec3();\n this._quaternion = math.identityQuaternion();\n this._rotation = math.vec3();\n this._position = math.vec3();\n\n this._worldMatrix = math.identityMat4();\n this._worldNormalMatrix = math.identityMat4();\n\n this._localMatrixDirty = true;\n this._worldMatrixDirty = true;\n this._worldNormalMatrixDirty = true;\n\n const origin = cfg.origin || cfg.rtcCenter;\n if (origin) {\n this._state.origin = math.vec3(origin);\n this._state.originHash = origin.join();\n }\n\n if (cfg.matrix) {\n this.matrix = cfg.matrix;\n } else {\n this.scale = cfg.scale;\n this.position = cfg.position;\n if (cfg.quaternion) ; else {\n this.rotation = cfg.rotation;\n }\n }\n\n this._isObject = cfg.isObject;\n if (this._isObject) {\n this.scene._registerObject(this);\n }\n\n this._isModel = cfg.isModel;\n if (this._isModel) {\n this.scene._registerModel(this);\n }\n\n this.visible = cfg.visible;\n this.culled = cfg.culled;\n this.pickable = cfg.pickable;\n this.clippable = cfg.clippable;\n this.collidable = cfg.collidable;\n this.castsShadow = cfg.castsShadow;\n this.receivesShadow = cfg.receivesShadow;\n this.xrayed = cfg.xrayed;\n this.highlighted = cfg.highlighted;\n this.selected = cfg.selected;\n this.edges = cfg.edges;\n this.layer = cfg.layer;\n this.colorize = cfg.colorize;\n this.opacity = cfg.opacity;\n this.offset = cfg.offset;\n\n if (cfg.parentId) {\n const parentNode = this.scene.components[cfg.parentId];\n if (!parentNode) {\n this.error(\"Parent not found: '\" + cfg.parentId + \"'\");\n } else if (!parentNode.isNode) {\n this.error(\"Parent is not a Node: '\" + cfg.parentId + \"'\");\n } else {\n parentNode.addChild(this);\n }\n this._parentNode = parentNode;\n } else if (cfg.parent) {\n if (!cfg.parent.isNode) {\n this.error(\"Parent is not a Node\");\n }\n cfg.parent.addChild(this);\n this._parentNode = cfg.parent;\n }\n\n this.compile();\n }\n\n /**\n @private\n */\n get type() {\n return \"Mesh\";\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Mesh members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Returns true to indicate that this Component is a Mesh.\n * @final\n * @type {Boolean}\n */\n get isMesh() {\n return true;\n }\n\n /**\n * The parent Node.\n *\n * The parent Node may also be set by passing the Mesh to the parent's {@link Node#addChild} method.\n *\n * @type {Node}\n */\n get parent() {\n return this._parentNode;\n }\n\n /**\n * Defines the shape of this Mesh.\n *\n * Set to {@link Scene#geometry} by default.\n *\n * @type {Geometry}\n */\n get geometry() {\n return this._geometry;\n }\n\n /**\n * Defines the appearance of this Mesh when rendering normally, ie. when not xrayed, highlighted or selected.\n *\n * Set to {@link Scene#material} by default.\n *\n * @type {Material}\n */\n get material() {\n return this._material;\n }\n\n /**\n * Gets the Mesh's local translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get position() {\n return this._position;\n }\n\n /**\n * Sets the Mesh's local translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set position(value) {\n this._position.set(value || [0, 0, 0]);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Mesh's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get rotation() {\n return this._rotation;\n }\n\n /**\n * Sets the Mesh's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set rotation(value) {\n this._rotation.set(value || [0, 0, 0]);\n math.eulerToQuaternion(this._rotation, \"XYZ\", this._quaternion);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Mesh's local rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n get quaternion() {\n return this._quaternion;\n }\n\n /**\n * Sets the Mesh's local rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n set quaternion(value) {\n this._quaternion.set(value || [0, 0, 0, 1]);\n math.quaternionToEuler(this._quaternion, \"XYZ\", this._rotation);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Mesh's local scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the Mesh's local scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n */\n set scale(value) {\n this._scale.set(value || [1, 1, 1]);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Mesh's local modeling transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n get matrix() {\n if (this._localMatrixDirty) {\n if (!this.__localMatrix) {\n this.__localMatrix = math.identityMat4();\n }\n math.composeMat4(this._position, this._quaternion, this._scale, this.__localMatrix);\n this._localMatrixDirty = false;\n }\n return this.__localMatrix;\n }\n\n /**\n * Sets the Mesh's local modeling transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n set matrix(value) {\n if (!this.__localMatrix) {\n this.__localMatrix = math.identityMat4();\n }\n this.__localMatrix.set(value || identityMat$2);\n math.decomposeMat4(this.__localMatrix, this._position, this._quaternion, this._scale);\n this._localMatrixDirty = false;\n this._setWorldMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Mesh's World matrix.\n *\n * @property worldMatrix\n * @type {Number[]}\n */\n get worldMatrix() {\n if (this._worldMatrixDirty) {\n this._buildWorldMatrix();\n }\n return this._worldMatrix;\n }\n\n /**\n * Gets the Mesh's World normal matrix.\n *\n * @type {Number[]}\n */\n get worldNormalMatrix() {\n if (this._worldNormalMatrixDirty) {\n this._buildWorldNormalMatrix();\n }\n return this._worldNormalMatrix;\n }\n\n /**\n * Returns true to indicate that Mesh implements {@link Entity}.\n *\n * @returns {Boolean}\n */\n get isEntity() {\n return true;\n }\n\n /**\n * Returns ````true```` if this Mesh represents a model.\n *\n * When this returns ````true````, the Mesh will be registered by {@link Mesh#id} in {@link Scene#models} and\n * may also have a corresponding {@link MetaModel}.\n *\n * @type {Boolean}\n */\n get isModel() {\n return this._isModel;\n }\n\n /**\n * Returns ````true```` if this Mesh represents an object.\n *\n * When this returns ````true````, the Mesh will be registered by {@link Mesh#id} in {@link Scene#objects} and\n * may also have a corresponding {@link MetaObject}.\n *\n * @type {Boolean}\n */\n get isObject() {\n return this._isObject;\n }\n\n /**\n * Gets the Mesh's World-space 3D axis-aligned bounding box.\n *\n * Represented by a six-element Float64Array containing the min/max extents of the\n * axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````.\n *\n * @type {Number[]}\n */\n get aabb() {\n if (this._aabbDirty) {\n this._updateAABB();\n }\n return this._aabb;\n }\n\n /**\n * Gets the 3D origin of the Mesh's {@link Geometry}'s vertex positions.\n *\n * When this is given, then {@link Mesh#matrix}, {@link Mesh#position} and {@link Mesh#geometry} are all assumed to be relative to this center position.\n *\n * @type {Float64Array}\n */\n get origin() {\n return this._state.origin;\n }\n\n /**\n * Sets the 3D origin of the Mesh's {@link Geometry}'s vertex positions.\n *\n * When this is given, then {@link Mesh#matrix}, {@link Mesh#position} and {@link Mesh#geometry} are all assumed to be relative to this center position.\n *\n * @type {Float64Array}\n */\n set origin(origin) {\n if (origin) {\n if (!this._state.origin) {\n this._state.origin = math.vec3();\n }\n this._state.origin.set(origin);\n this._state.originHash = origin.join();\n this._setAABBDirty();\n this.scene._aabbDirty = true;\n } else {\n if (this._state.origin) {\n this._state.origin = null;\n this._state.originHash = null;\n this._setAABBDirty();\n this.scene._aabbDirty = true;\n }\n }\n }\n\n /**\n * Gets the World-space origin for this Mesh.\n *\n * Deprecated and replaced by {@link Mesh#origin}.\n *\n * @deprecated\n * @type {Float64Array}\n */\n get rtcCenter() {\n return this.origin;\n }\n\n /**\n * Sets the World-space origin for this Mesh.\n *\n * Deprecated and replaced by {@link Mesh#origin}.\n *\n * @deprecated\n * @type {Float64Array}\n */\n set rtcCenter(rtcCenter) {\n this.origin = rtcCenter;\n }\n\n /**\n * The approximate number of triangles in this Mesh.\n *\n * @type {Number}\n */\n get numTriangles() {\n return this._numTriangles;\n }\n\n /**\n * Gets if this Mesh is visible.\n *\n * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````.\n *\n * When {@link Mesh#isObject} and {@link Mesh#visible} are both ````true```` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#visibleObjects}.\n *\n * @type {Boolean}\n */\n get visible() {\n return this._state.visible;\n }\n\n /**\n * Sets if this Mesh is visible.\n *\n * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````.\n *\n * When {@link Mesh#isObject} and {@link Mesh#visible} are both ````true```` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#visibleObjects}.\n *\n * @type {Boolean}\n */\n set visible(visible) {\n visible = visible !== false;\n this._state.visible = visible;\n if (this._isObject) {\n this.scene._objectVisibilityUpdated(this, visible);\n }\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh is xrayed.\n *\n * XRayed appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#xrayMaterial}.\n *\n * When {@link Mesh#isObject} and {@link Mesh#xrayed} are both ````true``` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#xrayedObjects}.\n *\n * @type {Boolean}\n */\n get xrayed() {\n return this._state.xrayed;\n }\n\n /**\n * Sets if this Mesh is xrayed.\n *\n * XRayed appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#xrayMaterial}.\n *\n * When {@link Mesh#isObject} and {@link Mesh#xrayed} are both ````true``` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#xrayedObjects}.\n *\n * @type {Boolean}\n */\n set xrayed(xrayed) {\n xrayed = !!xrayed;\n if (this._state.xrayed === xrayed) {\n return;\n }\n this._state.xrayed = xrayed;\n if (this._isObject) {\n this.scene._objectXRayedUpdated(this, xrayed);\n }\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh is highlighted.\n *\n * Highlighted appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#highlightMaterial}.\n *\n * When {@link Mesh#isObject} and {@link Mesh#highlighted} are both ````true```` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#highlightedObjects}.\n *\n * @type {Boolean}\n */\n get highlighted() {\n return this._state.highlighted;\n }\n\n /**\n * Sets if this Mesh is highlighted.\n *\n * Highlighted appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#highlightMaterial}.\n *\n * When {@link Mesh#isObject} and {@link Mesh#highlighted} are both ````true```` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#highlightedObjects}.\n *\n * @type {Boolean}\n */\n set highlighted(highlighted) {\n highlighted = !!highlighted;\n if (highlighted === this._state.highlighted) {\n return;\n }\n this._state.highlighted = highlighted;\n if (this._isObject) {\n this.scene._objectHighlightedUpdated(this, highlighted);\n }\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh is selected.\n *\n * Selected appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#selectedMaterial}.\n *\n * When {@link Mesh#isObject} and {@link Mesh#selected} are both ````true``` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#selectedObjects}.\n *\n * @type {Boolean}\n */\n get selected() {\n return this._state.selected;\n }\n\n /**\n * Sets if this Mesh is selected.\n *\n * Selected appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#selectedMaterial}.\n *\n * When {@link Mesh#isObject} and {@link Mesh#selected} are both ````true``` the Mesh will be\n * registered by {@link Mesh#id} in {@link Scene#selectedObjects}.\n *\n * @type {Boolean}\n */\n set selected(selected) {\n selected = !!selected;\n if (selected === this._state.selected) {\n return;\n }\n this._state.selected = selected;\n if (this._isObject) {\n this.scene._objectSelectedUpdated(this, selected);\n }\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh is edge-enhanced.\n *\n * Edge appearance is configured by the {@link EdgeMaterial} referenced by {@link Mesh#edgeMaterial}.\n *\n * @type {Boolean}\n */\n get edges() {\n return this._state.edges;\n }\n\n /**\n * Sets if this Mesh is edge-enhanced.\n *\n * Edge appearance is configured by the {@link EdgeMaterial} referenced by {@link Mesh#edgeMaterial}.\n *\n * @type {Boolean}\n */\n set edges(edges) {\n edges = !!edges;\n if (edges === this._state.edges) {\n return;\n }\n this._state.edges = edges;\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh is culled.\n *\n * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````.\n *\n * @type {Boolean}\n */\n get culled() {\n return this._state.culled;\n }\n\n /**\n * Sets if this Mesh is culled.\n *\n * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````.\n *\n * @type {Boolean}\n */\n set culled(value) {\n this._state.culled = !!value;\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * @type {Boolean}\n */\n get clippable() {\n return this._state.clippable;\n }\n\n /**\n * Sets if this Mesh is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * @type {Boolean}\n */\n set clippable(value) {\n value = value !== false;\n if (this._state.clippable === value) {\n return;\n }\n this._state.clippable = value;\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh included in boundary calculations.\n *\n * @type {Boolean}\n */\n get collidable() {\n return this._state.collidable;\n }\n\n /**\n * Sets if this Mesh included in boundary calculations.\n *\n * @type {Boolean}\n */\n set collidable(value) {\n value = value !== false;\n if (value === this._state.collidable) {\n return;\n }\n this._state.collidable = value;\n this._setAABBDirty();\n this.scene._aabbDirty = true;\n\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Entity members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Gets if this Mesh is pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * @type {Boolean}\n */\n get pickable() {\n return this._state.pickable;\n }\n\n /**\n * Sets if this Mesh is pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * @type {Boolean}\n */\n set pickable(value) {\n value = value !== false;\n if (this._state.pickable === value) {\n return;\n }\n this._state.pickable = value;\n // No need to trigger a render;\n // state is only used when picking\n }\n\n /**\n * Gets if this Mesh casts shadows.\n *\n * @type {Boolean}\n */\n get castsShadow() {\n return this._state.castsShadow;\n }\n\n /**\n * Sets if this Mesh casts shadows.\n *\n * @type {Boolean}\n */\n set castsShadow(value) {\n value = value !== false;\n if (value === this._state.castsShadow) {\n return;\n }\n this._state.castsShadow = value;\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh can have shadows cast upon it.\n *\n * @type {Boolean}\n */\n get receivesShadow() {\n return this._state.receivesShadow;\n }\n\n /**\n * Sets if this Mesh can have shadows cast upon it.\n *\n * @type {Boolean}\n */\n set receivesShadow(value) {\n value = value !== false;\n if (value === this._state.receivesShadow) {\n return;\n }\n this._state.receivesShadow = value;\n this._state.hash = value ? \"/mod/rs;\" : \"/mod;\";\n this.fire(\"dirty\", this); // Now need to (re)compile objectRenderers to include/exclude shadow mapping\n }\n\n /**\n * Gets if this Mesh can have Scalable Ambient Obscurance (SAO) applied to it.\n *\n * SAO is configured by {@link SAO}.\n *\n * @type {Boolean}\n * @abstract\n */\n get saoEnabled() {\n return false; // TODO: Support SAO on Meshes\n }\n\n /**\n * Gets the RGB colorize color for this Mesh.\n *\n * Multiplies by rendered fragment colors.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * @type {Number[]}\n */\n get colorize() {\n return this._state.colorize;\n }\n\n /**\n * Sets the RGB colorize color for this Mesh.\n *\n * Multiplies by rendered fragment colors.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * @type {Number[]}\n */\n set colorize(value) {\n let colorize = this._state.colorize;\n if (!colorize) {\n colorize = this._state.colorize = new Float32Array(4);\n colorize[3] = 1;\n }\n if (value) {\n colorize[0] = value[0];\n colorize[1] = value[1];\n colorize[2] = value[2];\n } else {\n colorize[0] = 1;\n colorize[1] = 1;\n colorize[2] = 1;\n }\n const colorized = (!!value);\n this.scene._objectColorizeUpdated(this, colorized);\n this.glRedraw();\n }\n\n /**\n * Gets the opacity factor for this Mesh.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n get opacity() {\n return this._state.colorize[3];\n }\n\n /**\n * Sets the opacity factor for this Mesh.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n set opacity(opacity) {\n let colorize = this._state.colorize;\n if (!colorize) {\n colorize = this._state.colorize = new Float32Array(4);\n colorize[0] = 1;\n colorize[1] = 1;\n colorize[2] = 1;\n }\n const opacityUpdated = (opacity !== null && opacity !== undefined);\n colorize[3] = opacityUpdated ? opacity : 1.0;\n this.scene._objectOpacityUpdated(this, opacityUpdated);\n this.glRedraw();\n }\n\n /**\n * Gets if this Mesh is transparent.\n * @returns {Boolean}\n */\n get transparent() {\n return this._material.alphaMode === 2 /* blend */ || this._state.colorize[3] < 1\n }\n\n /**\n * Gets the Mesh's rendering order relative to other Meshes.\n *\n * Default value is ````0````.\n *\n * This can be set on multiple transparent Meshes, to make them render in a specific order for correct alpha blending.\n *\n * @type {Number}\n */\n get layer() {\n return this._state.layer;\n }\n\n /**\n * Sets the Mesh's rendering order relative to other Meshes.\n *\n * Default value is ````0````.\n *\n * This can be set on multiple transparent Meshes, to make them render in a specific order for correct alpha blending.\n *\n * @type {Number}\n */\n set layer(value) {\n // TODO: Only accept rendering layer in range [0...MAX_layer]\n value = value || 0;\n value = Math.round(value);\n if (value === this._state.layer) {\n return;\n }\n this._state.layer = value;\n this._renderer.needStateSort();\n }\n\n /**\n * Gets if the Node's position is stationary.\n *\n * When true, will disable the effect of {@link Camera} translations for this Mesh, while still allowing it to rotate. This is useful for skyboxes.\n *\n * @type {Boolean}\n */\n get stationary() {\n return this._state.stationary;\n }\n\n /**\n * Gets the Node's billboarding behaviour.\n *\n * Options are:\n * * ````\"none\"```` - (default) - No billboarding.\n * * ````\"spherical\"```` - Mesh is billboarded to face the viewpoint, rotating both vertically and horizontally.\n * * ````\"cylindrical\"```` - Mesh is billboarded to face the viewpoint, rotating only about its vertically axis. Use this mode for things like trees on a landscape.\n * @type {String}\n */\n get billboard() {\n return this._state.billboard;\n }\n\n /**\n * Gets the Mesh's 3D World-space offset.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get offset() {\n return this._state.offset;\n }\n\n /**\n * Sets the Mesh's 3D World-space offset.\n *\n * The offset dynamically translates the Mesh in World-space.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * Provide a null or undefined value to reset to the default value.\n *\n * @type {Number[]}\n */\n set offset(value) {\n this._state.offset.set(value || [0, 0, 0]);\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Returns true to indicate that Mesh implements {@link Drawable}.\n * @final\n * @type {Boolean}\n */\n get isDrawable() {\n return true;\n }\n\n /**\n * Property with final value ````true```` to indicate that xeokit should render this Mesh in sorted order, relative to other Meshes.\n *\n * The sort order is determined by {@link Mesh#stateSortCompare}.\n *\n * Sorting is essential for rendering performance, so that xeokit is able to avoid applying runs of the same state changes to the GPU, ie. can collapse them.\n *\n * @type {Boolean}\n */\n get isStateSortable() {\n return true;\n }\n\n /**\n * Defines the appearance of this Mesh when xrayed.\n *\n * Mesh is xrayed when {@link Mesh#xrayed} is ````true````.\n *\n * Set to {@link Scene#xrayMaterial} by default.\n *\n * @type {EmphasisMaterial}\n */\n get xrayMaterial() {\n return this._xrayMaterial;\n }\n\n /**\n * Defines the appearance of this Mesh when highlighted.\n *\n * Mesh is xrayed when {@link Mesh#highlighted} is ````true````.\n *\n * Set to {@link Scene#highlightMaterial} by default.\n *\n * @type {EmphasisMaterial}\n */\n get highlightMaterial() {\n return this._highlightMaterial;\n }\n\n /**\n * Defines the appearance of this Mesh when selected.\n *\n * Mesh is xrayed when {@link Mesh#selected} is ````true````.\n *\n * Set to {@link Scene#selectedMaterial} by default.\n *\n * @type {EmphasisMaterial}\n */\n get selectedMaterial() {\n return this._selectedMaterial;\n }\n\n /**\n * Defines the appearance of this Mesh when edges are enhanced.\n *\n * Mesh is xrayed when {@link Mesh#edges} is ````true````.\n *\n * Set to {@link Scene#edgeMaterial} by default.\n *\n * @type {EdgeMaterial}\n */\n get edgeMaterial() {\n return this._edgeMaterial;\n }\n\n _checkBillboard(value) {\n value = value || \"none\";\n if (value !== \"spherical\" && value !== \"cylindrical\" && value !== \"none\") {\n this.error(\"Unsupported value for 'billboard': \" + value + \" - accepted values are \" +\n \"'spherical', 'cylindrical' and 'none' - defaulting to 'none'.\");\n value = \"none\";\n }\n return value;\n }\n\n /**\n * Called by xeokit to compile shaders for this Mesh.\n * @private\n */\n compile() {\n const drawHash = this._makeDrawHash();\n if (this._state.drawHash !== drawHash) {\n this._state.drawHash = drawHash;\n this._putDrawRenderers();\n this._drawRenderer = DrawRenderer.get(this);\n // this._shadowRenderer = ShadowRenderer.get(this);\n this._emphasisFillRenderer = EmphasisFillRenderer.get(this);\n this._emphasisEdgesRenderer = EmphasisEdgesRenderer.get(this);\n }\n const pickHash = this._makePickHash();\n if (this._state.pickHash !== pickHash) {\n this._state.pickHash = pickHash;\n this._putPickRenderers();\n this._pickMeshRenderer = PickMeshRenderer.get(this);\n }\n if (this._state.occluder) {\n const occlusionHash = this._makeOcclusionHash();\n if (this._state.occlusionHash !== occlusionHash) {\n this._state.occlusionHash = occlusionHash;\n this._putOcclusionRenderer();\n this._occlusionRenderer = OcclusionRenderer.get(this);\n }\n }\n }\n\n _setLocalMatrixDirty() {\n this._localMatrixDirty = true;\n this._setWorldMatrixDirty();\n }\n\n _setWorldMatrixDirty() {\n this._worldMatrixDirty = true;\n this._worldNormalMatrixDirty = true;\n }\n\n _buildWorldMatrix() {\n const localMatrix = this.matrix;\n if (!this._parentNode) {\n for (let i = 0, len = localMatrix.length; i < len; i++) {\n this._worldMatrix[i] = localMatrix[i];\n }\n } else {\n math.mulMat4(this._parentNode.worldMatrix, localMatrix, this._worldMatrix);\n }\n this._worldMatrixDirty = false;\n }\n\n _buildWorldNormalMatrix() {\n if (this._worldMatrixDirty) {\n this._buildWorldMatrix();\n }\n if (!this._worldNormalMatrix) {\n this._worldNormalMatrix = math.mat4();\n }\n // Note: order of inverse and transpose doesn't matter\n math.transposeMat4(this._worldMatrix, this._worldNormalMatrix);\n math.inverseMat4(this._worldNormalMatrix);\n this._worldNormalMatrixDirty = false;\n }\n\n _setAABBDirty() {\n if (this.collidable) {\n for (let node = this; node; node = node._parentNode) {\n node._aabbDirty = true;\n }\n }\n }\n\n _updateAABB() {\n this.scene._aabbDirty = true;\n if (!this._aabb) {\n this._aabb = math.AABB3();\n }\n this._buildAABB(this.worldMatrix, this._aabb); // Mesh or VBOSceneModel\n this._aabbDirty = false;\n }\n\n _webglContextRestored() {\n if (this._drawRenderer) {\n this._drawRenderer.webglContextRestored();\n }\n if (this._shadowRenderer) {\n this._shadowRenderer.webglContextRestored();\n }\n if (this._emphasisFillRenderer) {\n this._emphasisFillRenderer.webglContextRestored();\n }\n if (this._emphasisEdgesRenderer) {\n this._emphasisEdgesRenderer.webglContextRestored();\n }\n if (this._pickMeshRenderer) {\n this._pickMeshRenderer.webglContextRestored();\n }\n if (this._pickTriangleRenderer) {\n this._pickMeshRenderer.webglContextRestored();\n }\n if (this._occlusionRenderer) {\n this._occlusionRenderer.webglContextRestored();\n }\n }\n\n _makeDrawHash() {\n const scene = this.scene;\n const hash = [\n scene.canvas.canvas.id,\n (scene.gammaInput ? \"gi;\" : \";\") + (scene.gammaOutput ? \"go\" : \"\"),\n scene._lightsState.getHash(),\n scene._sectionPlanesState.getHash()\n ];\n const state = this._state;\n if (state.stationary) {\n hash.push(\"/s\");\n }\n if (state.billboard === \"none\") {\n hash.push(\"/n\");\n } else if (state.billboard === \"spherical\") {\n hash.push(\"/s\");\n } else if (state.billboard === \"cylindrical\") {\n hash.push(\"/c\");\n }\n if (state.receivesShadow) {\n hash.push(\"/rs\");\n }\n hash.push(\";\");\n return hash.join(\"\");\n }\n\n _makePickHash() {\n const scene = this.scene;\n const hash = [\n scene.canvas.canvas.id,\n scene._sectionPlanesState.getHash()\n ];\n const state = this._state;\n if (state.stationary) {\n hash.push(\"/s\");\n }\n if (state.billboard === \"none\") {\n hash.push(\"/n\");\n } else if (state.billboard === \"spherical\") {\n hash.push(\"/s\");\n } else if (state.billboard === \"cylindrical\") {\n hash.push(\"/c\");\n }\n hash.push(\";\");\n return hash.join(\"\");\n }\n\n _makeOcclusionHash() {\n const scene = this.scene;\n const hash = [\n scene.canvas.canvas.id,\n scene._sectionPlanesState.getHash()\n ];\n const state = this._state;\n if (state.stationary) {\n hash.push(\"/s\");\n }\n if (state.billboard === \"none\") {\n hash.push(\"/n\");\n } else if (state.billboard === \"spherical\") {\n hash.push(\"/s\");\n } else if (state.billboard === \"cylindrical\") {\n hash.push(\"/c\");\n }\n hash.push(\";\");\n return hash.join(\"\");\n }\n\n _buildAABB(worldMatrix, aabb) {\n\n math.transformOBB3(worldMatrix, this._geometry.obb, obb);\n math.OBB3ToAABB3(obb, aabb);\n\n const offset = this._state.offset;\n\n aabb[0] += offset[0];\n aabb[1] += offset[1];\n aabb[2] += offset[2];\n aabb[3] += offset[0];\n aabb[4] += offset[1];\n aabb[5] += offset[2];\n\n if (this._state.origin) {\n const origin = this._state.origin;\n aabb[0] += origin[0];\n aabb[1] += origin[1];\n aabb[2] += origin[2];\n aabb[3] += origin[0];\n aabb[4] += origin[1];\n aabb[5] += origin[2];\n }\n }\n\n /**\n * Rotates the Mesh about the given local axis by the given increment.\n *\n * @param {Number[]} axis Local axis about which to rotate.\n * @param {Number} angle Angle increment in degrees.\n */\n rotate(axis, angle) {\n angleAxis$2[0] = axis[0];\n angleAxis$2[1] = axis[1];\n angleAxis$2[2] = axis[2];\n angleAxis$2[3] = angle * math.DEGTORAD;\n math.angleAxisToQuaternion(angleAxis$2, q1$2);\n math.mulQuaternions(this.quaternion, q1$2, q2$2);\n this.quaternion = q2$2;\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n return this;\n }\n\n /**\n * Rotates the Mesh about the given World-space axis by the given increment.\n *\n * @param {Number[]} axis Local axis about which to rotate.\n * @param {Number} angle Angle increment in degrees.\n */\n rotateOnWorldAxis(axis, angle) {\n angleAxis$2[0] = axis[0];\n angleAxis$2[1] = axis[1];\n angleAxis$2[2] = axis[2];\n angleAxis$2[3] = angle * math.DEGTORAD;\n math.angleAxisToQuaternion(angleAxis$2, q1$2);\n math.mulQuaternions(q1$2, this.quaternion, q1$2);\n //this.quaternion.premultiply(q1);\n return this;\n }\n\n /**\n * Rotates the Mesh about the local X-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateX(angle) {\n return this.rotate(xAxis$2, angle);\n }\n\n /**\n * Rotates the Mesh about the local Y-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateY(angle) {\n return this.rotate(yAxis$2, angle);\n }\n\n /**\n * Rotates the Mesh about the local Z-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateZ(angle) {\n return this.rotate(zAxis$2, angle);\n }\n\n /**\n * Translates the Mesh along local space vector by the given increment.\n *\n * @param {Number[]} axis Normalized local space 3D vector along which to translate.\n * @param {Number} distance Distance to translate along the vector.\n */\n translate(axis, distance) {\n math.vec3ApplyQuaternion(this.quaternion, axis, veca$1);\n math.mulVec3Scalar(veca$1, distance, vecb$1);\n math.addVec3(this.position, vecb$1, this.position);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n return this;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Drawable members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Translates the Mesh along the local X-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the X-axis.\n */\n translateX(distance) {\n return this.translate(xAxis$2, distance);\n }\n\n /**\n * Translates the Mesh along the local Y-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the Y-axis.\n */\n translateY(distance) {\n return this.translate(yAxis$2, distance);\n }\n\n /**\n * Translates the Mesh along the local Z-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the Z-axis.\n */\n translateZ(distance) {\n return this.translate(zAxis$2, distance);\n }\n\n _putDrawRenderers() {\n if (this._drawRenderer) {\n this._drawRenderer.put();\n this._drawRenderer = null;\n }\n if (this._shadowRenderer) {\n this._shadowRenderer.put();\n this._shadowRenderer = null;\n }\n if (this._emphasisFillRenderer) {\n this._emphasisFillRenderer.put();\n this._emphasisFillRenderer = null;\n }\n if (this._emphasisEdgesRenderer) {\n this._emphasisEdgesRenderer.put();\n this._emphasisEdgesRenderer = null;\n }\n }\n\n _putPickRenderers() {\n if (this._pickMeshRenderer) {\n this._pickMeshRenderer.put();\n this._pickMeshRenderer = null;\n }\n if (this._pickTriangleRenderer) {\n this._pickTriangleRenderer.put();\n this._pickTriangleRenderer = null;\n }\n }\n\n _putOcclusionRenderer() {\n if (this._occlusionRenderer) {\n this._occlusionRenderer.put();\n this._occlusionRenderer = null;\n }\n }\n\n /**\n * Comparison function used by the renderer to determine the order in which xeokit should render the Mesh, relative to to other Meshes.\n *\n * xeokit requires this method because Mesh implements {@link Drawable}.\n *\n * Sorting is essential for rendering performance, so that xeokit is able to avoid needlessly applying runs of the same rendering state changes to the GPU, ie. can collapse them.\n *\n * @param {Mesh} mesh1\n * @param {Mesh} mesh2\n * @returns {number}\n */\n stateSortCompare(mesh1, mesh2) {\n return (mesh1._state.layer - mesh2._state.layer)\n || (mesh1._drawRenderer.id - mesh2._drawRenderer.id) // Program state\n || (mesh1._material._state.id - mesh2._material._state.id) // Material state\n || (mesh1._geometry._state.id - mesh2._geometry._state.id); // Geometry state\n }\n\n /** @private */\n rebuildRenderFlags() {\n this.renderFlags.reset();\n if (!this._getActiveSectionPlanes()) {\n this.renderFlags.culled = true;\n return;\n }\n this.renderFlags.numLayers = 1;\n this.renderFlags.numVisibleLayers = 1;\n this.renderFlags.visibleLayers[0] = 0;\n this._updateRenderFlags();\n }\n\n /**\n * @private\n */\n _updateRenderFlags() {\n\n const renderFlags = this.renderFlags;\n const state = this._state;\n\n if (state.xrayed) {\n const xrayMaterial = this._xrayMaterial._state;\n if (xrayMaterial.fill) {\n if (xrayMaterial.fillAlpha < 1.0) {\n renderFlags.xrayedSilhouetteTransparent = true;\n } else {\n renderFlags.xrayedSilhouetteOpaque = true;\n }\n }\n if (xrayMaterial.edges) {\n if (xrayMaterial.edgeAlpha < 1.0) {\n renderFlags.xrayedEdgesTransparent = true;\n } else {\n renderFlags.xrayedEdgesOpaque = true;\n }\n }\n } else {\n const normalMaterial = this._material._state;\n if (normalMaterial.alpha < 1.0 || state.colorize[3] < 1.0) {\n renderFlags.colorTransparent = true;\n } else {\n renderFlags.colorOpaque = true;\n }\n if (state.edges) {\n const edgeMaterial = this._edgeMaterial._state;\n if (edgeMaterial.alpha < 1.0) {\n renderFlags.edgesTransparent = true;\n } else {\n renderFlags.edgesOpaque = true;\n }\n }\n if (state.selected) {\n const selectedMaterial = this._selectedMaterial._state;\n if (selectedMaterial.fill) {\n if (selectedMaterial.fillAlpha < 1.0) {\n renderFlags.selectedSilhouetteTransparent = true;\n } else {\n renderFlags.selectedSilhouetteOpaque = true;\n }\n }\n if (selectedMaterial.edges) {\n if (selectedMaterial.edgeAlpha < 1.0) {\n renderFlags.selectedEdgesTransparent = true;\n } else {\n renderFlags.selectedEdgesOpaque = true;\n }\n }\n } else if (state.highlighted) {\n const highlightMaterial = this._highlightMaterial._state;\n if (highlightMaterial.fill) {\n if (highlightMaterial.fillAlpha < 1.0) {\n renderFlags.highlightedSilhouetteTransparent = true;\n } else {\n renderFlags.highlightedSilhouetteOpaque = true;\n }\n }\n if (highlightMaterial.edges) {\n if (highlightMaterial.edgeAlpha < 1.0) {\n renderFlags.highlightedEdgesTransparent = true;\n } else {\n renderFlags.highlightedEdgesOpaque = true;\n }\n }\n }\n }\n }\n\n _getActiveSectionPlanes() {\n\n if (this._state.clippable) {\n\n const sectionPlanes = this.scene._sectionPlanesState.sectionPlanes;\n const numSectionPlanes = sectionPlanes.length;\n\n if (numSectionPlanes > 0) {\n for (let i = 0; i < numSectionPlanes; i++) {\n\n const sectionPlane = sectionPlanes[i];\n const renderFlags = this.renderFlags;\n\n if (!sectionPlane.active) {\n renderFlags.sectionPlanesActivePerLayer[i] = false;\n\n } else {\n\n if (this._state.origin) {\n\n const intersect = math.planeAABB3Intersect(sectionPlane.dir, sectionPlane.dist, this.aabb);\n const outside = (intersect === -1);\n\n if (outside) {\n return false;\n }\n\n const intersecting = (intersect === 0);\n renderFlags.sectionPlanesActivePerLayer[i] = intersecting;\n\n } else {\n renderFlags.sectionPlanesActivePerLayer[i] = true;\n }\n }\n }\n }\n }\n\n return true;\n }\n\n // ---------------------- NORMAL RENDERING -----------------------------------\n\n /** @private */\n drawColorOpaque(frameCtx) {\n if (this._drawRenderer || (this._drawRenderer = DrawRenderer.get(this))) {\n this._drawRenderer.drawMesh(frameCtx, this);\n }\n }\n\n /** @private */\n drawColorTransparent(frameCtx) {\n if (this._drawRenderer || (this._drawRenderer = DrawRenderer.get(this))) {\n this._drawRenderer.drawMesh(frameCtx, this);\n }\n }\n\n // ---------------------- RENDERING SAO POST EFFECT TARGETS --------------\n\n // TODO\n\n // ---------------------- EMPHASIS RENDERING -----------------------------------\n\n /** @private */\n drawSilhouetteXRayed(frameCtx) {\n if (this._emphasisFillRenderer || (this._emphasisFillRenderer = EmphasisFillRenderer.get(this))) {\n this._emphasisFillRenderer.drawMesh(frameCtx, this, 0); // 0 == xray\n }\n }\n\n /** @private */\n drawSilhouetteHighlighted(frameCtx) {\n if (this._emphasisFillRenderer || (this._emphasisFillRenderer = EmphasisFillRenderer.get(this))) {\n this._emphasisFillRenderer.drawMesh(frameCtx, this, 1); // 1 == highlight\n }\n }\n\n /** @private */\n drawSilhouetteSelected(frameCtx) {\n if (this._emphasisFillRenderer || (this._emphasisFillRenderer = EmphasisFillRenderer.get(this))) {\n this._emphasisFillRenderer.drawMesh(frameCtx, this, 2); // 2 == selected\n }\n }\n\n // ---------------------- EDGES RENDERING -----------------------------------\n\n /** @private */\n drawEdgesColorOpaque(frameCtx) {\n if (this._emphasisEdgesRenderer || (this._emphasisEdgesRenderer = EmphasisEdgesRenderer.get(this))) {\n this._emphasisEdgesRenderer.drawMesh(frameCtx, this, 3); // 3 == edges\n }\n }\n\n /** @private */\n drawEdgesColorTransparent(frameCtx) {\n if (this._emphasisEdgesRenderer || (this._emphasisEdgesRenderer = EmphasisEdgesRenderer.get(this))) {\n this._emphasisEdgesRenderer.drawMesh(frameCtx, this, 3); // 3 == edges\n }\n }\n\n /** @private */\n drawEdgesXRayed(frameCtx) {\n if (this._emphasisEdgesRenderer || (this._emphasisEdgesRenderer = EmphasisEdgesRenderer.get(this))) {\n this._emphasisEdgesRenderer.drawMesh(frameCtx, this, 0); // 0 == xray\n }\n }\n\n /** @private */\n drawEdgesHighlighted(frameCtx) {\n if (this._emphasisEdgesRenderer || (this._emphasisEdgesRenderer = EmphasisEdgesRenderer.get(this))) {\n this._emphasisEdgesRenderer.drawMesh(frameCtx, this, 1); // 1 == highlight\n }\n }\n\n /** @private */\n drawEdgesSelected(frameCtx) {\n if (this._emphasisEdgesRenderer || (this._emphasisEdgesRenderer = EmphasisEdgesRenderer.get(this))) {\n this._emphasisEdgesRenderer.drawMesh(frameCtx, this, 2); // 2 == selected\n }\n }\n\n // ---------------------- OCCLUSION CULL RENDERING -----------------------------------\n\n /** @private */\n drawOcclusion(frameCtx) {\n if (this._state.occluder && this._occlusionRenderer || (this._occlusionRenderer = OcclusionRenderer.get(this))) {\n this._occlusionRenderer.drawMesh(frameCtx, this);\n }\n }\n\n // ---------------------- SHADOW BUFFER RENDERING -----------------------------------\n\n /** @private */\n drawShadow(frameCtx) {\n if (this._shadowRenderer || (this._shadowRenderer = ShadowRenderer.get(this))) {\n this._shadowRenderer.drawMesh(frameCtx, this);\n }\n }\n\n // ---------------------- PICKING RENDERING ----------------------------------\n\n /** @private */\n drawPickMesh(frameCtx) {\n if (this._pickMeshRenderer || (this._pickMeshRenderer = PickMeshRenderer.get(this))) {\n this._pickMeshRenderer.drawMesh(frameCtx, this);\n }\n }\n\n /** @private\n */\n canPickTriangle() {\n return this._geometry.isReadableGeometry; // VBOGeometry does not support surface picking because it has no geometry data in browser memory\n }\n\n /** @private */\n drawPickTriangles(frameCtx) {\n if (this._pickTriangleRenderer || (this._pickTriangleRenderer = PickTriangleRenderer.get(this))) {\n this._pickTriangleRenderer.drawMesh(frameCtx, this);\n }\n }\n\n /** @private */\n pickTriangleSurface(pickViewMatrix, pickProjMatrix, pickResult) {\n pickTriangleSurface(this, pickViewMatrix, pickProjMatrix, pickResult);\n }\n\n /** @private */\n drawPickVertices(frameCtx) {\n\n }\n\n /**\n * @private\n * @returns {PerformanceNode}\n */\n delegatePickedEntity() {\n return this;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Component members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Destroys this Mesh.\n */\n destroy() {\n super.destroy(); // xeokit.Object\n this._putDrawRenderers();\n this._putPickRenderers();\n this._putOcclusionRenderer();\n this.scene._renderer.putPickID(this._state.pickID); // TODO: somehow puch this down into xeokit framework?\n if (this._isObject) {\n this.scene._deregisterObject(this);\n if (this._visible) {\n this.scene._objectVisibilityUpdated(this, false, false);\n }\n if (this._xrayed) {\n this.scene._objectXRayedUpdated(this, false, false);\n }\n if (this._selected) {\n this.scene._objectSelectedUpdated(this, false, false);\n }\n if (this._highlighted) {\n this.scene._objectHighlightedUpdated(this, false, false);\n }\n this.scene._objectColorizeUpdated(this, false);\n this.scene._objectOpacityUpdated(this, false);\n if (this.offset.some((v) => v !== 0))\n this.scene._objectOffsetUpdated(this, false);\n }\n if (this._isModel) {\n this.scene._deregisterModel(this);\n }\n this.glRedraw();\n }\n\n}\n\n\nconst pickTriangleSurface = (function () {\n\n // Cached vars to avoid garbage collection\n\n const localRayOrigin = math.vec3();\n const localRayDir = math.vec3();\n const positionA = math.vec3();\n const positionB = math.vec3();\n const positionC = math.vec3();\n const triangleVertices = math.vec3();\n const position = math.vec4();\n const worldPos = math.vec3();\n const viewPos = math.vec3();\n const bary = math.vec3();\n const normalA = math.vec3();\n const normalB = math.vec3();\n const normalC = math.vec3();\n const uva = math.vec3();\n const uvb = math.vec3();\n const uvc = math.vec3();\n const tempVec4a = math.vec4();\n const tempVec4b = math.vec4();\n const tempVec4c = math.vec4();\n const tempVec3 = math.vec3();\n const tempVec3b = math.vec3();\n const tempVec3c = math.vec3();\n const tempVec3d = math.vec3();\n const tempVec3e = math.vec3();\n const tempVec3f = math.vec3();\n const tempVec3g = math.vec3();\n const tempVec3h = math.vec3();\n const tempVec3i = math.vec3();\n const tempVec3j = math.vec3();\n const tempVec3k = math.vec3();\n\n return function (mesh, pickViewMatrix, pickProjMatrix, pickResult) {\n\n var primIndex = pickResult.primIndex;\n\n if (primIndex !== undefined && primIndex !== null && primIndex > -1) {\n\n const geometry = mesh.geometry._state;\n const scene = mesh.scene;\n const camera = scene.camera;\n const canvas = scene.canvas;\n\n if (geometry.primitiveName === \"triangles\") {\n\n // Triangle picked; this only happens when the\n // Mesh has a Geometry that has primitives of type \"triangle\"\n\n pickResult.primitive = \"triangle\";\n\n // Get the World-space positions of the triangle's vertices\n\n const i = primIndex; // Indicates the first triangle index in the indices array\n\n const indices = geometry.indices; // Indices into geometry arrays, not into shared VertexBufs\n const positions = geometry.positions;\n\n let ia3;\n let ib3;\n let ic3;\n\n if (indices) {\n\n var ia = indices[i + 0];\n var ib = indices[i + 1];\n var ic = indices[i + 2];\n\n triangleVertices[0] = ia;\n triangleVertices[1] = ib;\n triangleVertices[2] = ic;\n\n pickResult.indices = triangleVertices;\n\n ia3 = ia * 3;\n ib3 = ib * 3;\n ic3 = ic * 3;\n\n } else {\n\n ia3 = i * 3;\n ib3 = ia3 + 3;\n ic3 = ib3 + 3;\n }\n\n positionA[0] = positions[ia3 + 0];\n positionA[1] = positions[ia3 + 1];\n positionA[2] = positions[ia3 + 2];\n\n positionB[0] = positions[ib3 + 0];\n positionB[1] = positions[ib3 + 1];\n positionB[2] = positions[ib3 + 2];\n\n positionC[0] = positions[ic3 + 0];\n positionC[1] = positions[ic3 + 1];\n positionC[2] = positions[ic3 + 2];\n\n if (geometry.compressGeometry) {\n\n // Decompress vertex positions\n\n const positionsDecodeMatrix = geometry.positionsDecodeMatrix;\n if (positionsDecodeMatrix) {\n geometryCompressionUtils.decompressPosition(positionA, positionsDecodeMatrix, positionA);\n geometryCompressionUtils.decompressPosition(positionB, positionsDecodeMatrix, positionB);\n geometryCompressionUtils.decompressPosition(positionC, positionsDecodeMatrix, positionC);\n }\n }\n\n // Attempt to ray-pick the triangle in local space\n\n if (pickResult.canvasPos) {\n math.canvasPosToLocalRay(canvas.canvas, mesh.origin ? createRTCViewMat(pickViewMatrix, mesh.origin) : pickViewMatrix, pickProjMatrix, mesh.worldMatrix, pickResult.canvasPos, localRayOrigin, localRayDir);\n\n } else if (pickResult.origin && pickResult.direction) {\n math.worldRayToLocalRay(mesh.worldMatrix, pickResult.origin, pickResult.direction, localRayOrigin, localRayDir);\n }\n\n math.normalizeVec3(localRayDir);\n math.rayPlaneIntersect(localRayOrigin, localRayDir, positionA, positionB, positionC, position);\n\n // Get Local-space cartesian coordinates of the ray-triangle intersection\n\n pickResult.localPos = position;\n pickResult.position = position;\n\n // Get interpolated World-space coordinates\n\n // Need to transform homogeneous coords\n\n tempVec4a[0] = position[0];\n tempVec4a[1] = position[1];\n tempVec4a[2] = position[2];\n tempVec4a[3] = 1;\n\n // Get World-space cartesian coordinates of the ray-triangle intersection\n\n math.transformVec4(mesh.worldMatrix, tempVec4a, tempVec4b);\n\n worldPos[0] = tempVec4b[0];\n worldPos[1] = tempVec4b[1];\n worldPos[2] = tempVec4b[2];\n\n if (pickResult.canvasPos && mesh.origin) {\n worldPos[0] += mesh.origin[0];\n worldPos[1] += mesh.origin[1];\n worldPos[2] += mesh.origin[2];\n }\n\n pickResult.worldPos = worldPos;\n\n // Get View-space cartesian coordinates of the ray-triangle intersection\n\n math.transformVec4(camera.matrix, tempVec4b, tempVec4c);\n\n viewPos[0] = tempVec4c[0];\n viewPos[1] = tempVec4c[1];\n viewPos[2] = tempVec4c[2];\n\n pickResult.viewPos = viewPos;\n\n // Get barycentric coordinates of the ray-triangle intersection\n\n math.cartesianToBarycentric(position, positionA, positionB, positionC, bary);\n\n pickResult.bary = bary;\n\n // Get interpolated normal vector\n\n const normals = geometry.normals;\n\n if (normals) {\n\n if (geometry.compressGeometry) {\n\n // Decompress vertex normals\n\n const ia2 = ia * 3;\n const ib2 = ib * 3;\n const ic2 = ic * 3;\n\n geometryCompressionUtils.decompressNormal(normals.subarray(ia2, ia2 + 2), normalA);\n geometryCompressionUtils.decompressNormal(normals.subarray(ib2, ib2 + 2), normalB);\n geometryCompressionUtils.decompressNormal(normals.subarray(ic2, ic2 + 2), normalC);\n\n } else {\n\n normalA[0] = normals[ia3];\n normalA[1] = normals[ia3 + 1];\n normalA[2] = normals[ia3 + 2];\n\n normalB[0] = normals[ib3];\n normalB[1] = normals[ib3 + 1];\n normalB[2] = normals[ib3 + 2];\n\n normalC[0] = normals[ic3];\n normalC[1] = normals[ic3 + 1];\n normalC[2] = normals[ic3 + 2];\n }\n\n const normal = math.addVec3(math.addVec3(\n math.mulVec3Scalar(normalA, bary[0], tempVec3),\n math.mulVec3Scalar(normalB, bary[1], tempVec3b), tempVec3c),\n math.mulVec3Scalar(normalC, bary[2], tempVec3d), tempVec3e);\n\n pickResult.worldNormal = math.normalizeVec3(math.transformVec3(mesh.worldNormalMatrix, normal, tempVec3f));\n }\n\n // Get interpolated UV coordinates\n\n const uvs = geometry.uv;\n\n if (uvs) {\n\n uva[0] = uvs[(ia * 2)];\n uva[1] = uvs[(ia * 2) + 1];\n\n uvb[0] = uvs[(ib * 2)];\n uvb[1] = uvs[(ib * 2) + 1];\n\n uvc[0] = uvs[(ic * 2)];\n uvc[1] = uvs[(ic * 2) + 1];\n\n if (geometry.compressGeometry) {\n\n // Decompress vertex UVs\n\n const uvDecodeMatrix = geometry.uvDecodeMatrix;\n if (uvDecodeMatrix) {\n geometryCompressionUtils.decompressUV(uva, uvDecodeMatrix, uva);\n geometryCompressionUtils.decompressUV(uvb, uvDecodeMatrix, uvb);\n geometryCompressionUtils.decompressUV(uvc, uvDecodeMatrix, uvc);\n }\n }\n\n pickResult.uv = math.addVec3(\n math.addVec3(\n math.mulVec2Scalar(uva, bary[0], tempVec3g),\n math.mulVec2Scalar(uvb, bary[1], tempVec3h), tempVec3i),\n math.mulVec2Scalar(uvc, bary[2], tempVec3j), tempVec3k);\n }\n }\n }\n }\n})();\n\n/**\n * @desc Creates a cylinder-shaped {@link Geometry}.\n *\n * ## Usage\n *\n * Creating a {@link Mesh} with a cylinder-shaped {@link ReadableGeometry} :\n *\n * [[Run this example](/examples/index.html#geometry_builders_buildCylinderGeometry)]\n *\n * ````javascript\n *\n * import {Viewer, Mesh, buildCylinderGeometry, ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 5];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildCylinderGeometry({\n * center: [0,0,0],\n * radiusTop: 2.0,\n * radiusBottom: 2.0,\n * height: 5.0,\n * radialSegments: 20,\n * heightSegments: 1,\n * openEnded: false\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n * ````\n *\n * @function buildCylinderGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for the {@link Geometry}, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.center] 3D point indicating the center position.\n * @param {Number} [cfg.radiusTop=1] Radius of top.\n * @param {Number} [cfg.radiusBottom=1] Radius of bottom.\n * @param {Number} [cfg.height=1] Height.\n * @param {Number} [cfg.radialSegments=60] Number of horizontal segments.\n * @param {Number} [cfg.heightSegments=1] Number of vertical segments.\n * @param {Boolean} [cfg.openEnded=false] Whether or not the cylinder has solid caps on the ends.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildCylinderGeometry(cfg = {}) {\n\n let radiusTop = cfg.radiusTop || 1;\n if (radiusTop < 0) {\n console.error(\"negative radiusTop not allowed - will invert\");\n radiusTop *= -1;\n }\n\n let radiusBottom = cfg.radiusBottom || 1;\n if (radiusBottom < 0) {\n console.error(\"negative radiusBottom not allowed - will invert\");\n radiusBottom *= -1;\n }\n\n let height = cfg.height || 1;\n if (height < 0) {\n console.error(\"negative height not allowed - will invert\");\n height *= -1;\n }\n\n let radialSegments = cfg.radialSegments || 32;\n if (radialSegments < 0) {\n console.error(\"negative radialSegments not allowed - will invert\");\n radialSegments *= -1;\n }\n if (radialSegments < 3) {\n radialSegments = 3;\n }\n\n let heightSegments = cfg.heightSegments || 1;\n if (heightSegments < 0) {\n console.error(\"negative heightSegments not allowed - will invert\");\n heightSegments *= -1;\n }\n if (heightSegments < 1) {\n heightSegments = 1;\n }\n\n const openEnded = !!cfg.openEnded;\n\n let center = cfg.center;\n const centerX = center ? center[0] : 0;\n const centerY = center ? center[1] : 0;\n const centerZ = center ? center[2] : 0;\n\n const heightHalf = height / 2;\n const heightLength = height / heightSegments;\n const radialAngle = (2.0 * Math.PI / radialSegments);\n const radialLength = 1.0 / radialSegments;\n //var nextRadius = this._radiusBottom;\n const radiusChange = (radiusTop - radiusBottom) / heightSegments;\n\n const positions = [];\n const normals = [];\n const uvs = [];\n const indices = [];\n\n let h;\n let i;\n\n let x;\n let z;\n\n let currentRadius;\n let currentHeight;\n\n let first;\n let second;\n\n let startIndex;\n let tu;\n let tv;\n\n // create vertices\n const normalY = (90.0 - (Math.atan(height / (radiusBottom - radiusTop))) * 180 / Math.PI) / 90.0;\n\n for (h = 0; h <= heightSegments; h++) {\n currentRadius = radiusTop - h * radiusChange;\n currentHeight = heightHalf - h * heightLength;\n\n for (i = 0; i <= radialSegments; i++) {\n x = Math.sin(i * radialAngle);\n z = Math.cos(i * radialAngle);\n\n normals.push(currentRadius * x);\n normals.push(normalY); //todo\n normals.push(currentRadius * z);\n\n uvs.push((i * radialLength));\n uvs.push(h * 1 / heightSegments);\n\n positions.push((currentRadius * x) + centerX);\n positions.push((currentHeight) + centerY);\n positions.push((currentRadius * z) + centerZ);\n }\n }\n\n // create faces\n for (h = 0; h < heightSegments; h++) {\n for (i = 0; i <= radialSegments; i++) {\n\n first = h * (radialSegments + 1) + i;\n second = first + radialSegments;\n\n indices.push(first);\n indices.push(second);\n indices.push(second + 1);\n\n indices.push(first);\n indices.push(second + 1);\n indices.push(first + 1);\n }\n }\n\n // create top cap\n if (!openEnded && radiusTop > 0) {\n startIndex = (positions.length / 3);\n\n // top center\n normals.push(0.0);\n normals.push(1.0);\n normals.push(0.0);\n\n uvs.push(0.5);\n uvs.push(0.5);\n\n positions.push(0 + centerX);\n positions.push(heightHalf + centerY);\n positions.push(0 + centerZ);\n\n // top triangle fan\n for (i = 0; i <= radialSegments; i++) {\n x = Math.sin(i * radialAngle);\n z = Math.cos(i * radialAngle);\n tu = (0.5 * Math.sin(i * radialAngle)) + 0.5;\n tv = (0.5 * Math.cos(i * radialAngle)) + 0.5;\n\n normals.push(radiusTop * x);\n normals.push(1.0);\n normals.push(radiusTop * z);\n\n uvs.push(tu);\n uvs.push(tv);\n\n positions.push((radiusTop * x) + centerX);\n positions.push((heightHalf) + centerY);\n positions.push((radiusTop * z) + centerZ);\n }\n\n for (i = 0; i < radialSegments; i++) {\n center = startIndex;\n first = startIndex + 1 + i;\n\n indices.push(first);\n indices.push(first + 1);\n indices.push(center);\n }\n }\n\n // create bottom cap\n if (!openEnded && radiusBottom > 0) {\n\n startIndex = (positions.length / 3);\n\n // top center\n normals.push(0.0);\n normals.push(-1.0);\n normals.push(0.0);\n\n uvs.push(0.5);\n uvs.push(0.5);\n\n positions.push(0 + centerX);\n positions.push(0 - heightHalf + centerY);\n positions.push(0 + centerZ);\n\n // top triangle fan\n for (i = 0; i <= radialSegments; i++) {\n\n x = Math.sin(i * radialAngle);\n z = Math.cos(i * radialAngle);\n\n tu = (0.5 * Math.sin(i * radialAngle)) + 0.5;\n tv = (0.5 * Math.cos(i * radialAngle)) + 0.5;\n\n normals.push(radiusBottom * x);\n normals.push(-1.0);\n normals.push(radiusBottom * z);\n\n uvs.push(tu);\n uvs.push(tv);\n\n positions.push((radiusBottom * x) + centerX);\n positions.push((0 - heightHalf) + centerY);\n positions.push((radiusBottom * z) + centerZ);\n }\n\n for (i = 0; i < radialSegments; i++) {\n\n center = startIndex;\n first = startIndex + 1 + i;\n\n indices.push(center);\n indices.push(first + 1);\n indices.push(first);\n }\n }\n\n return utils.apply(cfg, {\n positions: positions,\n normals: normals,\n uv: uvs,\n indices: indices\n });\n}\n\n/**\n * @desc Creates a sphere-shaped {@link Geometry}.\n *\n * ## Usage\n *\n * Creating a {@link Mesh} with a sphere-shaped {@link ReadableGeometry} :\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#buildSphereGeometry)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildSphereGeometry, ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 5];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * center: [0,0,0],\n * radius: 1.5,\n * heightSegments: 60,\n * widthSegments: 60\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n * ````\n *\n * @function buildSphereGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for the {@link Geometry}, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.center] 3D point indicating the center position.\n * @param {Number} [cfg.radius=1] Radius.\n * @param {Number} [cfg.heightSegments=24] Number of latitudinal bands.\n * @param {Number} [cfg.widthSegments=18] Number of longitudinal bands.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildSphereGeometry(cfg = {}) {\n\n const lod = cfg.lod || 1;\n\n const centerX = cfg.center ? cfg.center[0] : 0;\n const centerY = cfg.center ? cfg.center[1] : 0;\n const centerZ = cfg.center ? cfg.center[2] : 0;\n\n let radius = cfg.radius || 1;\n if (radius < 0) {\n console.error(\"negative radius not allowed - will invert\");\n radius *= -1;\n }\n\n let heightSegments = cfg.heightSegments || 18;\n if (heightSegments < 0) {\n console.error(\"negative heightSegments not allowed - will invert\");\n heightSegments *= -1;\n }\n heightSegments = Math.floor(lod * heightSegments);\n if (heightSegments < 18) {\n heightSegments = 18;\n }\n\n let widthSegments = cfg.widthSegments || 18;\n if (widthSegments < 0) {\n console.error(\"negative widthSegments not allowed - will invert\");\n widthSegments *= -1;\n }\n widthSegments = Math.floor(lod * widthSegments);\n if (widthSegments < 18) {\n widthSegments = 18;\n }\n\n const positions = [];\n const normals = [];\n const uvs = [];\n const indices = [];\n\n let i;\n let j;\n\n let theta;\n let sinTheta;\n let cosTheta;\n\n let phi;\n let sinPhi;\n let cosPhi;\n\n let x;\n let y;\n let z;\n\n let u;\n let v;\n\n let first;\n let second;\n\n for (i = 0; i <= heightSegments; i++) {\n\n theta = i * Math.PI / heightSegments;\n sinTheta = Math.sin(theta);\n cosTheta = Math.cos(theta);\n\n for (j = 0; j <= widthSegments; j++) {\n\n phi = j * 2 * Math.PI / widthSegments;\n sinPhi = Math.sin(phi);\n cosPhi = Math.cos(phi);\n\n x = cosPhi * sinTheta;\n y = cosTheta;\n z = sinPhi * sinTheta;\n u = 1.0 - j / widthSegments;\n v = i / heightSegments;\n\n normals.push(x);\n normals.push(y);\n normals.push(z);\n\n uvs.push(u);\n uvs.push(v);\n\n positions.push(centerX + radius * x);\n positions.push(centerY + radius * y);\n positions.push(centerZ + radius * z);\n }\n }\n\n for (i = 0; i < heightSegments; i++) {\n for (j = 0; j < widthSegments; j++) {\n\n first = (i * (widthSegments + 1)) + j;\n second = first + widthSegments + 1;\n\n indices.push(first + 1);\n indices.push(second + 1);\n indices.push(second);\n indices.push(first + 1);\n indices.push(second);\n indices.push(first);\n }\n }\n\n return utils.apply(cfg, {\n positions: positions,\n normals: normals,\n uv: uvs,\n indices: indices\n });\n}\n\nconst letters = {\n ' ': {width: 16, points: []},\n '!': {\n width: 10, points: [\n [5, 21],\n [5, 7],\n [-1, -1],\n [5, 2],\n [4, 1],\n [5, 0],\n [6, 1],\n [5, 2]\n ]\n },\n '\"': {\n width: 16, points: [\n [4, 21],\n [4, 14],\n [-1, -1],\n [12, 21],\n [12, 14]\n ]\n },\n '#': {\n width: 21, points: [\n [11, 25],\n [4, -7],\n [-1, -1],\n [17, 25],\n [10, -7],\n [-1, -1],\n [4, 12],\n [18, 12],\n [-1, -1],\n [3, 6],\n [17, 6]\n ]\n },\n '$': {\n width: 20, points: [\n [8, 25],\n [8, -4],\n [-1, -1],\n [12, 25],\n [12, -4],\n [-1, -1],\n [17, 18],\n [15, 20],\n [12, 21],\n [8, 21],\n [5, 20],\n [3, 18],\n [3, 16],\n [4, 14],\n [5, 13],\n [7, 12],\n [13, 10],\n [15, 9],\n [16, 8],\n [17, 6],\n [17, 3],\n [15, 1],\n [12, 0],\n [8, 0],\n [5, 1],\n [3, 3]\n ]\n },\n '%': {\n width: 24, points: [\n [21, 21],\n [3, 0],\n [-1, -1],\n [8, 21],\n [10, 19],\n [10, 17],\n [9, 15],\n [7, 14],\n [5, 14],\n [3, 16],\n [3, 18],\n [4, 20],\n [6, 21],\n [8, 21],\n [10, 20],\n [13, 19],\n [16, 19],\n [19, 20],\n [21, 21],\n [-1, -1],\n [17, 7],\n [15, 6],\n [14, 4],\n [14, 2],\n [16, 0],\n [18, 0],\n [20, 1],\n [21, 3],\n [21, 5],\n [19, 7],\n [17, 7]\n ]\n },\n '&': {\n width: 26, points: [\n [23, 12],\n [23, 13],\n [22, 14],\n [21, 14],\n [20, 13],\n [19, 11],\n [17, 6],\n [15, 3],\n [13, 1],\n [11, 0],\n [7, 0],\n [5, 1],\n [4, 2],\n [3, 4],\n [3, 6],\n [4, 8],\n [5, 9],\n [12, 13],\n [13, 14],\n [14, 16],\n [14, 18],\n [13, 20],\n [11, 21],\n [9, 20],\n [8, 18],\n [8, 16],\n [9, 13],\n [11, 10],\n [16, 3],\n [18, 1],\n [20, 0],\n [22, 0],\n [23, 1],\n [23, 2]\n ]\n },\n '\\'': {\n width: 10, points: [\n [5, 19],\n [4, 20],\n [5, 21],\n [6, 20],\n [6, 18],\n [5, 16],\n [4, 15]\n ]\n },\n '(': {\n width: 14, points: [\n [11, 25],\n [9, 23],\n [7, 20],\n [5, 16],\n [4, 11],\n [4, 7],\n [5, 2],\n [7, -2],\n [9, -5],\n [11, -7]\n ]\n },\n ')': {\n width: 14, points: [\n [3, 25],\n [5, 23],\n [7, 20],\n [9, 16],\n [10, 11],\n [10, 7],\n [9, 2],\n [7, -2],\n [5, -5],\n [3, -7]\n ]\n },\n '*': {\n width: 16, points: [\n [8, 21],\n [8, 9],\n [-1, -1],\n [3, 18],\n [13, 12],\n [-1, -1],\n [13, 18],\n [3, 12]\n ]\n },\n '+': {\n width: 26, points: [\n [13, 18],\n [13, 0],\n [-1, -1],\n [4, 9],\n [22, 9]\n ]\n },\n ',': {\n width: 10, points: [\n [6, 1],\n [5, 0],\n [4, 1],\n [5, 2],\n [6, 1],\n [6, -1],\n [5, -3],\n [4, -4]\n ]\n },\n '-': {\n width: 26, points: [\n [4, 9],\n [22, 9]\n ]\n },\n '.': {\n width: 10, points: [\n [5, 2],\n [4, 1],\n [5, 0],\n [6, 1],\n [5, 2]\n ]\n },\n '/': {\n width: 22, points: [\n [20, 25],\n [2, -7]\n ]\n },\n '0': {\n width: 20, points: [\n [9, 21],\n [6, 20],\n [4, 17],\n [3, 12],\n [3, 9],\n [4, 4],\n [6, 1],\n [9, 0],\n [11, 0],\n [14, 1],\n [16, 4],\n [17, 9],\n [17, 12],\n [16, 17],\n [14, 20],\n [11, 21],\n [9, 21]\n ]\n },\n '1': {\n width: 20, points: [\n [6, 17],\n [8, 18],\n [11, 21],\n [11, 0]\n ]\n },\n '2': {\n width: 20, points: [\n [4, 16],\n [4, 17],\n [5, 19],\n [6, 20],\n [8, 21],\n [12, 21],\n [14, 20],\n [15, 19],\n [16, 17],\n [16, 15],\n [15, 13],\n [13, 10],\n [3, 0],\n [17, 0]\n ]\n },\n '3': {\n width: 20, points: [\n [5, 21],\n [16, 21],\n [10, 13],\n [13, 13],\n [15, 12],\n [16, 11],\n [17, 8],\n [17, 6],\n [16, 3],\n [14, 1],\n [11, 0],\n [8, 0],\n [5, 1],\n [4, 2],\n [3, 4]\n ]\n },\n '4': {\n width: 20, points: [\n [13, 21],\n [3, 7],\n [18, 7],\n [-1, -1],\n [13, 21],\n [13, 0]\n ]\n },\n '5': {\n width: 20, points: [\n [15, 21],\n [5, 21],\n [4, 12],\n [5, 13],\n [8, 14],\n [11, 14],\n [14, 13],\n [16, 11],\n [17, 8],\n [17, 6],\n [16, 3],\n [14, 1],\n [11, 0],\n [8, 0],\n [5, 1],\n [4, 2],\n [3, 4]\n ]\n },\n '6': {\n width: 20, points: [\n [16, 18],\n [15, 20],\n [12, 21],\n [10, 21],\n [7, 20],\n [5, 17],\n [4, 12],\n [4, 7],\n [5, 3],\n [7, 1],\n [10, 0],\n [11, 0],\n [14, 1],\n [16, 3],\n [17, 6],\n [17, 7],\n [16, 10],\n [14, 12],\n [11, 13],\n [10, 13],\n [7, 12],\n [5, 10],\n [4, 7]\n ]\n },\n '7': {\n width: 20, points: [\n [17, 21],\n [7, 0],\n [-1, -1],\n [3, 21],\n [17, 21]\n ]\n },\n '8': {\n width: 20, points: [\n [8, 21],\n [5, 20],\n [4, 18],\n [4, 16],\n [5, 14],\n [7, 13],\n [11, 12],\n [14, 11],\n [16, 9],\n [17, 7],\n [17, 4],\n [16, 2],\n [15, 1],\n [12, 0],\n [8, 0],\n [5, 1],\n [4, 2],\n [3, 4],\n [3, 7],\n [4, 9],\n [6, 11],\n [9, 12],\n [13, 13],\n [15, 14],\n [16, 16],\n [16, 18],\n [15, 20],\n [12, 21],\n [8, 21]\n ]\n },\n '9': {\n width: 20, points: [\n [16, 14],\n [15, 11],\n [13, 9],\n [10, 8],\n [9, 8],\n [6, 9],\n [4, 11],\n [3, 14],\n [3, 15],\n [4, 18],\n [6, 20],\n [9, 21],\n [10, 21],\n [13, 20],\n [15, 18],\n [16, 14],\n [16, 9],\n [15, 4],\n [13, 1],\n [10, 0],\n [8, 0],\n [5, 1],\n [4, 3]\n ]\n },\n ':': {\n width: 10, points: [\n [5, 14],\n [4, 13],\n [5, 12],\n [6, 13],\n [5, 14],\n [-1, -1],\n [5, 2],\n [4, 1],\n [5, 0],\n [6, 1],\n [5, 2]\n ]\n },\n ';': {\n width: 10, points: [\n [5, 14],\n [4, 13],\n [5, 12],\n [6, 13],\n [5, 14],\n [-1, -1],\n [6, 1],\n [5, 0],\n [4, 1],\n [5, 2],\n [6, 1],\n [6, -1],\n [5, -3],\n [4, -4]\n ]\n },\n '<': {\n width: 24, points: [\n [20, 18],\n [4, 9],\n [20, 0]\n ]\n },\n '=': {\n width: 26, points: [\n [4, 12],\n [22, 12],\n [-1, -1],\n [4, 6],\n [22, 6]\n ]\n },\n '>': {\n width: 24, points: [\n [4, 18],\n [20, 9],\n [4, 0]\n ]\n },\n '?': {\n width: 18, points: [\n [3, 16],\n [3, 17],\n [4, 19],\n [5, 20],\n [7, 21],\n [11, 21],\n [13, 20],\n [14, 19],\n [15, 17],\n [15, 15],\n [14, 13],\n [13, 12],\n [9, 10],\n [9, 7],\n [-1, -1],\n [9, 2],\n [8, 1],\n [9, 0],\n [10, 1],\n [9, 2]\n ]\n },\n '@': {\n width: 27, points: [\n [18, 13],\n [17, 15],\n [15, 16],\n [12, 16],\n [10, 15],\n [9, 14],\n [8, 11],\n [8, 8],\n [9, 6],\n [11, 5],\n [14, 5],\n [16, 6],\n [17, 8],\n [-1, -1],\n [12, 16],\n [10, 14],\n [9, 11],\n [9, 8],\n [10, 6],\n [11, 5],\n [-1, -1],\n [18, 16],\n [17, 8],\n [17, 6],\n [19, 5],\n [21, 5],\n [23, 7],\n [24, 10],\n [24, 12],\n [23, 15],\n [22, 17],\n [20, 19],\n [18, 20],\n [15, 21],\n [12, 21],\n [9, 20],\n [7, 19],\n [5, 17],\n [4, 15],\n [3, 12],\n [3, 9],\n [4, 6],\n [5, 4],\n [7, 2],\n [9, 1],\n [12, 0],\n [15, 0],\n [18, 1],\n [20, 2],\n [21, 3],\n [-1, -1],\n [19, 16],\n [18, 8],\n [18, 6],\n [19, 5]\n ]\n },\n 'A': {\n width: 18, points: [\n [9, 21],\n [1, 0],\n [-1, -1],\n [9, 21],\n [17, 0],\n [-1, -1],\n [4, 7],\n [14, 7]\n ]\n },\n 'B': {\n width: 21, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [13, 21],\n [16, 20],\n [17, 19],\n [18, 17],\n [18, 15],\n [17, 13],\n [16, 12],\n [13, 11],\n [-1, -1],\n [4, 11],\n [13, 11],\n [16, 10],\n [17, 9],\n [18, 7],\n [18, 4],\n [17, 2],\n [16, 1],\n [13, 0],\n [4, 0]\n ]\n },\n 'C': {\n width: 21, points: [\n [18, 16],\n [17, 18],\n [15, 20],\n [13, 21],\n [9, 21],\n [7, 20],\n [5, 18],\n [4, 16],\n [3, 13],\n [3, 8],\n [4, 5],\n [5, 3],\n [7, 1],\n [9, 0],\n [13, 0],\n [15, 1],\n [17, 3],\n [18, 5]\n ]\n },\n 'D': {\n width: 21, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [11, 21],\n [14, 20],\n [16, 18],\n [17, 16],\n [18, 13],\n [18, 8],\n [17, 5],\n [16, 3],\n [14, 1],\n [11, 0],\n [4, 0]\n ]\n },\n 'E': {\n width: 19, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [17, 21],\n [-1, -1],\n [4, 11],\n [12, 11],\n [-1, -1],\n [4, 0],\n [17, 0]\n ]\n },\n 'F': {\n width: 18, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [17, 21],\n [-1, -1],\n [4, 11],\n [12, 11]\n ]\n },\n 'G': {\n width: 21, points: [\n [18, 16],\n [17, 18],\n [15, 20],\n [13, 21],\n [9, 21],\n [7, 20],\n [5, 18],\n [4, 16],\n [3, 13],\n [3, 8],\n [4, 5],\n [5, 3],\n [7, 1],\n [9, 0],\n [13, 0],\n [15, 1],\n [17, 3],\n [18, 5],\n [18, 8],\n [-1, -1],\n [13, 8],\n [18, 8]\n ]\n },\n 'H': {\n width: 22, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [18, 21],\n [18, 0],\n [-1, -1],\n [4, 11],\n [18, 11]\n ]\n },\n 'I': {\n width: 8, points: [\n [4, 21],\n [4, 0]\n ]\n },\n 'J': {\n width: 16, points: [\n [12, 21],\n [12, 5],\n [11, 2],\n [10, 1],\n [8, 0],\n [6, 0],\n [4, 1],\n [3, 2],\n [2, 5],\n [2, 7]\n ]\n },\n 'K': {\n width: 21, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [18, 21],\n [4, 7],\n [-1, -1],\n [9, 12],\n [18, 0]\n ]\n },\n 'L': {\n width: 17, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 0],\n [16, 0]\n ]\n },\n 'M': {\n width: 24, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [12, 0],\n [-1, -1],\n [20, 21],\n [12, 0],\n [-1, -1],\n [20, 21],\n [20, 0]\n ]\n },\n 'N': {\n width: 22, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [18, 0],\n [-1, -1],\n [18, 21],\n [18, 0]\n ]\n },\n 'O': {\n width: 22, points: [\n [9, 21],\n [7, 20],\n [5, 18],\n [4, 16],\n [3, 13],\n [3, 8],\n [4, 5],\n [5, 3],\n [7, 1],\n [9, 0],\n [13, 0],\n [15, 1],\n [17, 3],\n [18, 5],\n [19, 8],\n [19, 13],\n [18, 16],\n [17, 18],\n [15, 20],\n [13, 21],\n [9, 21]\n ]\n },\n 'P': {\n width: 21, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [13, 21],\n [16, 20],\n [17, 19],\n [18, 17],\n [18, 14],\n [17, 12],\n [16, 11],\n [13, 10],\n [4, 10]\n ]\n },\n 'Q': {\n width: 22, points: [\n [9, 21],\n [7, 20],\n [5, 18],\n [4, 16],\n [3, 13],\n [3, 8],\n [4, 5],\n [5, 3],\n [7, 1],\n [9, 0],\n [13, 0],\n [15, 1],\n [17, 3],\n [18, 5],\n [19, 8],\n [19, 13],\n [18, 16],\n [17, 18],\n [15, 20],\n [13, 21],\n [9, 21],\n [-1, -1],\n [12, 4],\n [18, -2]\n ]\n },\n 'R': {\n width: 21, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 21],\n [13, 21],\n [16, 20],\n [17, 19],\n [18, 17],\n [18, 15],\n [17, 13],\n [16, 12],\n [13, 11],\n [4, 11],\n [-1, -1],\n [11, 11],\n [18, 0]\n ]\n },\n 'S': {\n width: 20, points: [\n [17, 18],\n [15, 20],\n [12, 21],\n [8, 21],\n [5, 20],\n [3, 18],\n [3, 16],\n [4, 14],\n [5, 13],\n [7, 12],\n [13, 10],\n [15, 9],\n [16, 8],\n [17, 6],\n [17, 3],\n [15, 1],\n [12, 0],\n [8, 0],\n [5, 1],\n [3, 3]\n ]\n },\n 'T': {\n width: 16, points: [\n [8, 21],\n [8, 0],\n [-1, -1],\n [1, 21],\n [15, 21]\n ]\n },\n 'U': {\n width: 22, points: [\n [4, 21],\n [4, 6],\n [5, 3],\n [7, 1],\n [10, 0],\n [12, 0],\n [15, 1],\n [17, 3],\n [18, 6],\n [18, 21]\n ]\n },\n 'V': {\n width: 18, points: [\n [1, 21],\n [9, 0],\n [-1, -1],\n [17, 21],\n [9, 0]\n ]\n },\n 'W': {\n width: 24, points: [\n [2, 21],\n [7, 0],\n [-1, -1],\n [12, 21],\n [7, 0],\n [-1, -1],\n [12, 21],\n [17, 0],\n [-1, -1],\n [22, 21],\n [17, 0]\n ]\n },\n 'X': {\n width: 20, points: [\n [3, 21],\n [17, 0],\n [-1, -1],\n [17, 21],\n [3, 0]\n ]\n },\n 'Y': {\n width: 18, points: [\n [1, 21],\n [9, 11],\n [9, 0],\n [-1, -1],\n [17, 21],\n [9, 11]\n ]\n },\n 'Z': {\n width: 20, points: [\n [17, 21],\n [3, 0],\n [-1, -1],\n [3, 21],\n [17, 21],\n [-1, -1],\n [3, 0],\n [17, 0]\n ]\n },\n '[': {\n width: 14, points: [\n [4, 25],\n [4, -7],\n [-1, -1],\n [5, 25],\n [5, -7],\n [-1, -1],\n [4, 25],\n [11, 25],\n [-1, -1],\n [4, -7],\n [11, -7]\n ]\n },\n '\\\\': {\n width: 14, points: [\n [0, 21],\n [14, -3]\n ]\n },\n ']': {\n width: 14, points: [\n [9, 25],\n [9, -7],\n [-1, -1],\n [10, 25],\n [10, -7],\n [-1, -1],\n [3, 25],\n [10, 25],\n [-1, -1],\n [3, -7],\n [10, -7]\n ]\n },\n '^': {\n width: 16, points: [\n [6, 15],\n [8, 18],\n [10, 15],\n [-1, -1],\n [3, 12],\n [8, 17],\n [13, 12],\n [-1, -1],\n [8, 17],\n [8, 0]\n ]\n },\n '_': {\n width: 16, points: [\n [0, -2],\n [16, -2]\n ]\n },\n '`': {\n width: 10, points: [\n [6, 21],\n [5, 20],\n [4, 18],\n [4, 16],\n [5, 15],\n [6, 16],\n [5, 17]\n ]\n },\n 'a': {\n width: 19, points: [\n [15, 14],\n [15, 0],\n [-1, -1],\n [15, 11],\n [13, 13],\n [11, 14],\n [8, 14],\n [6, 13],\n [4, 11],\n [3, 8],\n [3, 6],\n [4, 3],\n [6, 1],\n [8, 0],\n [11, 0],\n [13, 1],\n [15, 3]\n ]\n },\n 'b': {\n width: 19, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 11],\n [6, 13],\n [8, 14],\n [11, 14],\n [13, 13],\n [15, 11],\n [16, 8],\n [16, 6],\n [15, 3],\n [13, 1],\n [11, 0],\n [8, 0],\n [6, 1],\n [4, 3]\n ]\n },\n 'c': {\n width: 18, points: [\n [15, 11],\n [13, 13],\n [11, 14],\n [8, 14],\n [6, 13],\n [4, 11],\n [3, 8],\n [3, 6],\n [4, 3],\n [6, 1],\n [8, 0],\n [11, 0],\n [13, 1],\n [15, 3]\n ]\n },\n 'd': {\n width: 19, points: [\n [15, 21],\n [15, 0],\n [-1, -1],\n [15, 11],\n [13, 13],\n [11, 14],\n [8, 14],\n [6, 13],\n [4, 11],\n [3, 8],\n [3, 6],\n [4, 3],\n [6, 1],\n [8, 0],\n [11, 0],\n [13, 1],\n [15, 3]\n ]\n },\n 'e': {\n width: 18, points: [\n [3, 8],\n [15, 8],\n [15, 10],\n [14, 12],\n [13, 13],\n [11, 14],\n [8, 14],\n [6, 13],\n [4, 11],\n [3, 8],\n [3, 6],\n [4, 3],\n [6, 1],\n [8, 0],\n [11, 0],\n [13, 1],\n [15, 3]\n ]\n },\n 'f': {\n width: 12, points: [\n [10, 21],\n [8, 21],\n [6, 20],\n [5, 17],\n [5, 0],\n [-1, -1],\n [2, 14],\n [9, 14]\n ]\n },\n 'g': {\n width: 19, points: [\n [15, 14],\n [15, -2],\n [14, -5],\n [13, -6],\n [11, -7],\n [8, -7],\n [6, -6],\n [-1, -1],\n [15, 11],\n [13, 13],\n [11, 14],\n [8, 14],\n [6, 13],\n [4, 11],\n [3, 8],\n [3, 6],\n [4, 3],\n [6, 1],\n [8, 0],\n [11, 0],\n [13, 1],\n [15, 3]\n ]\n },\n 'h': {\n width: 19, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [4, 10],\n [7, 13],\n [9, 14],\n [12, 14],\n [14, 13],\n [15, 10],\n [15, 0]\n ]\n },\n 'i': {\n width: 8, points: [\n [3, 21],\n [4, 20],\n [5, 21],\n [4, 22],\n [3, 21],\n [-1, -1],\n [4, 14],\n [4, 0]\n ]\n },\n 'j': {\n width: 10, points: [\n [5, 21],\n [6, 20],\n [7, 21],\n [6, 22],\n [5, 21],\n [-1, -1],\n [6, 14],\n [6, -3],\n [5, -6],\n [3, -7],\n [1, -7]\n ]\n },\n 'k': {\n width: 17, points: [\n [4, 21],\n [4, 0],\n [-1, -1],\n [14, 14],\n [4, 4],\n [-1, -1],\n [8, 8],\n [15, 0]\n ]\n },\n 'l': {\n width: 8, points: [\n [4, 21],\n [4, 0]\n ]\n },\n 'm': {\n width: 30, points: [\n [4, 14],\n [4, 0],\n [-1, -1],\n [4, 10],\n [7, 13],\n [9, 14],\n [12, 14],\n [14, 13],\n [15, 10],\n [15, 0],\n [-1, -1],\n [15, 10],\n [18, 13],\n [20, 14],\n [23, 14],\n [25, 13],\n [26, 10],\n [26, 0]\n ]\n },\n 'n': {\n width: 19, points: [\n [4, 14],\n [4, 0],\n [-1, -1],\n [4, 10],\n [7, 13],\n [9, 14],\n [12, 14],\n [14, 13],\n [15, 10],\n [15, 0]\n ]\n },\n 'o': {\n width: 19, points: [\n [8, 14],\n [6, 13],\n [4, 11],\n [3, 8],\n [3, 6],\n [4, 3],\n [6, 1],\n [8, 0],\n [11, 0],\n [13, 1],\n [15, 3],\n [16, 6],\n [16, 8],\n [15, 11],\n [13, 13],\n [11, 14],\n [8, 14]\n ]\n },\n 'p': {\n width: 19, points: [\n [4, 14],\n [4, -7],\n [-1, -1],\n [4, 11],\n [6, 13],\n [8, 14],\n [11, 14],\n [13, 13],\n [15, 11],\n [16, 8],\n [16, 6],\n [15, 3],\n [13, 1],\n [11, 0],\n [8, 0],\n [6, 1],\n [4, 3]\n ]\n },\n 'q': {\n width: 19, points: [\n [15, 14],\n [15, -7],\n [-1, -1],\n [15, 11],\n [13, 13],\n [11, 14],\n [8, 14],\n [6, 13],\n [4, 11],\n [3, 8],\n [3, 6],\n [4, 3],\n [6, 1],\n [8, 0],\n [11, 0],\n [13, 1],\n [15, 3]\n ]\n },\n 'r': {\n width: 13, points: [\n [4, 14],\n [4, 0],\n [-1, -1],\n [4, 8],\n [5, 11],\n [7, 13],\n [9, 14],\n [12, 14]\n ]\n },\n 's': {\n width: 17, points: [\n [14, 11],\n [13, 13],\n [10, 14],\n [7, 14],\n [4, 13],\n [3, 11],\n [4, 9],\n [6, 8],\n [11, 7],\n [13, 6],\n [14, 4],\n [14, 3],\n [13, 1],\n [10, 0],\n [7, 0],\n [4, 1],\n [3, 3]\n ]\n },\n 't': {\n width: 12, points: [\n [5, 21],\n [5, 4],\n [6, 1],\n [8, 0],\n [10, 0],\n [-1, -1],\n [2, 14],\n [9, 14]\n ]\n },\n 'u': {\n width: 19, points: [\n [4, 14],\n [4, 4],\n [5, 1],\n [7, 0],\n [10, 0],\n [12, 1],\n [15, 4],\n [-1, -1],\n [15, 14],\n [15, 0]\n ]\n },\n 'v': {\n width: 16, points: [\n [2, 14],\n [8, 0],\n [-1, -1],\n [14, 14],\n [8, 0]\n ]\n },\n 'w': {\n width: 22, points: [\n [3, 14],\n [7, 0],\n [-1, -1],\n [11, 14],\n [7, 0],\n [-1, -1],\n [11, 14],\n [15, 0],\n [-1, -1],\n [19, 14],\n [15, 0]\n ]\n },\n 'x': {\n width: 17, points: [\n [3, 14],\n [14, 0],\n [-1, -1],\n [14, 14],\n [3, 0]\n ]\n },\n 'y': {\n width: 16, points: [\n [2, 14],\n [8, 0],\n [-1, -1],\n [14, 14],\n [8, 0],\n [6, -4],\n [4, -6],\n [2, -7],\n [1, -7]\n ]\n },\n 'z': {\n width: 17, points: [\n [14, 14],\n [3, 0],\n [-1, -1],\n [3, 14],\n [14, 14],\n [-1, -1],\n [3, 0],\n [14, 0]\n ]\n },\n '{': {\n width: 14, points: [\n [9, 25],\n [7, 24],\n [6, 23],\n [5, 21],\n [5, 19],\n [6, 17],\n [7, 16],\n [8, 14],\n [8, 12],\n [6, 10],\n [-1, -1],\n [7, 24],\n [6, 22],\n [6, 20],\n [7, 18],\n [8, 17],\n [9, 15],\n [9, 13],\n [8, 11],\n [4, 9],\n [8, 7],\n [9, 5],\n [9, 3],\n [8, 1],\n [7, 0],\n [6, -2],\n [6, -4],\n [7, -6],\n [-1, -1],\n [6, 8],\n [8, 6],\n [8, 4],\n [7, 2],\n [6, 1],\n [5, -1],\n [5, -3],\n [6, -5],\n [7, -6],\n [9, -7]\n ]\n },\n '|': {\n width: 8, points: [\n [4, 25],\n [4, -7]\n ]\n },\n '}': {\n width: 14, points: [\n [5, 25],\n [7, 24],\n [8, 23],\n [9, 21],\n [9, 19],\n [8, 17],\n [7, 16],\n [6, 14],\n [6, 12],\n [8, 10],\n [-1, -1],\n [7, 24],\n [8, 22],\n [8, 20],\n [7, 18],\n [6, 17],\n [5, 15],\n [5, 13],\n [6, 11],\n [10, 9],\n [6, 7],\n [5, 5],\n [5, 3],\n [6, 1],\n [7, 0],\n [8, -2],\n [8, -4],\n [7, -6],\n [-1, -1],\n [8, 8],\n [6, 6],\n [6, 4],\n [7, 2],\n [8, 1],\n [9, -1],\n [9, -3],\n [8, -5],\n [7, -6],\n [5, -7]\n ]\n },\n '~': {\n width: 24, points: [\n [3, 6],\n [3, 8],\n [4, 11],\n [6, 12],\n [8, 12],\n [10, 11],\n [14, 8],\n [16, 7],\n [18, 7],\n [20, 8],\n [21, 10],\n [-1, -1],\n [3, 8],\n [4, 10],\n [6, 11],\n [8, 11],\n [10, 10],\n [14, 7],\n [16, 6],\n [18, 6],\n [20, 7],\n [21, 10],\n [21, 12]\n ]\n }\n};\n\n/**\n * @desc Creates wireframe vector text {@link Geometry}.\n *\n * ## Usage\n *\n * Creating a {@link Mesh} with vector text {@link ReadableGeometry} :\n *\n * [[Run this example](/examples/index.html#geometry_builders_buildVectorTextGeometry)]\n *\n * ````javascript\n *\n * import {Viewer, Mesh, buildVectorTextGeometry, ReadableGeometry, PhongMaterial} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 100];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildVectorTextGeometry({\n * origin: [0,0,0],\n * text: \"On the other side of the screen, it all looked so easy\"\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n * ````\n *\n * @function buildVectorTextGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.center] 3D point indicating the center position.\n * @param {Number[]} [cfg.origin] 3D point indicating the top left corner.\n * @param {Number} [cfg.size=1] Size of each character.\n * @param {String} [cfg.text=\"\"] The text.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildVectorTextGeometry(cfg = {}) {\n\n var origin = cfg.origin || [0, 0, 0];\n var xOrigin = origin[0];\n var yOrigin = origin[1];\n var zOrigin = origin[2];\n var size = cfg.size || 1;\n\n var positions = [];\n var indices = [];\n var text = cfg.text;\n if (utils.isNumeric(text)) {\n text = \"\" + text;\n }\n var lines = (text || \"\").split(\"\\n\");\n var countVerts = 0;\n var y = 0;\n var x;\n var str;\n var len;\n var c;\n var mag = 1.0 / 25.0;\n var penUp;\n var p1;\n var p2;\n var pointsLen;\n var a;\n\n for (var iLine = 0; iLine < lines.length; iLine++) {\n\n x = 0;\n str = lines[iLine];\n len = str.length;\n\n for (var i = 0; i < len; i++) {\n\n c = letters[str.charAt(i)];\n\n if (!c) {\n continue;\n }\n\n penUp = 1;\n p1 = -1;\n p2 = -1;\n\n pointsLen = c.points.length;\n\n for (var j = 0; j < pointsLen; j++) {\n a = c.points[j];\n\n if (a[0] === -1 && a[1] === -1) {\n penUp = 1;\n continue;\n }\n\n positions.push((x + (a[0] * size) * mag) + xOrigin);\n positions.push((y + (a[1] * size) * mag) + yOrigin);\n positions.push(0 + zOrigin);\n\n if (p1 === -1) {\n p1 = countVerts;\n } else if (p2 === -1) {\n p2 = countVerts;\n } else {\n p1 = p2;\n p2 = countVerts;\n }\n countVerts++;\n\n if (penUp) {\n penUp = false;\n\n } else {\n indices.push(p1);\n indices.push(p2);\n }\n }\n x += c.width * mag * size;\n\n }\n y -= 35 * mag * size;\n }\n\n return utils.apply(cfg, {\n primitive: \"lines\",\n positions: positions,\n indices: indices\n });\n}\n\n/**\n * {@link Viewer} plugin that shows the axii of the World-space coordinate system.\n *\n * ## Usage\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#gizmos_AxisGizmoPlugin)]\n *\n * ````JavaScript````\n * import {Viewer, XKTLoaderPlugin, AxisGizmoPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [-2.56, 8.38, 8.27];\n * viewer.camera.look = [13.44, 3.31, -14.83];\n * viewer.camera.up = [0.10, 0.98, -0.14];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * new AxisGizmoPlugin(viewer, {\n * canvasId: \"myAxisGizmoCanvas\"\n * });\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"../assets/models/xkt/Schependomlaan.xkt\",\n * edges: true\n * });\n * ````\n */\nclass AxisGizmoPlugin extends Plugin {\n\n /**\n * @constructor\n * @param {Viewer} viewer The Viewer.\n * @param {Object} cfg Plugin configuration.\n * @param {String} [cfg.id=\"AxisGizmo\"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.\n * @param {String} [cfg.canvasId] ID of an existing HTML canvas to display the AxisGizmo - either this or canvasElement is mandatory. When both values are given, the element reference is always preferred to the ID.\n * @param {HTMLCanvasElement} [cfg.canvasElement] Reference of an existing HTML canvas to display the AxisGizmo - either this or canvasId is mandatory. When both values are given, the element reference is always preferred to the ID.\n */\n constructor(viewer, cfg) {\n\n cfg = cfg || {};\n\n super(\"AxisGizmo\", viewer, cfg);\n\n const camera = viewer.scene.camera;\n\n if (!cfg.canvasId && !cfg.canvasElement) {\n this.error(\"Config expected: either 'canvasId' or 'canvasElement'\");\n }\n\n try {\n this._axisGizmoScene = new Scene(viewer, {\n canvasId: cfg.canvasId,\n canvasElement: cfg.canvasElement,\n transparent: true\n });\n } catch (error) {\n this.error(error);\n return;\n }\n\n const axisGizmoScene = this._axisGizmoScene;\n\n axisGizmoScene.clearLights();\n\n new AmbientLight(axisGizmoScene, {color: [0.45, 0.45, 0.5], intensity: 0.9});\n new DirLight(axisGizmoScene, {dir: [-0.5, 0.5, -0.6], color: [0.8, 0.8, 0.7], intensity: 1.0, space: \"view\"});\n new DirLight(axisGizmoScene, {dir: [0.5, -0.5, -0.6], color: [0.8, 0.8, 0.8], intensity: 1.0, space: \"view\"});\n\n // Rotate helper in synch with target camera\n\n const helperCamera = axisGizmoScene.camera;\n\n camera.on(\"matrix\", function () {\n\n const eye = camera.eye;\n const look = camera.look;\n const up = camera.up;\n\n const eyeLook = math.mulVec3Scalar(math.normalizeVec3(math.subVec3(eye, look, [])), 22);\n\n helperCamera.look = [0, 0, 0];\n helperCamera.eye = eyeLook;\n helperCamera.up = up;\n });\n\n // ----------------- Components that are shared among more than one mesh ---------------\n\n const arrowHead = new ReadableGeometry(axisGizmoScene, buildCylinderGeometry({\n radiusTop: 0.01,\n radiusBottom: 0.6,\n height: 1.7,\n radialSegments: 20,\n heightSegments: 1,\n openEnded: false\n }));\n\n const arrowShaft = new ReadableGeometry(axisGizmoScene, buildCylinderGeometry({\n radiusTop: 0.2,\n radiusBottom: 0.2,\n height: 4.5,\n radialSegments: 20,\n heightSegments: 1,\n openEnded: false\n }));\n\n const xAxisMaterial = new PhongMaterial(axisGizmoScene, { // Red by convention\n diffuse: [1, 0.3, 0.3],\n ambient: [0.0, 0.0, 0.0],\n specular: [.6, .6, .3],\n shininess: 80,\n lineWidth: 2\n });\n\n const xAxisLabelMaterial = new PhongMaterial(axisGizmoScene, { // Red by convention\n emissive: [1, 0.3, 0.3],\n ambient: [0.0, 0.0, 0.0],\n specular: [.6, .6, .3],\n shininess: 80,\n lineWidth: 2\n });\n\n const yAxisMaterial = new PhongMaterial(axisGizmoScene, { // Green by convention\n diffuse: [0.3, 1, 0.3],\n ambient: [0.0, 0.0, 0.0],\n specular: [.6, .6, .3],\n shininess: 80,\n lineWidth: 2\n });\n\n const yAxisLabelMaterial = new PhongMaterial(axisGizmoScene, { // Green by convention\n emissive: [0.3, 1, 0.3],\n ambient: [0.0, 0.0, 0.0],\n specular: [.6, .6, .3],\n shininess: 80,\n lineWidth: 2\n });\n\n const zAxisMaterial = new PhongMaterial(axisGizmoScene, { // Blue by convention\n diffuse: [0.3, 0.3, 1],\n ambient: [0.0, 0.0, 0.0],\n specular: [.6, .6, .3],\n shininess: 80,\n lineWidth: 2\n });\n\n const zAxisLabelMaterial = new PhongMaterial(axisGizmoScene, {\n emissive: [0.3, 0.3, 1],\n ambient: [0.0, 0.0, 0.0],\n specular: [.6, .6, .3],\n shininess: 80,\n lineWidth: 2\n });\n\n const ballMaterial = new PhongMaterial(axisGizmoScene, {\n diffuse: [0.5, 0.5, 0.5],\n ambient: [0.0, 0.0, 0.0],\n specular: [.6, .6, .3],\n shininess: 80,\n lineWidth: 2\n });\n\n // ----------------- Meshes ------------------------------\n\n this._meshes = [\n\n // Sphere behind gnomon\n\n new Mesh(axisGizmoScene, {\n geometry: new ReadableGeometry(axisGizmoScene, buildSphereGeometry({\n radius: 9.0,\n heightSegments: 60,\n widthSegments: 60\n })),\n material: new PhongMaterial(axisGizmoScene, {\n diffuse: [0.0, 0.0, 0.0],\n emissive: [0.1, 0.1, 0.1],\n ambient: [0.1, 0.1, 0.2],\n specular: [0, 0, 0],\n alpha: 0.4,\n alphaMode: \"blend\",\n frontface: \"cw\"\n }),\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false\n }),\n\n // Ball at center of axis\n\n new Mesh(axisGizmoScene, { // Arrow\n geometry: new ReadableGeometry(axisGizmoScene, buildSphereGeometry({\n radius: 1.0\n })),\n material: ballMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false\n }),\n\n // X-axis arrow, shaft and label\n\n new Mesh(axisGizmoScene, { // Arrow\n geometry: arrowHead,\n material: xAxisMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [5, 0, 0],\n rotation: [0, 0, -90]\n }),\n\n new Mesh(axisGizmoScene, { // Shaft\n geometry: arrowShaft,\n material: xAxisMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [2, 0, 0],\n rotation: [0, 0, 90]\n }),\n\n new Mesh(axisGizmoScene, { // Label\n geometry: new ReadableGeometry(axisGizmoScene, buildVectorTextGeometry({text: \"X\", size: 1.5})),\n material: xAxisLabelMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [7, 0, 0],\n billboard: \"spherical\"\n }),\n\n // Y-axis arrow, shaft and label\n\n new Mesh(axisGizmoScene, { // Arrow\n geometry: arrowHead,\n material: yAxisMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [0, 5, 0]\n }),\n\n new Mesh(axisGizmoScene, { // Shaft\n geometry: arrowShaft,\n material: yAxisMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [0, 2, 0]\n }),\n\n new Mesh(axisGizmoScene, { // Label\n geometry: new ReadableGeometry(axisGizmoScene, buildVectorTextGeometry({text: \"Y\", size: 1.5})),\n material: yAxisLabelMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [0, 7, 0],\n billboard: \"spherical\"\n }),\n\n // Z-axis arrow, shaft and label\n\n new Mesh(axisGizmoScene, { // Arrow\n geometry: arrowHead,\n material: zAxisMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [0, 0, 5],\n rotation: [90, 0, 0]\n }),\n\n new Mesh(axisGizmoScene, { // Shaft\n geometry: arrowShaft,\n material: zAxisMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [0, 0, 2],\n rotation: [90, 0, 0]\n }),\n\n new Mesh(axisGizmoScene, { // Label\n geometry: new ReadableGeometry(axisGizmoScene, buildVectorTextGeometry({text: \"Z\", size: 1.5})),\n material: zAxisLabelMaterial,\n pickable: false,\n collidable: false,\n visible: cfg.visible !== false,\n position: [0, 0, 7],\n billboard: \"spherical\"\n })\n ];\n }\n\n /** Shows or hides this AxisGizmoPlugin.\n *\n * @param visible\n */\n setVisible(visible) {\n for (let i = 0; i < this._meshes.length; i++) {\n this._meshes[i].visible = visible;\n }\n }\n\n /**\n * Destroys this AxisGizmoPlugin.\n */\n destroy() {\n this._axisGizmoCanvas = null;\n this._axisGizmoScene.destroy();\n this._axisGizmoScene = null;\n super.destroy();\n }\n}\n\nmath.vec3();\n\n/**\n * @desc An arbitrarily-aligned World-space clipping plane.\n *\n * * Slices portions off objects to create cross-section views or reveal interiors.\n * * Registered by {@link SectionPlane#id} in {@link Scene#sectionPlanes}.\n * * Indicates World-space position in {@link SectionPlane#pos} and orientation in {@link SectionPlane#dir}.\n * * Discards elements from the half-space in the direction of {@link SectionPlane#dir}.\n * * Can be be enabled or disabled via {@link SectionPlane#active}.\n *\n * ## Usage\n *\n * In the example below, we'll create two SectionPlanes to slice a model loaded from glTF. Note that we could also create them\n * using a {@link SectionPlanesPlugin}.\n *\n * ````javascript\n * import {Viewer, GLTFLoaderPlugin, SectionPlane} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * const gltfLoaderPlugin = new GLTFModelsPlugin(viewer, {\n * id: \"GLTFModels\"\n * });\n *\n * const model = gltfLoaderPlugin.load({\n * id: \"myModel\",\n * src: \"./models/gltf/mygltfmodel.gltf\"\n * });\n *\n * // Create a SectionPlane on negative diagonal\n * const sectionPlane1 = new SectionPlane(viewer.scene, {\n * pos: [1.0, 1.0, 1.0],\n * dir: [-1.0, -1.0, -1.0],\n * active: true\n * }),\n *\n * // Create a SectionPlane on positive diagonal\n * const sectionPlane2 = new SectionPlane(viewer.scene, {\n * pos: [-1.0, -1.0, -1.0],\n * dir: [1.0, 1.0, 1.0],\n * active: true\n * });\n * ````\n */\nclass SectionPlane extends Component {\n\n /**\n @private\n */\n get type() {\n return \"SectionPlane\";\n }\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this SectionPlane as well.\n * @param {*} [cfg] SectionPlane configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Boolean} [cfg.active=true] Indicates whether or not this SectionPlane is active.\n * @param {Number[]} [cfg.pos=[0,0,0]] World-space position of the SectionPlane.\n * @param {Number[]} [cfg.dir=[0,0,-1]] Vector perpendicular to the plane surface, indicating the SectionPlane plane orientation.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n active: true,\n pos: math.vec3(),\n dir: math.vec3(),\n dist: 0\n });\n\n this.active = cfg.active;\n this.pos = cfg.pos;\n this.dir = cfg.dir;\n\n this.scene._sectionPlaneCreated(this);\n }\n\n /**\n * Sets if this SectionPlane is active or not.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} value Set ````true```` to activate else ````false```` to deactivate.\n */\n set active(value) {\n this._state.active = value !== false;\n this.glRedraw();\n this.fire(\"active\", this._state.active);\n }\n\n /**\n * Gets if this SectionPlane is active or not.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} Returns ````true```` if active.\n */\n get active() {\n return this._state.active;\n }\n\n /**\n * Sets the World-space position of this SectionPlane's plane.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * @param {Number[]} value New position.\n */\n set pos(value) {\n this._state.pos.set(value || [0, 0, 0]);\n this._state.dist = (-math.dotVec3(this._state.pos, this._state.dir));\n this.fire(\"pos\", this._state.pos);\n this.scene.fire(\"sectionPlaneUpdated\", this);\n }\n\n /**\n * Gets the World-space position of this SectionPlane's plane.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * @returns {Number[]} Current position.\n */\n get pos() {\n return this._state.pos;\n }\n\n /**\n * Sets the direction of this SectionPlane's plane.\n *\n * Default value is ````[0, 0, -1]````.\n *\n * @param {Number[]} value New direction.\n */\n set dir(value) {\n this._state.dir.set(value || [0, 0, -1]);\n this._state.dist = (-math.dotVec3(this._state.pos, this._state.dir));\n this.glRedraw();\n this.fire(\"dir\", this._state.dir);\n this.scene.fire(\"sectionPlaneUpdated\", this);\n }\n\n /**\n * Gets the direction of this SectionPlane's plane.\n *\n * Default value is ````[0, 0, -1]````.\n *\n * @returns {Number[]} value Current direction.\n */\n get dir() {\n return this._state.dir;\n }\n\n /**\n * Gets this SectionPlane's distance to the origin of the World-space coordinate system.\n *\n * This is the dot product of {@link SectionPlane#pos} and {@link SectionPlane#dir} and is automatically re-calculated\n * each time either of two properties are updated.\n *\n * @returns {Number}\n */\n get dist() {\n return this._state.dist;\n }\n\n /**\n * Inverts the direction of {@link SectionPlane#dir}.\n */\n flipDir() {\n const dir = this._state.dir;\n dir[0] *= -1.0;\n dir[1] *= -1.0;\n dir[2] *= -1.0;\n this._state.dist = (-math.dotVec3(this._state.pos, this._state.dir));\n this.fire(\"dir\", this._state.dir);\n this.glRedraw();\n }\n\n /**\n * @destroy\n */\n destroy() {\n this._state.destroy();\n this.scene._sectionPlaneDestroyed(this);\n super.destroy();\n }\n}\n\nconst angleAxis$1 = math.vec4(4);\nconst q1$1 = math.vec4();\nconst q2$1 = math.vec4();\nconst xAxis$1 = math.vec3([1, 0, 0]);\nconst yAxis$1 = math.vec3([0, 1, 0]);\nconst zAxis$1 = math.vec3([0, 0, 1]);\n\nconst veca = math.vec3(3);\nconst vecb = math.vec3(3);\n\nconst identityMat$1 = math.identityMat4();\n\n/**\n * @desc An {@link Entity} that is a scene graph node that can have child Nodes and {@link Mesh}es.\n *\n * ## Usage\n *\n * The example below is the same as the one given for {@link Mesh}, since the two classes work together. In this example,\n * we'll create a scene graph in which a root Node represents a group and the {@link Mesh}s are leaves. Since Node\n * implements {@link Entity}, we can designate the root Node as a model, causing it to be registered by its ID in {@link Scene#models}.\n *\n * Since {@link Mesh} also implements {@link Entity}, we can designate the leaf {@link Mesh}es as objects, causing them to\n * be registered by their IDs in {@link Scene#objects}.\n *\n * We can then find those {@link Entity} types in {@link Scene#models} and {@link Scene#objects}.\n *\n * We can also update properties of our object-Meshes via calls to {@link Scene#setObjectsHighlighted} etc.\n *\n * [[Run this example](/examples/index.html#sceneRepresentation_SceneGraph)]\n *\n * ````javascript\n * import {Viewer, Mesh, Node, PhongMaterial} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * new Node(viewer.scene, {\n * id: \"table\",\n * isModel: true, // <---------- Node represents a model, so is registered by ID in viewer.scene.models\n * rotation: [0, 50, 0],\n * position: [0, 0, 0],\n * scale: [1, 1, 1],\n *\n * children: [\n *\n * new Mesh(viewer.scene, { // Red table leg\n * id: \"redLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1, 0.3, 0.3]\n * })\n * }),\n *\n * new Mesh(viewer.scene, { // Green table leg\n * id: \"greenLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.3, 1.0, 0.3]\n * })\n * }),\n *\n * new Mesh(viewer.scene, {// Blue table leg\n * id: \"blueLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.3, 0.3, 1.0]\n * })\n * }),\n *\n * new Mesh(viewer.scene, { // Yellow table leg\n * id: \"yellowLeg\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1.0, 1.0, 0.0]\n * })\n * }),\n *\n * new Mesh(viewer.scene, { // Purple table top\n * id: \"tableTop\",\n * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1.0, 0.3, 1.0]\n * })\n * })\n * ]\n * });\n *\n * // Find Nodes and Meshes by their IDs\n *\n * var table = viewer.scene.models[\"table\"]; // Since table Node has isModel == true\n *\n * var redLeg = viewer.scene.objects[\"redLeg\"]; // Since the Meshes have isObject == true\n * var greenLeg = viewer.scene.objects[\"greenLeg\"];\n * var blueLeg = viewer.scene.objects[\"blueLeg\"];\n *\n * // Highlight one of the table leg Meshes\n *\n * viewer.scene.setObjectsHighlighted([\"redLeg\"], true); // Since the Meshes have isObject == true\n *\n * // Periodically update transforms on our Nodes and Meshes\n *\n * viewer.scene.on(\"tick\", function () {\n *\n * // Rotate legs\n * redLeg.rotateY(0.5);\n * greenLeg.rotateY(0.5);\n * blueLeg.rotateY(0.5);\n *\n * // Rotate table\n * table.rotateY(0.5);\n * table.rotateX(0.3);\n * });\n * ````\n *\n * ## Metadata\n *\n * As mentioned, we can also associate {@link MetaModel}s and {@link MetaObject}s with our Nodes and {@link Mesh}es,\n * within a {@link MetaScene}. See {@link MetaScene} for an example.\n *\n * @implements {Entity}\n */\nclass Node$2 extends Component {\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent scene, generated automatically when omitted.\n * @param {Boolean} [cfg.isModel] Specify ````true```` if this Mesh represents a model, in which case the Mesh will be registered by {@link Mesh#id} in {@link Scene#models} and may also have a corresponding {@link MetaModel} with matching {@link MetaModel#id}, registered by that ID in {@link MetaScene#metaModels}.\n * @param {Boolean} [cfg.isObject] Specify ````true```` if this Mesh represents an object, in which case the Mesh will be registered by {@link Mesh#id} in {@link Scene#objects} and may also have a corresponding {@link MetaObject} with matching {@link MetaObject#id}, registered by that ID in {@link MetaScene#metaObjects}.\n * @param {Node} [cfg.parent] The parent Node.\n * @param {Number[]} [cfg.origin] World-space origin for this Node.\n * @param {Number[]} [cfg.rtcCenter] Deprecated - renamed to ````origin````.\n * @param {Number[]} [cfg.position=[0,0,0]] Local 3D position.\n * @param {Number[]} [cfg.scale=[1,1,1]] Local scale.\n * @param {Number[]} [cfg.rotation=[0,0,0]] Local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] Local modelling transform matrix. Overrides the position, scale and rotation parameters.\n * @param {Number[]} [cfg.offset=[0,0,0]] World-space 3D translation offset. Translates the Node in World space, after modelling transforms.\n * @param {Boolean} [cfg.visible=true] Indicates if the Node is initially visible.\n * @param {Boolean} [cfg.culled=false] Indicates if the Node is initially culled from view.\n * @param {Boolean} [cfg.pickable=true] Indicates if the Node is initially pickable.\n * @param {Boolean} [cfg.clippable=true] Indicates if the Node is initially clippable.\n * @param {Boolean} [cfg.collidable=true] Indicates if the Node is initially included in boundary calculations.\n * @param {Boolean} [cfg.castsShadow=true] Indicates if the Node initially casts shadows.\n * @param {Boolean} [cfg.receivesShadow=true] Indicates if the Node initially receives shadows.\n * @param {Boolean} [cfg.xrayed=false] Indicates if the Node is initially xrayed.\n * @param {Boolean} [cfg.highlighted=false] Indicates if the Node is initially highlighted.\n * @param {Boolean} [cfg.selected=false] Indicates if the Mesh is initially selected.\n * @param {Boolean} [cfg.edges=false] Indicates if the Node's edges are initially emphasized.\n * @param {Number[]} [cfg.colorize=[1.0,1.0,1.0]] Node's initial RGB colorize color, multiplies by the rendered fragment colors.\n * @param {Number} [cfg.opacity=1.0] Node's initial opacity factor, multiplies by the rendered fragment alpha.\n * @param {Array} [cfg.children] Child Nodes or {@link Mesh}es to add initially. Children must be in the same {@link Scene} and will be removed first from whatever parents they may already have.\n * @param {Boolean} [cfg.inheritStates=true] Indicates if children given to this constructor should inherit rendering state from this parent as they are added. Rendering state includes {@link Node#visible}, {@link Node#culled}, {@link Node#pickable}, {@link Node#clippable}, {@link Node#castsShadow}, {@link Node#receivesShadow}, {@link Node#selected}, {@link Node#highlighted}, {@link Node#colorize} and {@link Node#opacity}.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._parentNode = null;\n this._children = [];\n\n this._aabb = null;\n this._aabbDirty = true;\n\n this.scene._aabbDirty = true;\n\n this._numTriangles = 0;\n\n this._scale = math.vec3();\n this._quaternion = math.identityQuaternion();\n this._rotation = math.vec3();\n this._position = math.vec3();\n this._offset = math.vec3();\n\n this._localMatrix = math.identityMat4();\n this._worldMatrix = math.identityMat4();\n\n this._localMatrixDirty = true;\n this._worldMatrixDirty = true;\n\n if (cfg.matrix) {\n this.matrix = cfg.matrix;\n } else {\n this.scale = cfg.scale;\n this.position = cfg.position;\n if (cfg.quaternion) ; else {\n this.rotation = cfg.rotation;\n }\n }\n\n this._isModel = cfg.isModel;\n if (this._isModel) {\n this.scene._registerModel(this);\n }\n\n this._isObject = cfg.isObject;\n if (this._isObject) {\n this.scene._registerObject(this);\n }\n\n this.origin = cfg.origin;\n this.visible = cfg.visible;\n this.culled = cfg.culled;\n this.pickable = cfg.pickable;\n this.clippable = cfg.clippable;\n this.collidable = cfg.collidable;\n this.castsShadow = cfg.castsShadow;\n this.receivesShadow = cfg.receivesShadow;\n this.xrayed = cfg.xrayed;\n this.highlighted = cfg.highlighted;\n this.selected = cfg.selected;\n this.edges = cfg.edges;\n this.colorize = cfg.colorize;\n this.opacity = cfg.opacity;\n this.offset = cfg.offset;\n\n // Add children, which inherit state from this Node\n\n if (cfg.children) {\n const children = cfg.children;\n for (let i = 0, len = children.length; i < len; i++) {\n this.addChild(children[i], cfg.inheritStates);\n }\n }\n\n if (cfg.parentId) {\n const parentNode = this.scene.components[cfg.parentId];\n if (!parentNode) {\n this.error(\"Parent not found: '\" + cfg.parentId + \"'\");\n } else if (!parentNode.isNode) {\n this.error(\"Parent is not a Node: '\" + cfg.parentId + \"'\");\n } else {\n parentNode.addChild(this);\n }\n } else if (cfg.parent) {\n if (!cfg.parent.isNode) {\n this.error(\"Parent is not a Node\");\n }\n cfg.parent.addChild(this);\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Entity members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Returns true to indicate that this Component is an Entity.\n * @type {Boolean}\n */\n get isEntity() {\n return true;\n }\n\n /**\n * Returns ````true```` if this Mesh represents a model.\n *\n * When this returns ````true````, the Mesh will be registered by {@link Mesh#id} in {@link Scene#models} and\n * may also have a corresponding {@link MetaModel}.\n *\n * @type {Boolean}\n */\n get isModel() {\n return this._isModel;\n }\n\n /**\n * Returns ````true```` if this Node represents an object.\n *\n * When ````true```` the Node will be registered by {@link Node#id} in\n * {@link Scene#objects} and may also have a {@link MetaObject} with matching {@link MetaObject#id}.\n *\n * @type {Boolean}\n * @abstract\n */\n get isObject() {\n return this._isObject;\n }\n\n /**\n * Gets the Node's World-space 3D axis-aligned bounding box.\n *\n * Represented by a six-element Float64Array containing the min/max extents of the\n * axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````.\n *\n * @type {Number[]}\n */\n get aabb() {\n if (this._aabbDirty) {\n this._updateAABB();\n }\n return this._aabb;\n }\n\n /**\n * Sets the World-space origin for this Node.\n *\n * @type {Float64Array}\n */\n set origin(origin) {\n if (origin) {\n if (!this._origin) {\n this._origin = math.vec3();\n }\n this._origin.set(origin);\n } else {\n if (this._origin) {\n this._origin = null;\n }\n }\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].origin = origin;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the World-space origin for this Node.\n *\n * @type {Float64Array}\n */\n get origin() {\n return this._origin;\n }\n\n /**\n * Sets the World-space origin for this Node.\n *\n * Deprecated and replaced by {@link Node#origin}.\n *\n * @deprecated\n * @type {Float64Array}\n */\n set rtcCenter(rtcCenter) {\n this.origin = rtcCenter;\n }\n\n /**\n * Gets the World-space origin for this Node.\n *\n * Deprecated and replaced by {@link Node#origin}.\n *\n * @deprecated\n * @type {Float64Array}\n */\n get rtcCenter() {\n return this.origin;\n }\n\n /**\n * The number of triangles in this Node.\n *\n * @type {Number}\n */\n get numTriangles() {\n return this._numTriangles;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are visible.\n *\n * Only rendered both {@link Node#visible} is ````true```` and {@link Node#culled} is ````false````.\n *\n * When {@link Node#isObject} and {@link Node#visible} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#visibleObjects}.\n *\n * @type {Boolean}\n */\n set visible(visible) {\n visible = visible !== false;\n this._visible = visible;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].visible = visible;\n }\n if (this._isObject) {\n this.scene._objectVisibilityUpdated(this, visible);\n }\n }\n\n /**\n * Gets if this Node is visible.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * When {@link Node#isObject} and {@link Node#visible} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#visibleObjects}.\n *\n * @type {Boolean}\n */\n get visible() {\n return this._visible;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are xrayed.\n *\n * When {@link Node#isObject} and {@link Node#xrayed} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#xrayedObjects}.\n *\n * @type {Boolean}\n */\n set xrayed(xrayed) {\n xrayed = !!xrayed;\n this._xrayed = xrayed;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].xrayed = xrayed;\n }\n if (this._isObject) {\n this.scene._objectXRayedUpdated(this, xrayed);\n }\n }\n\n /**\n * Gets if this Node is xrayed.\n *\n * When {@link Node#isObject} and {@link Node#xrayed} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#xrayedObjects}.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get xrayed() {\n return this._xrayed;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are highlighted.\n *\n * When {@link Node#isObject} and {@link Node#highlighted} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#highlightedObjects}.\n *\n * @type {Boolean}\n */\n set highlighted(highlighted) {\n highlighted = !!highlighted;\n this._highlighted = highlighted;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].highlighted = highlighted;\n }\n if (this._isObject) {\n this.scene._objectHighlightedUpdated(this, highlighted);\n }\n }\n\n /**\n * Gets if this Node is highlighted.\n *\n * When {@link Node#isObject} and {@link Node#highlighted} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#highlightedObjects}.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get highlighted() {\n return this._highlighted;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are selected.\n *\n * When {@link Node#isObject} and {@link Node#selected} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#selectedObjects}.\n *\n * @type {Boolean}\n */\n set selected(selected) {\n selected = !!selected;\n this._selected = selected;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].selected = selected;\n }\n if (this._isObject) {\n this.scene._objectSelectedUpdated(this, selected);\n }\n }\n\n /**\n * Gets if this Node is selected.\n *\n * When {@link Node#isObject} and {@link Node#selected} are both ````true```` the Node will be\n * registered by {@link Node#id} in {@link Scene#selectedObjects}.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get selected() {\n return this._selected;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are edge-enhanced.\n *\n * @type {Boolean}\n */\n set edges(edges) {\n edges = !!edges;\n this._edges = edges;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].edges = edges;\n }\n }\n\n /**\n * Gets if this Node's edges are enhanced.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get edges() {\n return this._edges;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are culled.\n *\n * @type {Boolean}\n */\n set culled(culled) {\n culled = !!culled;\n this._culled = culled;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].culled = culled;\n }\n }\n\n /**\n * Gets if this Node is culled.\n *\n * @type {Boolean}\n */\n get culled() {\n return this._culled;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#clips}.\n *\n * @type {Boolean}\n */\n set clippable(clippable) {\n clippable = clippable !== false;\n this._clippable = clippable;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].clippable = clippable;\n }\n }\n\n /**\n * Gets if this Node is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#clips}.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get clippable() {\n return this._clippable;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are included in boundary calculations.\n *\n * @type {Boolean}\n */\n set collidable(collidable) {\n collidable = collidable !== false;\n this._collidable = collidable;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].collidable = collidable;\n }\n }\n\n /**\n * Gets if this Node is included in boundary calculations.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get collidable() {\n return this._collidable;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es are pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * @type {Boolean}\n */\n set pickable(pickable) {\n pickable = pickable !== false;\n this._pickable = pickable;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].pickable = pickable;\n }\n }\n\n /**\n * Gets if to this Node is pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get pickable() {\n return this._pickable;\n }\n\n /**\n * Sets the RGB colorize color for this Node and all child Nodes and {@link Mesh}es}.\n *\n * Multiplies by rendered fragment colors.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * @type {Number[]}\n */\n set colorize(rgb) {\n let colorize = this._colorize;\n if (!colorize) {\n colorize = this._colorize = new Float32Array(4);\n colorize[3] = 1.0;\n }\n if (rgb) {\n colorize[0] = rgb[0];\n colorize[1] = rgb[1];\n colorize[2] = rgb[2];\n } else {\n colorize[0] = 1;\n colorize[1] = 1;\n colorize[2] = 1;\n }\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].colorize = colorize;\n }\n if (this._isObject) {\n const colorized = (!!rgb);\n this.scene._objectColorizeUpdated(this, colorized);\n }\n }\n\n /**\n * Gets the RGB colorize color for this Node.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Number[]}\n */\n get colorize() {\n return this._colorize.slice(0, 3);\n }\n\n /**\n * Sets the opacity factor for this Node and all child Nodes and {@link Mesh}es.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n set opacity(opacity) {\n let colorize = this._colorize;\n if (!colorize) {\n colorize = this._colorize = new Float32Array(4);\n colorize[0] = 1;\n colorize[1] = 1;\n colorize[2] = 1;\n }\n colorize[3] = opacity !== null && opacity !== undefined ? opacity : 1.0;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].opacity = opacity;\n }\n if (this._isObject) {\n const opacityUpdated = (opacity !== null && opacity !== undefined);\n this.scene._objectOpacityUpdated(this, opacityUpdated);\n }\n }\n\n /**\n * Gets this Node's opacity factor.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Number}\n */\n get opacity() {\n return this._colorize[3];\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es cast shadows.\n *\n * @type {Boolean}\n */\n set castsShadow(castsShadow) {\n castsShadow = !!castsShadow;\n this._castsShadow = castsShadow;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].castsShadow = castsShadow;\n }\n }\n\n /**\n * Gets if this Node casts shadows.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get castsShadow() {\n return this._castsShadow;\n }\n\n /**\n * Sets if this Node and all child Nodes and {@link Mesh}es can have shadows cast upon them.\n *\n * @type {Boolean}\n */\n set receivesShadow(receivesShadow) {\n receivesShadow = !!receivesShadow;\n this._receivesShadow = receivesShadow;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].receivesShadow = receivesShadow;\n }\n }\n\n /**\n * Whether or not to this Node can have shadows cast upon it.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Boolean}\n */\n get receivesShadow() {\n return this._receivesShadow;\n }\n\n /**\n * Gets if this Node can have Scalable Ambient Obscurance (SAO) applied to it.\n *\n * SAO is configured by {@link SAO}.\n *\n * @type {Boolean}\n * @abstract\n */\n get saoEnabled() {\n return false; // TODO: Support SAO on Nodes\n }\n\n\n /**\n * Sets the 3D World-space offset for this Node and all child Nodes and {@link Mesh}es}.\n *\n * The offset dynamically translates those components in World-space.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * Note that child Nodes and {@link Mesh}es may subsequently be given different values for this property.\n *\n * @type {Number[]}\n */\n set offset(offset) {\n if (offset) {\n this._offset[0] = offset[0];\n this._offset[1] = offset[1];\n this._offset[2] = offset[2];\n } else {\n this._offset[0] = 0;\n this._offset[1] = 0;\n this._offset[2] = 0;\n }\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i].offset = this._offset;\n }\n if (this._isObject) {\n this.scene._objectOffsetUpdated(this, offset);\n }\n }\n\n /**\n * Gets the Node's 3D World-space offset.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * Child Nodes and {@link Mesh}es may have different values for this property.\n *\n * @type {Number[]}\n */\n get offset() {\n return this._offset;\n }\n\n\n //------------------------------------------------------------------------------------------------------------------\n // Node members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Returns true to indicate that this Component is a Node.\n * @type {Boolean}\n */\n get isNode() {\n return true;\n }\n\n _setLocalMatrixDirty() {\n this._localMatrixDirty = true;\n this._setWorldMatrixDirty();\n }\n\n _setWorldMatrixDirty() {\n this._worldMatrixDirty = true;\n for (let i = 0, len = this._children.length; i < len; i++) {\n this._children[i]._setWorldMatrixDirty();\n }\n }\n\n _buildWorldMatrix() {\n const localMatrix = this.matrix;\n if (!this._parentNode) {\n for (let i = 0, len = localMatrix.length; i < len; i++) {\n this._worldMatrix[i] = localMatrix[i];\n }\n } else {\n math.mulMat4(this._parentNode.worldMatrix, localMatrix, this._worldMatrix);\n }\n this._worldMatrixDirty = false;\n }\n\n _setSubtreeAABBsDirty(node) {\n node._aabbDirty = true;\n if (node._children) {\n for (let i = 0, len = node._children.length; i < len; i++) {\n this._setSubtreeAABBsDirty(node._children[i]);\n }\n }\n }\n\n _setAABBDirty() {\n this._setSubtreeAABBsDirty(this);\n if (this.collidable) {\n for (let node = this; node; node = node._parentNode) {\n node._aabbDirty = true;\n }\n }\n }\n\n _updateAABB() {\n this.scene._aabbDirty = true;\n if (!this._aabb) {\n this._aabb = math.AABB3();\n }\n if (this._buildAABB) {\n this._buildAABB(this.worldMatrix, this._aabb); // Mesh or VBOSceneModel\n } else { // Node | Node | Model\n math.collapseAABB3(this._aabb);\n let node;\n for (let i = 0, len = this._children.length; i < len; i++) {\n node = this._children[i];\n if (!node.collidable) {\n continue;\n }\n math.expandAABB3(this._aabb, node.aabb);\n }\n }\n this._aabbDirty = false;\n }\n\n /**\n * Adds a child Node or {@link Mesh}.\n *\n * The child must be a Node or {@link Mesh} in the same {@link Scene}.\n *\n * If the child already has a parent, will be removed from that parent first.\n *\n * Does nothing if already a child.\n *\n * @param {Node|Mesh|String} child Instance or ID of the child to add.\n * @param [inheritStates=false] Indicates if the child should inherit rendering states from this parent as it is added. Rendering state includes {@link Node#visible}, {@link Node#culled}, {@link Node#pickable}, {@link Node#clippable}, {@link Node#castsShadow}, {@link Node#receivesShadow}, {@link Node#selected}, {@link Node#highlighted}, {@link Node#colorize} and {@link Node#opacity}.\n * @returns {Node|Mesh} The child.\n */\n addChild(child, inheritStates) {\n if (utils.isNumeric(child) || utils.isString(child)) {\n const nodeId = child;\n child = this.scene.component[nodeId];\n if (!child) {\n this.warn(\"Component not found: \" + utils.inQuotes(nodeId));\n return;\n }\n if (!child.isNode && !child.isMesh) {\n this.error(\"Not a Node or Mesh: \" + nodeId);\n return;\n }\n } else {\n if (!child.isNode && !child.isMesh) {\n this.error(\"Not a Node or Mesh: \" + child.id);\n return;\n }\n if (child._parentNode) {\n if (child._parentNode.id === this.id) {\n this.warn(\"Already a child: \" + child.id);\n return;\n }\n child._parentNode.removeChild(child);\n }\n }\n child.id;\n if (child.scene.id !== this.scene.id) {\n this.error(\"Child not in same Scene: \" + child.id);\n return;\n }\n this._children.push(child);\n child._parentNode = this;\n if (!!inheritStates) {\n child.visible = this.visible;\n child.culled = this.culled;\n child.xrayed = this.xrayed;\n child.highlited = this.highlighted;\n child.selected = this.selected;\n child.edges = this.edges;\n child.clippable = this.clippable;\n child.pickable = this.pickable;\n child.collidable = this.collidable;\n child.castsShadow = this.castsShadow;\n child.receivesShadow = this.receivesShadow;\n child.colorize = this.colorize;\n child.opacity = this.opacity;\n child.offset = this.offset;\n }\n child._setWorldMatrixDirty();\n child._setAABBDirty();\n this._numTriangles += child.numTriangles;\n return child;\n }\n\n /**\n * Removes the given child Node or {@link Mesh}.\n *\n * @param {Node|Mesh} child Child to remove.\n */\n removeChild(child) {\n for (let i = 0, len = this._children.length; i < len; i++) {\n if (this._children[i].id === child.id) {\n child._parentNode = null;\n this._children = this._children.splice(i, 1);\n child._setWorldMatrixDirty();\n child._setAABBDirty();\n this._setAABBDirty();\n this._numTriangles -= child.numTriangles;\n return;\n }\n }\n }\n\n /**\n * Removes all child Nodes and {@link Mesh}es.\n */\n removeChildren() {\n let child;\n for (let i = 0, len = this._children.length; i < len; i++) {\n child = this._children[i];\n child._parentNode = null;\n child._setWorldMatrixDirty();\n child._setAABBDirty();\n this._numTriangles -= child.numTriangles;\n }\n this._children = [];\n this._setAABBDirty();\n }\n\n /**\n * Number of child Nodes or {@link Mesh}es.\n *\n * @type {Number}\n */\n get numChildren() {\n return this._children.length;\n }\n\n /**\n * Array of child Nodes or {@link Mesh}es.\n *\n * @type {Array}\n */\n get children() {\n return this._children;\n }\n\n /**\n * The parent Node.\n *\n * The parent Node may also be set by passing the Node to the parent's {@link Node#addChild} method.\n *\n * @type {Node}\n */\n set parent(node) {\n if (utils.isNumeric(node) || utils.isString(node)) {\n const nodeId = node;\n node = this.scene.components[nodeId];\n if (!node) {\n this.warn(\"Node not found: \" + utils.inQuotes(nodeId));\n return;\n }\n if (!node.isNode) {\n this.error(\"Not a Node: \" + node.id);\n return;\n }\n }\n if (node.scene.id !== this.scene.id) {\n this.error(\"Node not in same Scene: \" + node.id);\n return;\n }\n if (this._parentNode && this._parentNode.id === node.id) {\n this.warn(\"Already a child of Node: \" + node.id);\n return;\n }\n node.addChild(this);\n }\n\n /**\n * The parent Node.\n *\n * @type {Node}\n */\n get parent() {\n return this._parentNode;\n }\n\n /**\n * Sets the Node's local translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set position(value) {\n this._position.set(value || [0, 0, 0]);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Node's local translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get position() {\n return this._position;\n }\n\n /**\n * Sets the Node's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set rotation(value) {\n this._rotation.set(value || [0, 0, 0]);\n math.eulerToQuaternion(this._rotation, \"XYZ\", this._quaternion);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Node's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get rotation() {\n return this._rotation;\n }\n\n /**\n * Sets the Node's local rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n set quaternion(value) {\n this._quaternion.set(value || [0, 0, 0, 1]);\n math.quaternionToEuler(this._quaternion, \"XYZ\", this._rotation);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Node's local rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n get quaternion() {\n return this._quaternion;\n }\n\n /**\n * Sets the Node's local scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n */\n set scale(value) {\n this._scale.set(value || [1, 1, 1]);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Node's local scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the Node's local modeling transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n set matrix(value) {\n if (!this._localMatrix) {\n this._localMatrix = math.identityMat4();\n }\n this._localMatrix.set(value || identityMat$1);\n math.decomposeMat4(this._localMatrix, this._position, this._quaternion, this._scale);\n this._localMatrixDirty = false;\n this._setWorldMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the Node's local modeling transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n get matrix() {\n if (this._localMatrixDirty) {\n if (!this._localMatrix) {\n this._localMatrix = math.identityMat4();\n }\n math.composeMat4(this._position, this._quaternion, this._scale, this._localMatrix);\n this._localMatrixDirty = false;\n }\n return this._localMatrix;\n }\n\n /**\n * Gets the Node's World matrix.\n *\n * @property worldMatrix\n * @type {Number[]}\n */\n get worldMatrix() {\n if (this._worldMatrixDirty) {\n this._buildWorldMatrix();\n }\n return this._worldMatrix;\n }\n\n /**\n * Rotates the Node about the given local axis by the given increment.\n *\n * @param {Number[]} axis Local axis about which to rotate.\n * @param {Number} angle Angle increment in degrees.\n */\n rotate(axis, angle) {\n angleAxis$1[0] = axis[0];\n angleAxis$1[1] = axis[1];\n angleAxis$1[2] = axis[2];\n angleAxis$1[3] = angle * math.DEGTORAD;\n math.angleAxisToQuaternion(angleAxis$1, q1$1);\n math.mulQuaternions(this.quaternion, q1$1, q2$1);\n this.quaternion = q2$1;\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n return this;\n }\n\n /**\n * Rotates the Node about the given World-space axis by the given increment.\n *\n * @param {Number[]} axis Local axis about which to rotate.\n * @param {Number} angle Angle increment in degrees.\n */\n rotateOnWorldAxis(axis, angle) {\n angleAxis$1[0] = axis[0];\n angleAxis$1[1] = axis[1];\n angleAxis$1[2] = axis[2];\n angleAxis$1[3] = angle * math.DEGTORAD;\n math.angleAxisToQuaternion(angleAxis$1, q1$1);\n math.mulQuaternions(q1$1, this.quaternion, q1$1);\n //this.quaternion.premultiply(q1);\n return this;\n }\n\n /**\n * Rotates the Node about the local X-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateX(angle) {\n return this.rotate(xAxis$1, angle);\n }\n\n /**\n * Rotates the Node about the local Y-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateY(angle) {\n return this.rotate(yAxis$1, angle);\n }\n\n /**\n * Rotates the Node about the local Z-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateZ(angle) {\n return this.rotate(zAxis$1, angle);\n }\n\n /**\n * Translates the Node along local space vector by the given increment.\n *\n * @param {Number[]} axis Normalized local space 3D vector along which to translate.\n * @param {Number} distance Distance to translate along the vector.\n */\n translate(axis, distance) {\n math.vec3ApplyQuaternion(this.quaternion, axis, veca);\n math.mulVec3Scalar(veca, distance, vecb);\n math.addVec3(this.position, vecb, this.position);\n this._setLocalMatrixDirty();\n this._setAABBDirty();\n this.glRedraw();\n return this;\n }\n\n /**\n * Translates the Node along the local X-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the X-axis.\n */\n translateX(distance) {\n return this.translate(xAxis$1, distance);\n }\n\n /**\n * Translates the Node along the local Y-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the Y-axis.\n */\n translateY(distance) {\n return this.translate(yAxis$1, distance);\n }\n\n /**\n * Translates the Node along the local Z-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the Z-axis.\n */\n translateZ(distance) {\n return this.translate(zAxis$1, distance);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Component members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n @private\n */\n get type() {\n return \"Node\";\n }\n\n /**\n * Destroys this Node.\n */\n destroy() {\n super.destroy();\n if (this._parentNode) {\n this._parentNode.removeChild(this);\n }\n if (this._isObject) {\n this.scene._deregisterObject(this);\n if (this._visible) {\n this.scene._objectVisibilityUpdated(this, false, false);\n }\n if (this._xrayed) {\n this.scene._objectXRayedUpdated(this, false, false);\n }\n if (this._selected) {\n this.scene._objectSelectedUpdated(this, false, false);\n }\n if (this._highlighted) {\n this.scene._objectHighlightedUpdated(this, false, false);\n }\n this.scene._objectColorizeUpdated(this, false);\n this.scene._objectOpacityUpdated(this, false);\n if (this.offset.some((v) => v !== 0))\n this.scene._objectOffsetUpdated(this, false);\n }\n if (this._isModel) {\n this.scene._deregisterModel(this);\n }\n if (this._children.length) {\n // Clone the _children before iterating, so our children don't mess us up when calling removeChild().\n const tempChildList = this._children.splice();\n let child;\n for (let i = 0, len = tempChildList.length; i < len; i++) {\n child = tempChildList[i];\n child.destroy();\n }\n }\n this._children = [];\n this._setAABBDirty();\n this.scene._aabbDirty = true;\n }\n\n}\n\n/**\n * @desc Configures the normal rendered appearance of {@link Mesh}es using the non-realistic but GPU-efficient Lambertian flat shading model for calculating reflectance.\n *\n * * Useful for efficiently rendering non-realistic objects for high-detail CAD.\n * * Use {@link PhongMaterial} when you need specular highlights.\n * * Use the physically-based {@link MetallicMaterial} or {@link SpecularMaterial} when you need more realism.\n * * For LambertMaterial, the illumination calculation is performed at each triangle vertex, and the resulting color is interpolated across the face of the triangle. For {@link PhongMaterial}, {@link MetallicMaterial} and\n * {@link SpecularMaterial}, vertex normals are interpolated across the surface of the triangle, and the illumination calculation is performed at each texel.\n *\n * ## Usage\n *\n * [[Run this example](/examples/index.html#materials_LambertMaterial)]\n *\n * In the example below we'll create a {@link Mesh} with a shape defined by a {@link buildTorusGeometry} and normal rendering appearance configured with a LambertMaterial.\n *\n * ```` javascript\n * import {Viewer, Mesh, buildTorusGeometry, ReadableGeometry, LambertMaterial} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({\n * center: [0, 0, 0],\n * radius: 1.5,\n * tube: 0.5,\n * radialSegments: 12,\n * tubeSegments: 8,\n * arc: Math.PI * 2.0\n * }),\n * material: new LambertMaterial(viewer.scene, {\n * ambient: [0.3, 0.3, 0.3],\n * color: [0.5, 0.5, 0.0],\n * alpha: 1.0, // Default\n * lineWidth: 1,\n * pointSize: 1,\n * backfaces: false,\n * frontFace: \"ccw\"\n * })\n * });\n * ````\n *\n * ## LambertMaterial Properties\n *\n * The following table summarizes LambertMaterial properties:\n *\n * | Property | Type | Range | Default Value | Space | Description |\n * |:--------:|:----:|:-----:|:-------------:|:-----:|:-----------:|\n * | {@link LambertMaterial#ambient} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the ambient light reflected by the material. |\n * | {@link LambertMaterial#color} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the diffuse light reflected by the material. |\n * | {@link LambertMaterial#emissive} | Array | [0, 1] for all components | [0,0,0] | linear | The RGB components of the light emitted by the material. |\n * | {@link LambertMaterial#alpha} | Number | [0, 1] | 1 | linear | The transparency of the material surface (0 fully transparent, 1 fully opaque). |\n * | {@link LambertMaterial#lineWidth} | Number | [0..100] | 1 | | Line width in pixels. |\n * | {@link LambertMaterial#pointSize} | Number | [0..100] | 1 | | Point size in pixels. |\n * | {@link LambertMaterial#backfaces} | Boolean | | false | | Whether to render {@link Geometry} backfaces. |\n * | {@link LambertMaterial#frontface} | String | \"ccw\", \"cw\" | \"ccw\" | | The winding order for {@link Geometry} frontfaces - \"cw\" for clockwise, or \"ccw\" for counter-clockwise. |\n *\n */\nclass LambertMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"LambertMaterial\";\n }\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] The LambertMaterial configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {String:Object} [cfg.meta=null] Metadata to attach to this LambertMaterial.\n * @param {Number[]} [cfg.ambient=[1.0, 1.0, 1.0 ]] LambertMaterial ambient color.\n * @param {Number[]} [cfg.color=[ 1.0, 1.0, 1.0 ]] LambertMaterial diffuse color.\n * @param {Number[]} [cfg.emissive=[ 0.0, 0.0, 0.0 ]] LambertMaterial emissive color.\n * @param {Number} [cfg.alpha=1]Scalar in range 0-1 that controls alpha, where 0 is completely transparent and 1 is completely opaque.\n * @param {Number} [cfg.reflectivity=1]Scalar in range 0-1 that controls how much {@link ReflectionMap} is reflected.\n * @param {Number} [cfg.lineWidth=1] Scalar that controls the width of {@link Geometry} lines.\n * @param {Number} [cfg.pointSize=1] Scalar that controls the size of points for {@link Geometry} with {@link Geometry#primitive} set to \"points\".\n * @param {Boolean} [cfg.backfaces=false] Whether to render {@link Geometry} backfaces.\n * @param {Boolean} [cfg.frontface=\"ccw\"] The winding order for {@link Geometry} front faces - \"cw\" for clockwise, or \"ccw\" for counter-clockwise.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"LambertMaterial\",\n ambient: math.vec3([1.0, 1.0, 1.0]),\n color: math.vec3([1.0, 1.0, 1.0]),\n emissive: math.vec3([0.0, 0.0, 0.0]),\n alpha: null,\n alphaMode: 0, // 2 (\"blend\") when transparent, so renderer knows when to add to transparency bin\n lineWidth: null,\n pointSize: null,\n backfaces: null,\n frontface: null, // Boolean for speed; true == \"ccw\", false == \"cw\"\n hash: \"/lam;\"\n });\n\n this.ambient = cfg.ambient;\n this.color = cfg.color;\n this.emissive = cfg.emissive;\n this.alpha = cfg.alpha;\n this.lineWidth = cfg.lineWidth;\n this.pointSize = cfg.pointSize;\n this.backfaces = cfg.backfaces;\n this.frontface = cfg.frontface;\n }\n\n /**\n * Sets the LambertMaterial's ambient color.\n *\n * Default value is ````[0.3, 0.3, 0.3]````.\n *\n * @type {Number[]}\n */\n set ambient(value) {\n let ambient = this._state.ambient;\n if (!ambient) {\n ambient = this._state.ambient = new Float32Array(3);\n } else if (value && ambient[0] === value[0] && ambient[1] === value[1] && ambient[2] === value[2]) {\n return;\n }\n if (value) {\n ambient[0] = value[0];\n ambient[1] = value[1];\n ambient[2] = value[2];\n } else {\n ambient[0] = .2;\n ambient[1] = .2;\n ambient[2] = .2;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the LambertMaterial's ambient color.\n *\n * Default value is ````[0.3, 0.3, 0.3]````.\n *\n * @type {Number[]}\n */\n get ambient() {\n return this._state.ambient;\n }\n\n /**\n * Sets the LambertMaterial's diffuse color.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @type {Number[]}\n */\n set color(value) {\n let color = this._state.color;\n if (!color) {\n color = this._state.color = new Float32Array(3);\n } else if (value && color[0] === value[0] && color[1] === value[1] && color[2] === value[2]) {\n return;\n }\n if (value) {\n color[0] = value[0];\n color[1] = value[1];\n color[2] = value[2];\n } else {\n color[0] = 1;\n color[1] = 1;\n color[2] = 1;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the LambertMaterial's diffuse color.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @type {Number[]}\n */\n get color() {\n return this._state.color;\n }\n\n /**\n * Sets the LambertMaterial's emissive color.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @type {Number[]}\n */\n set emissive(value) {\n let emissive = this._state.emissive;\n if (!emissive) {\n emissive = this._state.emissive = new Float32Array(3);\n } else if (value && emissive[0] === value[0] && emissive[1] === value[1] && emissive[2] === value[2]) {\n return;\n }\n if (value) {\n emissive[0] = value[0];\n emissive[1] = value[1];\n emissive[2] = value[2];\n } else {\n emissive[0] = 0;\n emissive[1] = 0;\n emissive[2] = 0;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the LambertMaterial's emissive color.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @type {Number[]}\n */\n get emissive() {\n return this._state.emissive;\n }\n\n /**\n * Sets factor in the range ````[0..1]```` indicating how transparent the LambertMaterial is.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default value is ````1.0````\n *\n * @type {Number}\n */\n set alpha(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.alpha === value) {\n return;\n }\n this._state.alpha = value;\n this._state.alphaMode = value < 1.0 ? 2 /* blend */ : 0;\n /* opaque */\n this.glRedraw();\n }\n\n /**\n * Gets factor in the range ````[0..1]```` indicating how transparent the LambertMaterial is.\n *\n * A value of ````0.0```` indicates fully transparent, ````1.0```` is fully opaque.\n *\n * Default value is ````1.0````\n *\n * @type {Number}\n */\n get alpha() {\n return this._state.alpha;\n }\n\n /**\n * Sets the LambertMaterial's line width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set lineWidth(value) {\n this._state.lineWidth = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the LambertMaterial's line width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n get lineWidth() {\n return this._state.lineWidth;\n }\n\n /**\n * Sets the LambertMaterial's point size.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set pointSize(value) {\n this._state.pointSize = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the LambertMaterial's point size.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n get pointSize() {\n return this._state.pointSize;\n }\n\n /**\n * Sets whether backfaces are visible on attached {@link Mesh}es.\n *\n * @type {Boolean}\n */\n set backfaces(value) {\n value = !!value;\n if (this._state.backfaces === value) {\n return;\n }\n this._state.backfaces = value;\n this.glRedraw();\n }\n\n /**\n * Gets whether backfaces are visible on attached {@link Mesh}es.\n *\n * @type {Boolean}\n */\n get backfaces() {\n return this._state.backfaces;\n }\n\n /**\n * Sets the winding direction of front faces of {@link Geometry} of attached {@link Mesh}es.\n *\n * Default value is ````\"ccw\"````.\n *\n * @type {String}\n */\n set frontface(value) {\n value = value !== \"cw\";\n if (this._state.frontface === value) {\n return;\n }\n this._state.frontface = value;\n this.glRedraw();\n }\n\n /**\n * Gets the winding direction of front faces of {@link Geometry} of attached {@link Mesh}es.\n *\n * Default value is ````\"ccw\"````.\n *\n * @type {String}\n */\n get frontface() {\n return this._state.frontface ? \"ccw\" : \"cw\";\n }\n\n _getState() {\n return this._state;\n }\n\n /**\n * Destroys this LambertMaterial.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\nconst modes = {\"opaque\": 0, \"mask\": 1, \"blend\": 2};\nconst modeNames = [\"opaque\", \"mask\", \"blend\"];\n\n/**\n * @desc Configures the normal rendered appearance of {@link Mesh}es using the physically-accurate *metallic-roughness* shading model.\n *\n * * Useful for conductive materials, such as metal, but also appropriate for insulators.\n * * {@link SpecularMaterial} is best for insulators, such as wood, ceramics and plastic.\n * * {@link PhongMaterial} is appropriate for non-realistic objects.\n * * {@link LambertMaterial} is appropriate for high-detail models that need to render as efficiently as possible.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with {@link MetallicMaterial} and {@link ReadableGeometry} loaded from OBJ.\n *\n * Note that in this example we're providing separate {@link Texture} for the {@link MetallicMaterial#metallic} and {@link MetallicMaterial#roughness}\n * channels, which allows us a little creative flexibility. Then, in the next example further down, we'll combine those channels\n * within the same {@link Texture} for efficiency.\n *\n * [[Run this example](/examples/index.html#materials_MetallicMaterial)]\n *\n * ````javascript\n * import {Viewer, Mesh, loadOBJGeometry, ReadableGeometry, MetallicMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0.57, 1.37, 1.14];\n * viewer.scene.camera.look = [0.04, 0.58, 0.00];\n * viewer.scene.camera.up = [-0.22, 0.84, -0.48];\n *\n * loadOBJGeometry(viewer.scene, {\n * src: \"models/obj/fireHydrant/FireHydrantMesh.obj\"\n * })\n * .then(function (geometry) {\n *\n * // Success\n *\n * new Mesh(viewer.scene, {\n *\n * geometry: new ReadableGeometry(viewer.scene, geometry),\n *\n * material: new MetallicMaterial(viewer.scene, {\n *\n * baseColor: [1, 1, 1],\n * metallic: 1.0,\n * roughness: 1.0,\n *\n * baseColorMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Base_Color.png\",\n * encoding: \"sRGB\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Normal_OpenGL.png\"\n * }),\n * roughnessMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Roughness.png\"\n * }),\n * metallicMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Metallic.png\"\n * }),\n * occlusionMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Mixed_AO.png\"\n * }),\n *\n * specularF0: 0.7\n * })\n * });\n * }, function () {\n * // Error\n * });\n * ````\n *\n * ## Background Theory\n *\n * For an introduction to physically-based rendering (PBR) concepts, try these articles:\n *\n * * Joe Wilson's [Basic Theory of Physically-Based Rendering](https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/)\n * * Jeff Russel's [Physically-based Rendering, and you can too!](https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/)\n * * Sebastien Legarde's [Adapting a physically-based shading model](http://seblagarde.wordpress.com/tag/physically-based-rendering/)\n *\n * ## MetallicMaterial Properties\n *\n * The following table summarizes MetallicMaterial properties:\n *\n * | Property | Type | Range | Default Value | Space | Description |\n * |:--------:|:----:|:-----:|:-------------:|:-----:|:-----------:|\n * | {@link MetallicMaterial#baseColor} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the base color of the material. |\n * | {@link MetallicMaterial#metallic} | Number | [0, 1] | 1 | linear | The metallic-ness the material (1 for metals, 0 for non-metals). |\n * | {@link MetallicMaterial#roughness} | Number | [0, 1] | 1 | linear | The roughness of the material surface. |\n * | {@link MetallicMaterial#specularF0} | Number | [0, 1] | 1 | linear | The specular Fresnel of the material surface. |\n * | {@link MetallicMaterial#emissive} | Array | [0, 1] for all components | [0,0,0] | linear | The RGB components of the emissive color of the material. |\n * | {@link MetallicMaterial#alpha} | Number | [0, 1] | 1 | linear | The transparency of the material surface (0 fully transparent, 1 fully opaque). |\n * | {@link MetallicMaterial#baseColorMap} | {@link Texture} | | null | sRGB | Texture RGB components multiplying by {@link MetallicMaterial#baseColor}. If the fourth component (A) is present, it multiplies by {@link MetallicMaterial#alpha}. |\n * | {@link MetallicMaterial#metallicMap} | {@link Texture} | | null | linear | Texture with first component multiplying by {@link MetallicMaterial#metallic}. |\n * | {@link MetallicMaterial#roughnessMap} | {@link Texture} | | null | linear | Texture with first component multiplying by {@link MetallicMaterial#roughness}. |\n * | {@link MetallicMaterial#metallicRoughnessMap} | {@link Texture} | | null | linear | Texture with first component multiplying by {@link MetallicMaterial#metallic} and second component multiplying by {@link MetallicMaterial#roughness}. |\n * | {@link MetallicMaterial#emissiveMap} | {@link Texture} | | null | linear | Texture with RGB components multiplying by {@link MetallicMaterial#emissive}. |\n * | {@link MetallicMaterial#alphaMap} | {@link Texture} | | null | linear | Texture with first component multiplying by {@link MetallicMaterial#alpha}. |\n * | {@link MetallicMaterial#occlusionMap} | {@link Texture} | | null | linear | Ambient occlusion texture multiplying by surface's reflected diffuse and specular light. |\n * | {@link MetallicMaterial#normalMap} | {@link Texture} | | null | linear | Tangent-space normal map. |\n * | {@link MetallicMaterial#alphaMode} | String | \"opaque\", \"blend\", \"mask\" | \"blend\" | | Alpha blend mode. |\n * | {@link MetallicMaterial#alphaCutoff} | Number | [0..1] | 0.5 | | Alpha cutoff value. |\n * | {@link MetallicMaterial#backfaces} | Boolean | | false | | Whether to render {@link ReadableGeometry} backfaces. |\n * | {@link MetallicMaterial#frontface} | String | \"ccw\", \"cw\" | \"ccw\" | | The winding order for {@link ReadableGeometry} frontfaces - \"cw\" for clockwise, or \"ccw\" for counter-clockwise. |\n *\n *\n * ## Combining Channels Within the Same Textures\n *\n * In the previous example we provided separate {@link Texture} for the {@link MetallicMaterial#metallic} and\n * {@link MetallicMaterial#roughness} channels, but we can combine those channels into the same {@link Texture} to\n * reduce download time, memory footprint and rendering time (and also for glTF compatibility).\n *\n * Here's the {@link Mesh} again, with our MetallicMaterial with those channels combined in the {@link MetallicMaterial#metallicRoughnessMap}\n * {@link Texture}, where the *R* component multiplies by {@link MetallicMaterial#metallic} and *G* multiplies\n * by {@link MetallicMaterial#roughness}.\n *\n * ````javascript\n * new Mesh(viewer.scene, {\n *\n * geometry: geometry,\n *\n * material: new MetallicMaterial(viewer.scene, {\n *\n * baseColor: [1, 1, 1],\n * metallic: 1.0,\n * roughness: 1.0,\n *\n * baseColorMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Base_Color.png\",\n * encoding: \"sRGB\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Normal_OpenGL.png\"\n * }),\n * metallicRoughnessMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_MetallicRoughness.png\"\n * }),\n * metallicRoughnessMap : new Texture(viewer.scene, { // <<----------- Added\n * src: \"models/obj/fireHydrant/fire_hydrant_MetallicRoughness.png\" // R component multiplies by metallic\n * }), // G component multiplies by roughness\n * occlusionMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Mixed_AO.png\"\n * }),\n *\n * specularF0: 0.7\n * })\n * ````\n *\n * Although not shown in this example, we can also texture {@link MetallicMaterial#alpha} with the *A* component of\n * {@link MetallicMaterial#baseColorMap}'s {@link Texture}, if required.\n *\n * ## Alpha Blending\n *\n * Let's make our {@link Mesh} transparent.\n *\n * We'll update the {@link MetallicMaterial#alpha} and {@link MetallicMaterial#alphaMode}, causing it to blend 50%\n * with the background:\n *\n * ````javascript\n * hydrant.material.alpha = 0.5;\n * hydrant.material.alphaMode = \"blend\";\n * ````\n *\n * ## Alpha Masking\n *\n * Let's apply an alpha mask to our {@link Mesh}.\n *\n * We'll configure an {@link MetallicMaterial#alphaMap} to multiply by {@link MetallicMaterial#alpha},\n * with {@link MetallicMaterial#alphaMode} and {@link MetallicMaterial#alphaCutoff} to treat it as an alpha mask:\n *\n * ````javascript\n * new Mesh(viewer.scene, {\n *\n * geometry: geometry,\n *\n * material: new MetallicMaterial(viewer.scene, {\n *\n * baseColor: [1, 1, 1],\n * metallic: 1.0,\n * roughness: 1.0,\n * alpha: 1.0,\n * alphaMode : \"mask\", // <<---------------- Added\n * alphaCutoff : 0.2, // <<---------------- Added\n *\n * alphaMap : new Texture(viewer.scene{ // <<---------------- Added\n * src: \"textures/alphaMap.jpg\"\n * }),\n * baseColorMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Base_Color.png\",\n * encoding: \"sRGB\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Normal_OpenGL.png\"\n * }),\n * metallicRoughnessMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_MetallicRoughness.png\"\n * }),\n * metallicRoughnessMap : new Texture(viewer.scene, { // <<----------- Added\n * src: \"models/obj/fireHydrant/fire_hydrant_MetallicRoughness.png\" // R component multiplies by metallic\n * }), // G component multiplies by roughness\n * occlusionMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Mixed_AO.png\"\n * }),\n *\n * specularF0: 0.7\n * })\n * ````\n */\nclass MetallicMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"MetallicMaterial\";\n }\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this MetallicMaterial as well.\n * @param {*} [cfg] The MetallicMaterial configuration.\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.baseColor=[1,1,1]] RGB diffuse color of this MetallicMaterial. Multiplies by the RGB components of {@link MetallicMaterial#baseColorMap}.\n * @param {Number} [cfg.metallic=1.0] Factor in the range ````[0..1]```` indicating how metallic this MetallicMaterial is. ````1```` is metal, ````0```` is non-metal. Multiplies by the *R* component of {@link MetallicMaterial#metallicMap} and the *A* component of {@link MetallicMaterial#metallicRoughnessMap}.\n * @param {Number} [cfg.roughness=1.0] Factor in the range ````[0..1]```` indicating the roughness of this MetallicMaterial. ````0```` is fully smooth, ````1```` is fully rough. Multiplies by the *R* component of {@link MetallicMaterial#roughnessMap}.\n * @param {Number} [cfg.specularF0=0.0] Factor in the range ````[0..1]```` indicating specular Fresnel.\n * @param {Number[]} [cfg.emissive=[0,0,0]] RGB emissive color of this MetallicMaterial. Multiplies by the RGB components of {@link MetallicMaterial#emissiveMap}.\n * @param {Number} [cfg.alpha=1.0] Factor in the range ````[0..1]```` indicating the alpha of this MetallicMaterial. Multiplies by the *R* component of {@link MetallicMaterial#alphaMap} and the *A* component, if present, of {@link MetallicMaterial#baseColorMap}. The value of {@link MetallicMaterial#alphaMode} indicates how alpha is interpreted when rendering.\n * @param {Texture} [cfg.baseColorMap=undefined] RGBA {@link Texture} containing the diffuse color of this MetallicMaterial, with optional *A* component for alpha. The RGB components multiply by the {@link MetallicMaterial#baseColor} property, while the *A* component, if present, multiplies by the {@link MetallicMaterial#alpha} property.\n * @param {Texture} [cfg.alphaMap=undefined] RGB {@link Texture} containing this MetallicMaterial's alpha in its *R* component. The *R* component multiplies by the {@link MetallicMaterial#alpha} property. Must be within the same {@link Scene} as this MetallicMaterial.\n * @param {Texture} [cfg.metallicMap=undefined] RGB {@link Texture} containing this MetallicMaterial's metallic factor in its *R* component. The *R* component multiplies by the {@link MetallicMaterial#metallic} property. Must be within the same {@link Scene} as this MetallicMaterial.\n * @param {Texture} [cfg.roughnessMap=undefined] RGB {@link Texture} containing this MetallicMaterial's roughness factor in its *R* component. The *R* component multiplies by the {@link MetallicMaterial#roughness} property. Must be within the same {@link Scene} as this MetallicMaterial.\n * @param {Texture} [cfg.metallicRoughnessMap=undefined] RGB {@link Texture} containing this MetallicMaterial's metalness in its *R* component and roughness in its *G* component. Its *R* component multiplies by the {@link MetallicMaterial#metallic} property, while its *G* component multiplies by the {@link MetallicMaterial#roughness} property. Must be within the same {@link Scene} as this MetallicMaterial.\n * @param {Texture} [cfg.emissiveMap=undefined] RGB {@link Texture} containing the emissive color of this MetallicMaterial. Multiplies by the {@link MetallicMaterial#emissive} property. Must be within the same {@link Scene} as this MetallicMaterial.\n * @param {Texture} [cfg.occlusionMap=undefined] RGB ambient occlusion {@link Texture}. Within shaders, multiplies by the specular and diffuse light reflected by surfaces. Must be within the same {@link Scene} as this MetallicMaterial.\n * @param {Texture} [cfg.normalMap=undefined] RGB tangent-space normal {@link Texture}. Must be within the same {@link Scene} as this MetallicMaterial.\n * @param {String} [cfg.alphaMode=\"opaque\"] The alpha blend mode, which specifies how alpha is to be interpreted. Accepted values are \"opaque\", \"blend\" and \"mask\". See the {@link MetallicMaterial#alphaMode} property for more info.\n * @param {Number} [cfg.alphaCutoff=0.5] The alpha cutoff value. See the {@link MetallicMaterial#alphaCutoff} property for more info.\n * @param {Boolean} [cfg.backfaces=false] Whether to render {@link ReadableGeometry} backfaces.\n * @param {Boolean} [cfg.frontface=\"ccw\"] The winding order for {@link ReadableGeometry} front faces - ````\"cw\"```` for clockwise, or ````\"ccw\"```` for counter-clockwise.\n * @param {Number} [cfg.lineWidth=1] Scalar that controls the width of lines for {@link ReadableGeometry} with {@link ReadableGeometry#primitive} set to \"lines\".\n * @param {Number} [cfg.pointSize=1] Scalar that controls the size of points for {@link ReadableGeometry} with {@link ReadableGeometry#primitive} set to \"points\".\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"MetallicMaterial\",\n baseColor: math.vec4([1.0, 1.0, 1.0]),\n emissive: math.vec4([0.0, 0.0, 0.0]),\n metallic: null,\n roughness: null,\n specularF0: null,\n alpha: null,\n alphaMode: null, // \"opaque\"\n alphaCutoff: null,\n lineWidth: null,\n pointSize: null,\n backfaces: null,\n frontface: null, // Boolean for speed; true == \"ccw\", false == \"cw\"\n hash: null\n });\n\n this.baseColor = cfg.baseColor;\n this.metallic = cfg.metallic;\n this.roughness = cfg.roughness;\n this.specularF0 = cfg.specularF0;\n this.emissive = cfg.emissive;\n this.alpha = cfg.alpha;\n\n if (cfg.baseColorMap) {\n this._baseColorMap = this._checkComponent(\"Texture\", cfg.baseColorMap);\n }\n if (cfg.metallicMap) {\n this._metallicMap = this._checkComponent(\"Texture\", cfg.metallicMap);\n\n }\n if (cfg.roughnessMap) {\n this._roughnessMap = this._checkComponent(\"Texture\", cfg.roughnessMap);\n }\n if (cfg.metallicRoughnessMap) {\n this._metallicRoughnessMap = this._checkComponent(\"Texture\", cfg.metallicRoughnessMap);\n }\n if (cfg.emissiveMap) {\n this._emissiveMap = this._checkComponent(\"Texture\", cfg.emissiveMap);\n }\n if (cfg.occlusionMap) {\n this._occlusionMap = this._checkComponent(\"Texture\", cfg.occlusionMap);\n }\n if (cfg.alphaMap) {\n this._alphaMap = this._checkComponent(\"Texture\", cfg.alphaMap);\n }\n if (cfg.normalMap) {\n this._normalMap = this._checkComponent(\"Texture\", cfg.normalMap);\n }\n\n this.alphaMode = cfg.alphaMode;\n this.alphaCutoff = cfg.alphaCutoff;\n this.backfaces = cfg.backfaces;\n this.frontface = cfg.frontface;\n this.lineWidth = cfg.lineWidth;\n this.pointSize = cfg.pointSize;\n\n this._makeHash();\n }\n\n _makeHash() {\n const state = this._state;\n const hash = [\"/met\"];\n if (this._baseColorMap) {\n hash.push(\"/bm\");\n if (this._baseColorMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n hash.push(\"/\" + this._baseColorMap._state.encoding);\n }\n if (this._metallicMap) {\n hash.push(\"/mm\");\n if (this._metallicMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._roughnessMap) {\n hash.push(\"/rm\");\n if (this._roughnessMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._metallicRoughnessMap) {\n hash.push(\"/mrm\");\n if (this._metallicRoughnessMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._emissiveMap) {\n hash.push(\"/em\");\n if (this._emissiveMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._occlusionMap) {\n hash.push(\"/ocm\");\n if (this._occlusionMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._alphaMap) {\n hash.push(\"/am\");\n if (this._alphaMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._normalMap) {\n hash.push(\"/nm\");\n if (this._normalMap._state.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n hash.push(\";\");\n state.hash = hash.join(\"\");\n }\n\n\n /**\n * Sets the RGB diffuse color.\n *\n * Multiplies by the RGB components of {@link MetallicMaterial#baseColorMap}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n * @type {Number[]}\n */\n set baseColor(value) {\n let baseColor = this._state.baseColor;\n if (!baseColor) {\n baseColor = this._state.baseColor = new Float32Array(3);\n } else if (value && baseColor[0] === value[0] && baseColor[1] === value[1] && baseColor[2] === value[2]) {\n return;\n }\n if (value) {\n baseColor[0] = value[0];\n baseColor[1] = value[1];\n baseColor[2] = value[2];\n } else {\n baseColor[0] = 1;\n baseColor[1] = 1;\n baseColor[2] = 1;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the RGB diffuse color.\n *\n * Multiplies by the RGB components of {@link MetallicMaterial#baseColorMap}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n * @type {Number[]}\n */\n get baseColor() {\n return this._state.baseColor;\n }\n\n\n /**\n * Gets the RGB {@link Texture} containing the diffuse color of this MetallicMaterial, with optional *A* component for alpha.\n *\n * The RGB components multiply by {@link MetallicMaterial#baseColor}, while the *A* component, if present, multiplies by {@link MetallicMaterial#alpha}.\n *\n * @type {Texture}\n */\n get baseColorMap() {\n return this._baseColorMap;\n }\n\n /**\n * Sets the metallic factor.\n *\n * This is in the range ````[0..1]```` and indicates how metallic this MetallicMaterial is.\n *\n * ````1```` is metal, ````0```` is non-metal.\n *\n * Multiplies by the *R* component of {@link MetallicMaterial#metallicMap} and the *A* component of {@link MetallicMaterial#metallicRoughnessMap}.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set metallic(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.metallic === value) {\n return;\n }\n this._state.metallic = value;\n this.glRedraw();\n }\n\n /**\n * Gets the metallic factor.\n *\n * @type {Number}\n */\n get metallic() {\n return this._state.metallic;\n }\n\n /**\n * Gets the RGB {@link Texture} containing this MetallicMaterial's metallic factor in its *R* component.\n *\n * The *R* component multiplies by {@link MetallicMaterial#metallic}.\n *\n * @type {Texture}\n */\n get metallicMap() {\n return this._attached.metallicMap;\n }\n\n /**\n * Sets the roughness factor.\n *\n * This factor is in the range ````[0..1]````, where ````0```` is fully smooth,````1```` is fully rough.\n *\n * Multiplies by the *R* component of {@link MetallicMaterial#roughnessMap}.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set roughness(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.roughness === value) {\n return;\n }\n this._state.roughness = value;\n this.glRedraw();\n }\n\n /**\n * Gets the roughness factor.\n *\n * @type {Number}\n */\n get roughness() {\n return this._state.roughness;\n }\n\n /**\n * Gets the RGB {@link Texture} containing this MetallicMaterial's roughness factor in its *R* component.\n *\n * The *R* component multiplies by {@link MetallicMaterial#roughness}.\n *\n * @type {Texture}\n */\n get roughnessMap() {\n return this._attached.roughnessMap;\n }\n\n /**\n * Gets the RGB {@link Texture} containing this MetallicMaterial's metalness in its *R* component and roughness in its *G* component.\n *\n * Its *B* component multiplies by the {@link MetallicMaterial#metallic} property, while its *G* component multiplies by the {@link MetallicMaterial#roughness} property.\n *\n * @type {Texture}\n */\n get metallicRoughnessMap() {\n return this._attached.metallicRoughnessMap;\n }\n\n /**\n * Sets the factor in the range [0..1] indicating specular Fresnel value.\n *\n * Default value is ````0.0````.\n *\n * @type {Number}\n */\n set specularF0(value) {\n value = (value !== undefined && value !== null) ? value : 0.0;\n if (this._state.specularF0 === value) {\n return;\n }\n this._state.specularF0 = value;\n this.glRedraw();\n }\n\n /**\n * Gets the factor in the range [0..1] indicating specular Fresnel value.\n *\n * @type {Number}\n */\n get specularF0() {\n return this._state.specularF0;\n }\n\n /**\n * Sets the RGB emissive color.\n *\n * Multiplies by {@link MetallicMaterial#emissiveMap}.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @type {Number[]}\n */\n set emissive(value) {\n let emissive = this._state.emissive;\n if (!emissive) {\n emissive = this._state.emissive = new Float32Array(3);\n } else if (value && emissive[0] === value[0] && emissive[1] === value[1] && emissive[2] === value[2]) {\n return;\n }\n if (value) {\n emissive[0] = value[0];\n emissive[1] = value[1];\n emissive[2] = value[2];\n } else {\n emissive[0] = 0;\n emissive[1] = 0;\n emissive[2] = 0;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the RGB emissive color.\n *\n * @type {Number[]}\n */\n get emissive() {\n return this._state.emissive;\n }\n\n /**\n * Gets the RGB emissive map.\n *\n * Multiplies by {@link MetallicMaterial#emissive}.\n *\n * @type {Texture}\n */\n get emissiveMap() {\n return this._attached.emissiveMap;\n }\n\n /**\n * Gets the RGB ambient occlusion map.\n *\n * Multiplies by the specular and diffuse light reflected by surfaces.\n *\n * @type {Texture}\n */\n get occlusionMap() {\n return this._attached.occlusionMap;\n }\n\n /**\n * Sets factor in the range ````[0..1]```` that indicates the alpha value.\n *\n * Multiplies by the *R* component of {@link MetallicMaterial#alphaMap} and the *A* component, if present, of {@link MetallicMaterial#baseColorMap}.\n *\n * The value of {@link MetallicMaterial#alphaMode} indicates how alpha is interpreted when rendering.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set alpha(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.alpha === value) {\n return;\n }\n this._state.alpha = value;\n this.glRedraw();\n }\n\n /**\n * Gets factor in the range ````[0..1]```` that indicates the alpha value.\n *\n * @type {Number}\n */\n get alpha() {\n return this._state.alpha;\n }\n\n /**\n * Gets the RGB {@link Texture} containing this MetallicMaterial's alpha in its *R* component.\n *\n * The *R* component multiplies by the {@link MetallicMaterial#alpha} property.\n *\n * @type {Texture}\n */\n get alphaMap() {\n return this._attached.alphaMap;\n }\n\n /**\n * Gets the RGB tangent-space normal map {@link Texture}.\n *\n * @type {Texture}\n */\n get normalMap() {\n return this._attached.normalMap;\n }\n\n /**\n * Sets the alpha rendering mode.\n *\n * This specifies how alpha is interpreted. Alpha is the combined result of the {@link MetallicMaterial#alpha} and {@link MetallicMaterial#alphaMap} properties.\n *\n * Accepted values are:\n *\n * * \"opaque\" - The alpha value is ignored and the rendered output is fully opaque (default).\n * * \"mask\" - The rendered output is either fully opaque or fully transparent depending on the alpha and {@link MetallicMaterial#alphaCutoff}.\n * * \"blend\" - The alpha value is used to composite the source and destination areas. The rendered output is combined with the background using the normal painting operation (i.e. the Porter and Duff over operator).\n *\n * @type {String}\n */\n set alphaMode(alphaMode) {\n alphaMode = alphaMode || \"opaque\";\n let value = modes[alphaMode];\n if (value === undefined) {\n this.error(\"Unsupported value for 'alphaMode': \" + alphaMode + \" defaulting to 'opaque'\");\n value = \"opaque\";\n }\n if (this._state.alphaMode === value) {\n return;\n }\n this._state.alphaMode = value;\n this.glRedraw();\n }\n\n /**\n * Gets the alpha rendering mode.\n *\n * @type {String}\n */\n get alphaMode() {\n return modeNames[this._state.alphaMode];\n }\n\n /**\n * Sets the alpha cutoff value.\n *\n * Specifies the cutoff threshold when {@link MetallicMaterial#alphaMode} equals \"mask\". If the alpha is greater than or equal to this value then it is rendered as fully opaque, otherwise, it is rendered as fully transparent. A value greater than 1.0 will render the entire\n * material as fully transparent. This value is ignored for other modes.\n *\n * Alpha is the combined result of the {@link MetallicMaterial#alpha} and {@link MetallicMaterial#alphaMap} properties.\n *\n * Default value is ````0.5````.\n *\n * @type {Number}\n */\n set alphaCutoff(alphaCutoff) {\n if (alphaCutoff === null || alphaCutoff === undefined) {\n alphaCutoff = 0.5;\n }\n if (this._state.alphaCutoff === alphaCutoff) {\n return;\n }\n this._state.alphaCutoff = alphaCutoff;\n }\n\n /**\n * Gets the alpha cutoff value.\n *\n * @type {Number}\n */\n get alphaCutoff() {\n return this._state.alphaCutoff;\n }\n\n /**\n * Sets whether backfaces are visible on attached {@link Mesh}es.\n *\n * The backfaces will belong to {@link ReadableGeometry} compoents that are also attached to the {@link Mesh}es.\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n set backfaces(value) {\n value = !!value;\n if (this._state.backfaces === value) {\n return;\n }\n this._state.backfaces = value;\n this.glRedraw();\n }\n\n /**\n * Gets whether backfaces are visible on attached {@link Mesh}es.\n *\n * @type {Boolean}\n */\n get backfaces() {\n return this._state.backfaces;\n }\n\n /**\n * Sets the winding direction of front faces of {@link Geometry} of attached {@link Mesh}es.\n *\n * Default value is ````\"ccw\"````.\n *\n * @type {String}\n */\n set frontface(value) {\n value = value !== \"cw\";\n if (this._state.frontface === value) {\n return;\n }\n this._state.frontface = value;\n this.glRedraw();\n }\n\n /**\n * Gets the winding direction of front faces of {@link Geometry} of attached {@link Mesh}es.\n*\n * @type {String}\n */\n get frontface() {\n return this._state.frontface ? \"ccw\" : \"cw\";\n }\n\n /**\n * Sets the MetallicMaterial's line width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set lineWidth(value) {\n this._state.lineWidth = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the MetallicMaterial's line width.\n *\n * @type {Number}\n */\n get lineWidth() {\n return this._state.lineWidth;\n }\n\n /**\n * Sets the MetallicMaterial's point size.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set pointSize(value) {\n this._state.pointSize = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the MetallicMaterial's point size.\n *\n * @type {Number}\n */\n get pointSize() {\n return this._state.pointSize;\n }\n\n /**\n * Destroys this MetallicMaterial.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\nconst alphaModes = {\"opaque\": 0, \"mask\": 1, \"blend\": 2};\nconst alphaModeNames = [\"opaque\", \"mask\", \"blend\"];\n\n/**\n * @desc Configures the normal rendered appearance of {@link Mesh}es using the physically-accurate *specular-glossiness* shading model.\n *\n * * Useful for insulators, such as wood, ceramics and plastic.\n * * {@link MetallicMaterial} is best for conductive materials, such as metal.\n * * {@link PhongMaterial} is appropriate for non-realistic objects.\n * * {@link LambertMaterial} is appropriate for high-detail models that need to render as efficiently as possible.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with a {@link buildTorusGeometry} and a SpecularMaterial.\n *\n * Note that in this example we're providing separate {@link Texture} for the {@link SpecularMaterial#specular} and {@link SpecularMaterial#glossiness}\n * channels, which allows us a little creative flexibility. Then, in the next example further down, we'll combine those channels\n * within the same {@link Texture} for efficiency.\n *\n * ````javascript\n * import {Viewer, Mesh, buildTorusGeometry, SpecularMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({ canvasId: \"myCanvas\" });\n *\n * const myMesh = new Mesh(viewer.scene,{\n *\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry()),\n *\n * material: new SpecularMaterial(viewer.scene,{\n *\n * // Channels with default values, just to show them\n *\n * diffuse: [1.0, 1.0, 1.0],\n * specular: [1.0, 1.0, 1.0],\n * glossiness: 1.0,\n * emissive: [0.0, 0.0, 0.0]\n * alpha: 1.0,\n *\n * // Textures to multiply some of the channels\n *\n * diffuseMap: new Texture(viewer.scene, { // RGB components multiply by diffuse\n * src: \"textures/diffuse.jpg\"\n * }),\n * specularMap: new Texture(viewer.scene, { // RGB component multiplies by specular\n * src: \"textures/specular.jpg\"\n * }),\n * glossinessMap: new Texture(viewer.scene, { // R component multiplies by glossiness\n * src: \"textures/glossiness.jpg\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"textures/normalMap.jpg\"\n * })\n * })\n * });\n * ````\n *\n * ## Combining Channels Within the Same Textures\n *\n * In the previous example we provided separate {@link Texture} for the {@link SpecularMaterial#specular} and\n * {@link SpecularMaterial#glossiness} channels, but we can combine those channels into the same {@link Texture} to reduce\n * download time, memory footprint and rendering time (and also for glTF compatibility).\n *\n * Here's our SpecularMaterial again with those channels combined in the {@link SpecularMaterial#specularGlossinessMap}\n * {@link Texture}, where the *RGB* component multiplies by {@link SpecularMaterial#specular} and *A* multiplies by {@link SpecularMaterial#glossiness}.\n *\n * ````javascript\n * const myMesh = new Mesh(viewer.scene,{\n *\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry()),\n *\n * material: new SpecularMaterial(viewer.scene,{\n *\n * // Channels with default values, just to show them\n *\n * diffuse: [1.0, 1.0, 1.0],\n * specular: [1.0, 1.0, 1.0],\n * glossiness: 1.0,\n * emissive: [0.0, 0.0, 0.0]\n * alpha: 1.0,\n *\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse.jpg\"\n * }),\n * specularGlossinessMap: new Texture(viewer.scene, { // RGB multiplies by specular, A by glossiness\n * src: \"textures/specularGlossiness.jpg\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"textures/normalMap.jpg\"\n * })\n * })\n * });\n * ````\n *\n * Although not shown in this example, we can also texture {@link SpecularMaterial#alpha} with\n * the *A* component of {@link SpecularMaterial#diffuseMap}'s {@link Texture}, if required.\n *\n * ## Alpha Blending\n *\n * Let's make our {@link Mesh} transparent. We'll redefine {@link SpecularMaterial#alpha}\n * and {@link SpecularMaterial#alphaMode}, causing it to blend 50% with the background:\n *\n * ````javascript\n * const myMesh = new Mesh(viewer.scene,{\n *\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry()),\n *\n * material: new SpecularMaterial(viewer.scene,{\n *\n * // Channels with default values, just to show them\n *\n * diffuse: [1.0, 1.0, 1.0],\n * specular: [1.0, 1.0, 1.0],\n * glossiness: 1.0,\n * emissive: [0.0, 0.0, 0.0]\n * alpha: 0.5, // <<----------- Changed\n * alphaMode: \"blend\", // <<----------- Added\n *\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse.jpg\"\n * }),\n * specularGlossinessMap: new Texture(viewer.scene, { // RGB multiplies by specular, A by glossiness\n * src: \"textures/specularGlossiness.jpg\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"textures/normalMap.jpg\"\n * })\n * })\n * });\n * ````\n *\n * ## Alpha Masking\n *\n * Now let's make holes in our {@link Mesh}. We'll give its SpecularMaterial an {@link SpecularMaterial#alphaMap}\n * and configure {@link SpecularMaterial#alpha}, {@link SpecularMaterial#alphaMode},\n * and {@link SpecularMaterial#alphaCutoff} to treat it as an alpha mask:\n *\n * ````javascript\n * const myMesh = new Mesh(viewer.scene,{\n *\n * geometry: buildTorusGeometry(viewer.scene, ReadableGeometry, {}),\n *\n * material: new SpecularMaterial(viewer.scene, {\n *\n * // Channels with default values, just to show them\n *\n * diffuse: [1.0, 1.0, 1.0],\n * specular: [1.0, 1.0, 1.0],\n * glossiness: 1.0,\n * emissive: [0.0, 0.0, 0.0]\n * alpha: 1.0, // <<----------- Changed\n * alphaMode: \"mask\", // <<----------- Changed\n * alphaCutoff: 0.2, // <<----------- Added\n *\n * alphaMap: new Texture(viewer.scene, { // <<---------- Added\n * src: \"textures/diffuse/crossGridColorMap.jpg\"\n * }),\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse.jpg\"\n * }),\n * specularGlossinessMap: new Texture(viewer.scene, { // RGB multiplies by specular, A by glossiness\n * src: \"textures/specularGlossiness.jpg\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"textures/normalMap.jpg\"\n * })\n * })\n * });\n * ````\n *\n * ## Background Theory\n *\n * For an introduction to physically-based rendering (PBR) concepts, try these articles:\n *\n * * Joe Wilson's [Basic Theory of Physically-Based Rendering](https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/)\n * * Jeff Russel's [Physically-based Rendering, and you can too!](https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/)\n * * Sebastien Legarde's [Adapting a physically-based shading model](http://seblagarde.wordpress.com/tag/physically-based-rendering/)\n *\n * ## SpecularMaterial Properties\n *\n * The following table summarizes SpecularMaterial properties:\n *\n * | Property | Type | Range | Default Value | Space | Description |\n * |:--------:|:----:|:-----:|:-------------:|:-----:|:-----------:|\n * | {@link SpecularMaterial#diffuse} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the diffuse color of the material. |\n * | {@link SpecularMaterial#specular} | Array | [0, 1] for all components | [1,1,1,1] | linear | The RGB components of the specular color of the material. |\n * | {@link SpecularMaterial#glossiness} | Number | [0, 1] | 1 | linear | The glossiness the material. |\n * | {@link SpecularMaterial#specularF0} | Number | [0, 1] | 1 | linear | The specularF0 of the material surface. |\n * | {@link SpecularMaterial#emissive} | Array | [0, 1] for all components | [0,0,0] | linear | The RGB components of the emissive color of the material. |\n * | {@link SpecularMaterial#alpha} | Number | [0, 1] | 1 | linear | The transparency of the material surface (0 fully transparent, 1 fully opaque). |\n * | {@link SpecularMaterial#diffuseMap} | {@link Texture} | | null | sRGB | Texture RGB components multiplying by {@link SpecularMaterial#diffuse}. If the fourth component (A) is present, it multiplies by {@link SpecularMaterial#alpha}. |\n * | {@link SpecularMaterial#specularMap} | {@link Texture} | | null | sRGB | Texture RGB components multiplying by {@link SpecularMaterial#specular}. If the fourth component (A) is present, it multiplies by {@link SpecularMaterial#alpha}. |\n * | {@link SpecularMaterial#glossinessMap} | {@link Texture} | | null | linear | Texture with first component multiplying by {@link SpecularMaterial#glossiness}. |\n * | {@link SpecularMaterial#specularGlossinessMap} | {@link Texture} | | null | linear | Texture with first three components multiplying by {@link SpecularMaterial#specular} and fourth component multiplying by {@link SpecularMaterial#glossiness}. |\n * | {@link SpecularMaterial#emissiveMap} | {@link Texture} | | null | linear | Texture with RGB components multiplying by {@link SpecularMaterial#emissive}. |\n * | {@link SpecularMaterial#alphaMap} | {@link Texture} | | null | linear | Texture with first component multiplying by {@link SpecularMaterial#alpha}. |\n * | {@link SpecularMaterial#occlusionMap} | {@link Texture} | | null | linear | Ambient occlusion texture multiplying by surface's reflected diffuse and specular light. |\n * | {@link SpecularMaterial#normalMap} | {@link Texture} | | null | linear | Tangent-space normal map. |\n * | {@link SpecularMaterial#alphaMode} | String | \"opaque\", \"blend\", \"mask\" | \"blend\" | | Alpha blend mode. |\n * | {@link SpecularMaterial#alphaCutoff} | Number | [0..1] | 0.5 | | Alpha cutoff value. |\n * | {@link SpecularMaterial#backfaces} | Boolean | | false | | Whether to render {@link Geometry} backfaces. |\n * | {@link SpecularMaterial#frontface} | String | \"ccw\", \"cw\" | \"ccw\" | | The winding order for {@link Geometry} frontfaces - \"cw\" for clockwise, or \"ccw\" for counter-clockwise. |\n *\n */\nclass SpecularMaterial extends Material {\n\n /**\n @private\n */\n get type() {\n return \"SpecularMaterial\";\n }\n\n /**\n *\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] The SpecularMaterial configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.diffuse=[1,1,1]] RGB diffuse color of this SpecularMaterial. Multiplies by the RGB components of {@link SpecularMaterial#diffuseMap}.\n * @param {Texture} [cfg.diffuseMap=undefined] RGBA {@link Texture} containing the diffuse color of this SpecularMaterial, with optional *A* component for alpha. The RGB components multiply by {@link SpecularMaterial#diffuse}, while the *A* component, if present, multiplies by {@link SpecularMaterial#alpha}.\n * @param {Number} [cfg.specular=[1,1,1]] RGB specular color of this SpecularMaterial. Multiplies by the {@link SpecularMaterial#specularMap} and the *RGB* components of {@link SpecularMaterial#specularGlossinessMap}.\n * @param {Texture} [cfg.specularMap=undefined] RGB texture containing the specular color of this SpecularMaterial. Multiplies by the {@link SpecularMaterial#specular} property. Must be within the same {@link Scene} as this SpecularMaterial.\n * @param {Number} [cfg.glossiness=1.0] Factor in the range [0..1] indicating how glossy this SpecularMaterial is. 0 is no glossiness, 1 is full glossiness. Multiplies by the *R* component of {@link SpecularMaterial#glossinessMap} and the *A* component of {@link SpecularMaterial#specularGlossinessMap}.\n * @param {Texture} [cfg.specularGlossinessMap=undefined] RGBA {@link Texture} containing this SpecularMaterial's specular color in its *RGB* component and glossiness in its *A* component. Its *RGB* components multiply by {@link SpecularMaterial#specular}, while its *A* component multiplies by {@link SpecularMaterial#glossiness}. Must be within the same {@link Scene} as this SpecularMaterial.\n * @param {Number} [cfg.specularF0=0.0] Factor in the range 0..1 indicating how reflective this SpecularMaterial is.\n * @param {Number[]} [cfg.emissive=[0,0,0]] RGB emissive color of this SpecularMaterial. Multiplies by the RGB components of {@link SpecularMaterial#emissiveMap}.\n * @param {Texture} [cfg.emissiveMap=undefined] RGB {@link Texture} containing the emissive color of this SpecularMaterial. Multiplies by the {@link SpecularMaterial#emissive} property. Must be within the same {@link Scene} as this SpecularMaterial.\n * @param {Texture} [cfg.occlusionMap=undefined] RGB ambient occlusion {@link Texture}. Within shaders, multiplies by the specular and diffuse light reflected by surfaces. Must be within the same {@link Scene} as this SpecularMaterial.\n * @param {Texture} [cfg.normalMap=undefined] {Texture} RGB tangent-space normal {@link Texture}. Must be within the same {@link Scene} as this SpecularMaterial.\n * @param {Number} [cfg.alpha=1.0] Factor in the range 0..1 indicating how transparent this SpecularMaterial is. A value of 0.0 indicates fully transparent, 1.0 is fully opaque. Multiplies by the *R* component of {@link SpecularMaterial#alphaMap} and the *A* component, if present, of {@link SpecularMaterial#diffuseMap}.\n * @param {Texture} [cfg.alphaMap=undefined] RGB {@link Texture} containing this SpecularMaterial's alpha in its *R* component. The *R* component multiplies by the {@link SpecularMaterial#alpha} property. Must be within the same {@link Scene} as this SpecularMaterial.\n * @param {String} [cfg.alphaMode=\"opaque\"] The alpha blend mode - accepted values are \"opaque\", \"blend\" and \"mask\". See the {@link SpecularMaterial#alphaMode} property for more info.\n * @param {Number} [cfg.alphaCutoff=0.5] The alpha cutoff value. See the {@link SpecularMaterial#alphaCutoff} property for more info.\n * @param {Boolean} [cfg.backfaces=false] Whether to render {@link Geometry} backfaces.\n * @param {Boolean} [cfg.frontface=\"ccw\"] The winding order for {@link Geometry} front faces - \"cw\" for clockwise, or \"ccw\" for counter-clockwise.\n * @param {Number} [cfg.lineWidth=1] Scalar that controls the width of {@link Geometry lines.\n * @param {Number} [cfg.pointSize=1] Scalar that controls the size of {@link Geometry} points.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n type: \"SpecularMaterial\",\n diffuse: math.vec3([1.0, 1.0, 1.0]),\n emissive: math.vec3([0.0, 0.0, 0.0]),\n specular: math.vec3([1.0, 1.0, 1.0]),\n glossiness: null,\n specularF0: null,\n alpha: null,\n alphaMode: null,\n alphaCutoff: null,\n lineWidth: null,\n pointSize: null,\n backfaces: null,\n frontface: null, // Boolean for speed; true == \"ccw\", false == \"cw\"\n hash: null\n });\n\n this.diffuse = cfg.diffuse;\n this.specular = cfg.specular;\n this.glossiness = cfg.glossiness;\n this.specularF0 = cfg.specularF0;\n this.emissive = cfg.emissive;\n this.alpha = cfg.alpha;\n\n if (cfg.diffuseMap) {\n this._diffuseMap = this._checkComponent(\"Texture\", cfg.diffuseMap);\n }\n if (cfg.emissiveMap) {\n this._emissiveMap = this._checkComponent(\"Texture\", cfg.emissiveMap);\n }\n if (cfg.specularMap) {\n this._specularMap = this._checkComponent(\"Texture\", cfg.specularMap);\n }\n if (cfg.glossinessMap) {\n this._glossinessMap = this._checkComponent(\"Texture\", cfg.glossinessMap);\n }\n if (cfg.specularGlossinessMap) {\n this._specularGlossinessMap = this._checkComponent(\"Texture\", cfg.specularGlossinessMap);\n }\n if (cfg.occlusionMap) {\n this._occlusionMap = this._checkComponent(\"Texture\", cfg.occlusionMap);\n }\n if (cfg.alphaMap) {\n this._alphaMap = this._checkComponent(\"Texture\", cfg.alphaMap);\n }\n if (cfg.normalMap) {\n this._normalMap = this._checkComponent(\"Texture\", cfg.normalMap);\n }\n\n this.alphaMode = cfg.alphaMode;\n this.alphaCutoff = cfg.alphaCutoff;\n this.backfaces = cfg.backfaces;\n this.frontface = cfg.frontface;\n\n this.lineWidth = cfg.lineWidth;\n this.pointSize = cfg.pointSize;\n\n this._makeHash();\n }\n\n _makeHash() {\n const state = this._state;\n const hash = [\"/spe\"];\n if (this._diffuseMap) {\n hash.push(\"/dm\");\n if (this._diffuseMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n hash.push(\"/\" + this._diffuseMap.encoding);\n }\n if (this._emissiveMap) {\n hash.push(\"/em\");\n if (this._emissiveMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._glossinessMap) {\n hash.push(\"/gm\");\n if (this._glossinessMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._specularMap) {\n hash.push(\"/sm\");\n if (this._specularMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._specularGlossinessMap) {\n hash.push(\"/sgm\");\n if (this._specularGlossinessMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._occlusionMap) {\n hash.push(\"/ocm\");\n if (this._occlusionMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._normalMap) {\n hash.push(\"/nm\");\n if (this._normalMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n if (this._alphaMap) {\n hash.push(\"/opm\");\n if (this._alphaMap.hasMatrix) {\n hash.push(\"/mat\");\n }\n }\n hash.push(\";\");\n state.hash = hash.join(\"\");\n }\n\n /**\n * Sets the RGB diffuse color of this SpecularMaterial.\n *\n * Multiplies by the *RGB* components of {@link SpecularMaterial#diffuseMap}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n * @type {Number[]}\n */\n set diffuse(value) {\n let diffuse = this._state.diffuse;\n if (!diffuse) {\n diffuse = this._state.diffuse = new Float32Array(3);\n } else if (value && diffuse[0] === value[0] && diffuse[1] === value[1] && diffuse[2] === value[2]) {\n return;\n }\n if (value) {\n diffuse[0] = value[0];\n diffuse[1] = value[1];\n diffuse[2] = value[2];\n } else {\n diffuse[0] = 1;\n diffuse[1] = 1;\n diffuse[2] = 1;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the RGB diffuse color of this SpecularMaterial.\n *\n * @type {Number[]}\n */\n get diffuse() {\n return this._state.diffuse;\n }\n\n /**\n * Gets the RGB {@link Texture} containing the diffuse color of this SpecularMaterial, with optional *A* component for alpha.\n *\n * The *RGB* components multipliues by the {@link SpecularMaterial#diffuse} property, while the *A* component, if present, multiplies by the {@link SpecularMaterial#alpha} property.\n *\n * @type {Texture}\n */\n get diffuseMap() {\n return this._diffuseMap;\n }\n\n /**\n * Sets the RGB specular color of this SpecularMaterial.\n *\n * Multiplies by {@link SpecularMaterial#specularMap} and the *A* component of {@link SpecularMaterial#specularGlossinessMap}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @type {Number[]}\n */\n set specular(value) {\n let specular = this._state.specular;\n if (!specular) {\n specular = this._state.specular = new Float32Array(3);\n } else if (value && specular[0] === value[0] && specular[1] === value[1] && specular[2] === value[2]) {\n return;\n }\n if (value) {\n specular[0] = value[0];\n specular[1] = value[1];\n specular[2] = value[2];\n } else {\n specular[0] = 1;\n specular[1] = 1;\n specular[2] = 1;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the RGB specular color of this SpecularMaterial.\n *\n * @type {Number[]}\n */\n get specular() {\n return this._state.specular;\n }\n\n /**\n * Gets the RGB texture containing the specular color of this SpecularMaterial.\n *\n * Multiplies by {@link SpecularMaterial#specular}.\n *\n * @type {Texture}\n */\n get specularMap() {\n return this._specularMap;\n }\n\n /**\n * Gets the RGBA texture containing this SpecularMaterial's specular color in its *RGB* components and glossiness in its *A* component.\n *\n * The *RGB* components multiplies {@link SpecularMaterial#specular}, while the *A* component multiplies by {@link SpecularMaterial#glossiness}.\n *\n * @type {Texture}\n */\n get specularGlossinessMap() {\n return this._specularGlossinessMap;\n }\n\n /**\n * Sets the Factor in the range [0..1] indicating how glossy this SpecularMaterial is.\n *\n * ````0```` is no glossiness, ````1```` is full glossiness.\n *\n * Multiplies by the *R* component of {@link SpecularMaterial#glossinessMap} and the *A* component of {@link SpecularMaterial#specularGlossinessMap}.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set glossiness(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.glossiness === value) {\n return;\n }\n this._state.glossiness = value;\n this.glRedraw();\n }\n\n /**\n * Gets the Factor in the range ````[0..1]```` indicating how glossy this SpecularMaterial is.\n\n * @type {Number}\n */\n get glossiness() {\n return this._state.glossiness;\n }\n\n /**\n * Gets the RGB texture containing this SpecularMaterial's glossiness in its *R* component.\n *\n * The *R* component multiplies by {@link SpecularMaterial#glossiness}.\n ** @type {Texture}\n */\n get glossinessMap() {\n return this._glossinessMap;\n }\n\n /**\n * Sets the factor in the range ````[0..1]```` indicating amount of specular Fresnel.\n *\n * Default value is ````0.0````.\n *\n * @type {Number}\n */\n set specularF0(value) {\n value = (value !== undefined && value !== null) ? value : 0.0;\n if (this._state.specularF0 === value) {\n return;\n }\n this._state.specularF0 = value;\n this.glRedraw();\n }\n\n /**\n * Gets the factor in the range ````[0..1]```` indicating amount of specular Fresnel.\n *\n * @type {Number}\n */\n get specularF0() {\n return this._state.specularF0;\n }\n\n /**\n * Sets the RGB emissive color of this SpecularMaterial.\n *\n * Multiplies by {@link SpecularMaterial#emissiveMap}.\n\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @type {Number[]}\n */\n set emissive(value) {\n let emissive = this._state.emissive;\n if (!emissive) {\n emissive = this._state.emissive = new Float32Array(3);\n } else if (value && emissive[0] === value[0] && emissive[1] === value[1] && emissive[2] === value[2]) {\n return;\n }\n if (value) {\n emissive[0] = value[0];\n emissive[1] = value[1];\n emissive[2] = value[2];\n } else {\n emissive[0] = 0;\n emissive[1] = 0;\n emissive[2] = 0;\n }\n this.glRedraw();\n }\n\n /**\n * Gets the RGB emissive color of this SpecularMaterial.\n *\n * @type {Number[]}\n */\n get emissive() {\n return this._state.emissive;\n }\n\n /**\n * Gets the RGB texture containing the emissive color of this SpecularMaterial.\n *\n * Multiplies by {@link SpecularMaterial#emissive}.\n *\n * @type {Texture}\n */\n get emissiveMap() {\n return this._emissiveMap;\n }\n\n /**\n * Sets the factor in the range [0..1] indicating how transparent this SpecularMaterial is.\n *\n * A value of ````0.0```` is fully transparent, while ````1.0```` is fully opaque.\n *\n * Multiplies by the *R* component of {@link SpecularMaterial#alphaMap} and the *A* component, if present, of {@link SpecularMaterial#diffuseMap}.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set alpha(value) {\n value = (value !== undefined && value !== null) ? value : 1.0;\n if (this._state.alpha === value) {\n return;\n }\n this._state.alpha = value;\n this.glRedraw();\n }\n\n /**\n * Gets the factor in the range [0..1] indicating how transparent this SpecularMaterial is.\n *\n * @type {Number}\n */\n get alpha() {\n return this._state.alpha;\n }\n\n /**\n * Gets the RGB {@link Texture} with alpha in its *R* component.\n *\n * The *R* component multiplies by the {@link SpecularMaterial#alpha} property.\n *\n * @type {Texture}\n */\n get alphaMap() {\n return this._alphaMap;\n }\n\n /**\n * Gets the RGB tangent-space normal {@link Texture} attached to this SpecularMaterial.\n *\n * @type {Texture}\n */\n get normalMap() {\n return this._normalMap;\n }\n\n /**\n * Gets the RGB ambient occlusion {@link Texture} attached to this SpecularMaterial.\n *\n * Multiplies by the specular and diffuse light reflected by surfaces.\n *\n * @type {Texture}\n */\n get occlusionMap() {\n return this._occlusionMap;\n }\n\n /**\n * Sets the alpha rendering mode.\n *\n * This governs how alpha is treated. Alpha is the combined result of the {@link SpecularMaterial#alpha} and {@link SpecularMaterial#alphaMap} properties.\n *\n * Accepted values are:\n *\n * * \"opaque\" - The alpha value is ignored and the rendered output is fully opaque (default).\n * * \"mask\" - The rendered output is either fully opaque or fully transparent depending on the alpha value and the specified alpha cutoff value.\n * * \"blend\" - The alpha value is used to composite the source and destination areas. The rendered output is combined with the background using the normal painting operation (i.e. the Porter and Duff over operator)\n *\n * @type {String}\n */\n set alphaMode(alphaMode) {\n alphaMode = alphaMode || \"opaque\";\n let value = alphaModes[alphaMode];\n if (value === undefined) {\n this.error(\"Unsupported value for 'alphaMode': \" + alphaMode + \" defaulting to 'opaque'\");\n value = \"opaque\";\n }\n if (this._state.alphaMode === value) {\n return;\n }\n this._state.alphaMode = value;\n this.glRedraw();\n }\n\n get alphaMode() {\n return alphaModeNames[this._state.alphaMode];\n }\n\n /**\n * Sets the alpha cutoff value.\n *\n * Specifies the cutoff threshold when {@link SpecularMaterial#alphaMode} equals \"mask\". If the alpha is greater than or equal to this value then it is rendered as fully opaque, otherwise, it is rendered as fully transparent. A value greater than 1.0 will render the entire material as fully transparent. This value is ignored for other modes.\n *\n * Alpha is the combined result of the {@link SpecularMaterial#alpha} and {@link SpecularMaterial#alphaMap} properties.\n *\n * Default value is ````0.5````.\n *\n * @type {Number}\n */\n set alphaCutoff(alphaCutoff) {\n if (alphaCutoff === null || alphaCutoff === undefined) {\n alphaCutoff = 0.5;\n }\n if (this._state.alphaCutoff === alphaCutoff) {\n return;\n }\n this._state.alphaCutoff = alphaCutoff;\n }\n\n /**\n * Gets the alpha cutoff value.\n\n * @type {Number}\n */\n get alphaCutoff() {\n return this._state.alphaCutoff;\n }\n\n /**\n * Sets whether backfaces are visible on attached {@link Mesh}es.\n *\n * The backfaces will belong to {@link ReadableGeometry} compoents that are also attached to the {@link Mesh}es.\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n set backfaces(value) {\n value = !!value;\n if (this._state.backfaces === value) {\n return;\n }\n this._state.backfaces = value;\n this.glRedraw();\n }\n\n /**\n * Gets whether backfaces are visible on attached {@link Mesh}es.\n *\n * @type {Boolean}\n */\n get backfaces() {\n return this._state.backfaces;\n }\n\n /**\n * Sets the winding direction of front faces of {@link Geometry} of attached {@link Mesh}es.\n *\n * Default value is ````\"ccw\"````.\n *\n * @type {String}\n */\n set frontface(value) {\n value = value !== \"cw\";\n if (this._state.frontface === value) {\n return;\n }\n this._state.frontface = value;\n this.glRedraw();\n }\n\n /**\n * Gets the winding direction of front faces of {@link Geometry} of attached {@link Mesh}es.\n *\n * @type {String}\n */\n get frontface() {\n return this._state.frontface ? \"ccw\" : \"cw\";\n }\n\n /**\n * Sets the SpecularMaterial's line width.\n *\n * This is not supported by WebGL implementations based on DirectX [2019].\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set lineWidth(value) {\n this._state.lineWidth = value || 1.0;\n this.glRedraw();\n }\n\n /**\n * Gets the SpecularMaterial's line width.\n *\n * @type {Number}\n */\n get lineWidth() {\n return this._state.lineWidth;\n }\n\n /**\n * Sets the SpecularMaterial's point size.\n *\n * Default value is ````1.0````.\n *\n * @type {Number}\n */\n set pointSize(value) {\n this._state.pointSize = value || 1;\n this.glRedraw();\n }\n\n /**\n * Sets the SpecularMaterial's point size.\n *\n * @type {Number}\n */\n get pointSize() {\n return this._state.pointSize;\n }\n\n /**\n * Destroys this SpecularMaterial.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\n/**\n * @private\n */\nfunction convertConstant(gl, constantVal, encoding = null) {\n\n let extension;\n const p = constantVal;\n\n if (p === UnsignedByteType) return gl.UNSIGNED_BYTE;\n if (p === UnsignedShort4444Type) return gl.UNSIGNED_SHORT_4_4_4_4;\n if (p === UnsignedShort5551Type) return gl.UNSIGNED_SHORT_5_5_5_1;\n\n if (p === ByteType) return gl.BYTE;\n if (p === ShortType) return gl.SHORT;\n if (p === UnsignedShortType) return gl.UNSIGNED_SHORT;\n if (p === IntType) return gl.INT;\n if (p === UnsignedIntType) return gl.UNSIGNED_INT;\n if (p === FloatType) return gl.FLOAT;\n\n if (p === HalfFloatType) {\n return gl.HALF_FLOAT;\n }\n\n if (p === AlphaFormat) return gl.ALPHA;\n if (p === RGBAFormat) return gl.RGBA;\n if (p === LuminanceFormat) return gl.LUMINANCE;\n if (p === LuminanceAlphaFormat) return gl.LUMINANCE_ALPHA;\n if (p === DepthFormat) return gl.DEPTH_COMPONENT;\n if (p === DepthStencilFormat) return gl.DEPTH_STENCIL;\n if (p === RedFormat) return gl.RED;\n\n if (p === RGBFormat) {\n return gl.RGBA;\n }\n\n // WebGL2 formats.\n\n if (p === RedIntegerFormat) return gl.RED_INTEGER;\n if (p === RGFormat) return gl.RG;\n if (p === RGIntegerFormat) return gl.RG_INTEGER;\n if (p === RGBAIntegerFormat) return gl.RGBA_INTEGER;\n\n // S3TC\n\n if (p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format) {\n if (encoding === sRGBEncoding) {\n const extension = getExtension(gl, 'WEBGL_compressed_texture_s3tc_srgb');\n if (extension !== null) {\n if (p === RGB_S3TC_DXT1_Format) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT;\n if (p === RGBA_S3TC_DXT1_Format) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;\n if (p === RGBA_S3TC_DXT3_Format) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;\n if (p === RGBA_S3TC_DXT5_Format) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;\n } else {\n return null;\n }\n } else {\n extension = getExtension(gl, 'WEBGL_compressed_texture_s3tc');\n if (extension !== null) {\n if (p === RGB_S3TC_DXT1_Format) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;\n if (p === RGBA_S3TC_DXT1_Format) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;\n if (p === RGBA_S3TC_DXT3_Format) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;\n if (p === RGBA_S3TC_DXT5_Format) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;\n } else {\n return null;\n }\n }\n }\n\n // PVRTC\n\n if (p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format) {\n const extension = getExtension(gl, 'WEBGL_compressed_texture_pvrtc');\n if (extension !== null) {\n if (p === RGB_PVRTC_4BPPV1_Format) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;\n if (p === RGB_PVRTC_2BPPV1_Format) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;\n if (p === RGBA_PVRTC_4BPPV1_Format) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;\n if (p === RGBA_PVRTC_2BPPV1_Format) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;\n } else {\n return null;\n }\n }\n\n // ETC1\n\n if (p === RGB_ETC1_Format) {\n const extension = getExtension(gl, 'WEBGL_compressed_texture_etc1');\n if (extension !== null) {\n return extension.COMPRESSED_RGB_ETC1_WEBGL;\n } else {\n return null;\n }\n }\n\n // ETC2\n\n if (p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format) {\n const extension = getExtension(gl, 'WEBGL_compressed_texture_etc');\n if (extension !== null) {\n if (p === RGB_ETC2_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2;\n if (p === RGBA_ETC2_EAC_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC;\n } else {\n return null;\n }\n }\n\n // ASTC\n\n if (p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||\n p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||\n p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||\n p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||\n p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format) {\n const extension = getExtension(gl, 'WEBGL_compressed_texture_astc');\n if (extension !== null) {\n if (p === RGBA_ASTC_4x4_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR;\n if (p === RGBA_ASTC_5x4_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR;\n if (p === RGBA_ASTC_5x5_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR;\n if (p === RGBA_ASTC_6x5_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR;\n if (p === RGBA_ASTC_6x6_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR;\n if (p === RGBA_ASTC_8x5_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR;\n if (p === RGBA_ASTC_8x6_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR;\n if (p === RGBA_ASTC_8x8_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR;\n if (p === RGBA_ASTC_10x5_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR;\n if (p === RGBA_ASTC_10x6_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR;\n if (p === RGBA_ASTC_10x8_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR;\n if (p === RGBA_ASTC_10x10_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR;\n if (p === RGBA_ASTC_12x10_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR;\n if (p === RGBA_ASTC_12x12_Format) return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR;\n } else {\n return null;\n }\n }\n\n // BPTC\n\n if (p === RGBA_BPTC_Format) {\n const extension = getExtension(gl, 'EXT_texture_compression_bptc');\n if (extension !== null) {\n if (p === RGBA_BPTC_Format) {\n return (encoding === sRGBEncoding) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT;\n }\n } else {\n return null;\n }\n }\n\n //\n\n if (p === UnsignedInt248Type) {\n return gl.UNSIGNED_INT_24_8;\n }\n if (p === RepeatWrapping) {\n return gl.REPEAT;\n }\n if (p === ClampToEdgeWrapping) {\n return gl.CLAMP_TO_EDGE;\n }\n if (p === NearestMipMapNearestFilter) {\n return gl.NEAREST_MIPMAP_LINEAR;\n }\n if (p === NearestMipMapLinearFilter) {\n return gl.NEAREST_MIPMAP_LINEAR;\n }\n if (p === LinearMipMapNearestFilter) {\n return gl.LINEAR_MIPMAP_NEAREST;\n }\n if (p === LinearMipMapLinearFilter) {\n return gl.LINEAR_MIPMAP_LINEAR;\n }\n if (p === NearestFilter) {\n return gl.NEAREST;\n }\n if (p === LinearFilter) {\n return gl.LINEAR;\n }\n\n return null;\n}\n\nconst color$4 = new Uint8Array([0, 0, 0, 1]);\n\n/**\n * @desc A low-level component that represents a 2D WebGL texture.\n *\n * @private\n */\nclass Texture2D {\n\n constructor({gl, target, format, type, wrapS, wrapT, wrapR, encoding, preloadColor, premultiplyAlpha, flipY}) {\n\n this.gl = gl;\n\n this.target = target || gl.TEXTURE_2D;\n this.format = format || RGBAFormat;\n this.type = type || UnsignedByteType;\n this.internalFormat = null;\n this.premultiplyAlpha = !!premultiplyAlpha;\n this.flipY = !!flipY;\n this.unpackAlignment = 4;\n this.wrapS = wrapS || RepeatWrapping;\n this.wrapT = wrapT || RepeatWrapping;\n this.wrapR = wrapR || RepeatWrapping;\n this.encoding = encoding || sRGBEncoding;\n this.texture = gl.createTexture();\n\n if (preloadColor) {\n this.setPreloadColor(preloadColor); // Prevents \"there is no texture bound to the unit 0\" error\n }\n\n this.allocated = true;\n }\n\n setPreloadColor(value) {\n if (!value) {\n color$4[0] = 0;\n color$4[1] = 0;\n color$4[2] = 0;\n color$4[3] = 255;\n } else {\n color$4[0] = Math.floor(value[0] * 255);\n color$4[1] = Math.floor(value[1] * 255);\n color$4[2] = Math.floor(value[2] * 255);\n color$4[3] = Math.floor((value[3] !== undefined ? value[3] : 1) * 255);\n }\n const gl = this.gl;\n gl.bindTexture(this.target, this.texture);\n if (this.target === gl.TEXTURE_CUBE_MAP) {\n const faces = [\n gl.TEXTURE_CUBE_MAP_POSITIVE_X,\n gl.TEXTURE_CUBE_MAP_NEGATIVE_X,\n gl.TEXTURE_CUBE_MAP_POSITIVE_Y,\n gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,\n gl.TEXTURE_CUBE_MAP_POSITIVE_Z,\n gl.TEXTURE_CUBE_MAP_NEGATIVE_Z\n ];\n for (let i = 0, len = faces.length; i < len; i++) {\n gl.texImage2D(faces[i], 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, color$4);\n }\n } else {\n gl.texImage2D(this.target, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, color$4);\n }\n gl.bindTexture(this.target, null);\n }\n\n setTarget(target) {\n this.target = target || this.gl.TEXTURE_2D;\n }\n\n setImage(image, props = {}) {\n\n const gl = this.gl;\n\n if (props.format !== undefined) {\n this.format = props.format;\n }\n if (props.internalFormat !== undefined) {\n this.internalFormat = props.internalFormat;\n }\n if (props.encoding !== undefined) {\n this.encoding = props.encoding;\n }\n if (props.type !== undefined) {\n this.type = props.type;\n }\n if (props.flipY !== undefined) {\n this.flipY = props.flipY;\n }\n if (props.premultiplyAlpha !== undefined) {\n this.premultiplyAlpha = props.premultiplyAlpha;\n }\n if (props.unpackAlignment !== undefined) {\n this.unpackAlignment = props.unpackAlignment;\n }\n if (props.minFilter !== undefined) {\n this.minFilter = props.minFilter;\n }\n if (props.magFilter !== undefined) {\n this.magFilter = props.magFilter;\n }\n if (props.wrapS !== undefined) {\n this.wrapS = props.wrapS;\n }\n if (props.wrapT !== undefined) {\n this.wrapT = props.wrapT;\n }\n if (props.wrapR !== undefined) {\n this.wrapR = props.wrapR;\n }\n\n let generateMipMap = false;\n\n gl.bindTexture(this.target, this.texture);\n\n const bak1 = gl.getParameter(gl.UNPACK_FLIP_Y_WEBGL);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this.flipY);\n\n const bak2 = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha);\n\n const bak3 = gl.getParameter(gl.UNPACK_ALIGNMENT);\n gl.pixelStorei(gl.UNPACK_ALIGNMENT, this.unpackAlignment);\n\n const bak4 = gl.getParameter(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL); gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);\n\n const minFilter = convertConstant(gl, this.minFilter);\n gl.texParameteri(this.target, gl.TEXTURE_MIN_FILTER, minFilter);\n\n if (minFilter === gl.NEAREST_MIPMAP_NEAREST\n || minFilter === gl.LINEAR_MIPMAP_NEAREST\n || minFilter === gl.NEAREST_MIPMAP_LINEAR\n || minFilter === gl.LINEAR_MIPMAP_LINEAR) {\n generateMipMap = true;\n }\n\n const magFilter = convertConstant(gl, this.magFilter);\n if (magFilter) {\n gl.texParameteri(this.target, gl.TEXTURE_MAG_FILTER, magFilter);\n }\n\n const wrapS = convertConstant(gl, this.wrapS);\n if (wrapS) {\n gl.texParameteri(this.target, gl.TEXTURE_WRAP_S, wrapS);\n }\n\n const wrapT = convertConstant(gl, this.wrapT);\n if (wrapT) {\n gl.texParameteri(this.target, gl.TEXTURE_WRAP_T, wrapT);\n }\n\n const glFormat = convertConstant(gl, this.format, this.encoding);\n const glType = convertConstant(gl, this.type);\n const glInternalFormat = getInternalFormat(gl, this.internalFormat, glFormat, glType, this.encoding, false);\n\n if (this.target === gl.TEXTURE_CUBE_MAP) {\n if (utils.isArray(image)) {\n const images = image;\n const faces = [\n gl.TEXTURE_CUBE_MAP_POSITIVE_X,\n gl.TEXTURE_CUBE_MAP_NEGATIVE_X,\n gl.TEXTURE_CUBE_MAP_POSITIVE_Y,\n gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,\n gl.TEXTURE_CUBE_MAP_POSITIVE_Z,\n gl.TEXTURE_CUBE_MAP_NEGATIVE_Z\n ];\n for (let i = 0, len = faces.length; i < len; i++) {\n gl.texImage2D(faces[i], 0, glInternalFormat, glFormat, glType, images[i]);\n }\n }\n } else {\n gl.texImage2D(gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image);\n }\n\n if (generateMipMap) {\n gl.generateMipmap(this.target);\n }\n\n gl.bindTexture(this.target, null);\n\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, bak1);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, bak2);\n gl.pixelStorei(gl.UNPACK_ALIGNMENT, bak3);\n gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, bak4);\n }\n\n setCompressedData({mipmaps, props = {}}) {\n\n const gl = this.gl;\n const levels = mipmaps.length;\n\n // Cache props\n\n if (props.format !== undefined) {\n this.format = props.format;\n }\n if (props.internalFormat !== undefined) {\n this.internalFormat = props.internalFormat;\n }\n if (props.encoding !== undefined) {\n this.encoding = props.encoding;\n }\n if (props.type !== undefined) {\n this.type = props.type;\n }\n if (props.flipY !== undefined) {\n this.flipY = props.flipY;\n }\n if (props.premultiplyAlpha !== undefined) {\n this.premultiplyAlpha = props.premultiplyAlpha;\n }\n if (props.unpackAlignment !== undefined) {\n this.unpackAlignment = props.unpackAlignment;\n }\n if (props.minFilter !== undefined) {\n this.minFilter = props.minFilter;\n }\n if (props.magFilter !== undefined) {\n this.magFilter = props.magFilter;\n }\n if (props.wrapS !== undefined) {\n this.wrapS = props.wrapS;\n }\n if (props.wrapT !== undefined) {\n this.wrapT = props.wrapT;\n }\n if (props.wrapR !== undefined) {\n this.wrapR = props.wrapR;\n }\n\n gl.activeTexture(gl.TEXTURE0 + 0);\n gl.bindTexture(this.target, this.texture);\n\n let supportsMips = mipmaps.length > 1;\n\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this.flipY);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha);\n gl.pixelStorei(gl.UNPACK_ALIGNMENT, this.unpackAlignment);\n gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);\n\n const wrapS = convertConstant(gl, this.wrapS);\n if (wrapS) {\n gl.texParameteri(this.target, gl.TEXTURE_WRAP_S, wrapS);\n }\n\n const wrapT = convertConstant(gl, this.wrapT);\n if (wrapT) {\n gl.texParameteri(this.target, gl.TEXTURE_WRAP_T, wrapT);\n }\n\n if (this.type === gl.TEXTURE_3D || this.type === gl.TEXTURE_2D_ARRAY) {\n const wrapR = convertConstant(gl, this.wrapR);\n if (wrapR) {\n gl.texParameteri(this.target, gl.TEXTURE_WRAP_R, wrapR);\n }\n gl.texParameteri(this.type, gl.TEXTURE_WRAP_R, wrapR);\n }\n\n if (supportsMips) {\n gl.texParameteri(this.target, gl.TEXTURE_MIN_FILTER, filterFallback(gl, this.minFilter));\n gl.texParameteri(this.target, gl.TEXTURE_MAG_FILTER, filterFallback(gl, this.magFilter));\n\n } else {\n gl.texParameteri(this.target, gl.TEXTURE_MIN_FILTER, convertConstant(gl, this.minFilter));\n gl.texParameteri(this.target, gl.TEXTURE_MAG_FILTER, convertConstant(gl, this.magFilter));\n }\n\n const glFormat = convertConstant(gl, this.format, this.encoding);\n const glType = convertConstant(gl, this.type);\n const glInternalFormat = getInternalFormat(gl, this.internalFormat, glFormat, glType, this.encoding, false);\n\n gl.texStorage2D(gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[0].width, mipmaps[0].height);\n\n for (let i = 0, len = mipmaps.length; i < len; i++) {\n\n const mipmap = mipmaps[i];\n\n if (this.format !== RGBAFormat) {\n if (glFormat !== null) {\n gl.compressedTexSubImage2D(gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data);\n } else {\n console.warn('Attempt to load unsupported compressed texture format in .setCompressedData()');\n }\n } else {\n gl.texSubImage2D(gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data);\n }\n }\n\n // if (generateMipMap) {\n // // gl.generateMipmap(this.target); // Only for roughness textures?\n // }\n\n gl.bindTexture(this.target, null);\n }\n\n setProps(props) {\n const gl = this.gl;\n gl.bindTexture(this.target, this.texture);\n this._uploadProps(props);\n gl.bindTexture(this.target, null);\n }\n\n _uploadProps(props) {\n const gl = this.gl;\n if (props.format !== undefined) {\n this.format = props.format;\n }\n if (props.internalFormat !== undefined) {\n this.internalFormat = props.internalFormat;\n }\n if (props.encoding !== undefined) {\n this.encoding = props.encoding;\n }\n if (props.type !== undefined) {\n this.type = props.type;\n }\n if (props.minFilter !== undefined) {\n const minFilter = convertConstant(gl, props.minFilter);\n if (minFilter) {\n this.minFilter = props.minFilter;\n gl.texParameteri(this.target, gl.TEXTURE_MIN_FILTER, minFilter);\n if (minFilter === gl.NEAREST_MIPMAP_NEAREST || minFilter === gl.LINEAR_MIPMAP_NEAREST || minFilter === gl.NEAREST_MIPMAP_LINEAR || minFilter === gl.LINEAR_MIPMAP_LINEAR) {\n gl.generateMipmap(this.target);\n }\n }\n }\n if (props.magFilter !== undefined) {\n const magFilter = convertConstant(gl, props.magFilter);\n if (magFilter) {\n this.magFilter = props.magFilter;\n gl.texParameteri(this.target, gl.TEXTURE_MAG_FILTER, magFilter);\n }\n }\n if (props.wrapS !== undefined) {\n const wrapS = convertConstant(gl, props.wrapS);\n if (wrapS) {\n this.wrapS = props.wrapS;\n gl.texParameteri(this.target, gl.TEXTURE_WRAP_S, wrapS);\n }\n }\n if (props.wrapT !== undefined) {\n const wrapT = convertConstant(gl, props.wrapT);\n if (wrapT) {\n this.wrapT = props.wrapT;\n gl.texParameteri(this.target, gl.TEXTURE_WRAP_T, wrapT);\n }\n }\n }\n\n bind(unit) {\n if (!this.allocated) {\n return;\n }\n if (this.texture) {\n const gl = this.gl;\n gl.activeTexture(gl[\"TEXTURE\" + unit]);\n gl.bindTexture(this.target, this.texture);\n return true;\n }\n return false;\n }\n\n unbind(unit) {\n if (!this.allocated) {\n return;\n }\n if (this.texture) {\n const gl = this.gl;\n gl.activeTexture(gl[\"TEXTURE\" + unit]);\n gl.bindTexture(this.target, null);\n }\n }\n\n destroy() {\n if (!this.allocated) {\n return;\n }\n if (this.texture) {\n this.gl.deleteTexture(this.texture);\n this.texture = null;\n }\n }\n}\n\nfunction getInternalFormat(gl, internalFormatName, glFormat, glType, encoding, isVideoTexture = false) {\n if (internalFormatName !== null) {\n if (gl[internalFormatName] !== undefined) {\n return gl[internalFormatName];\n }\n console.warn('Attempt to use non-existing WebGL internal format \\'' + internalFormatName + '\\'');\n }\n let internalFormat = glFormat;\n if (glFormat === gl.RED) {\n if (glType === gl.FLOAT) internalFormat = gl.R32F;\n if (glType === gl.HALF_FLOAT) internalFormat = gl.R16F;\n if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.R8;\n }\n if (glFormat === gl.RG) {\n if (glType === gl.FLOAT) internalFormat = gl.RG32F;\n if (glType === gl.HALF_FLOAT) internalFormat = gl.RG16F;\n if (glType === gl.UNSIGNED_BYTE) internalFormat = gl.RG8;\n }\n if (glFormat === gl.RGBA) {\n if (glType === gl.FLOAT) internalFormat = gl.RGBA32F;\n if (glType === gl.HALF_FLOAT) internalFormat = gl.RGBA16F;\n if (glType === gl.UNSIGNED_BYTE) internalFormat = (encoding === sRGBEncoding && isVideoTexture === false) ? gl.SRGB8_ALPHA8 : gl.RGBA8;\n if (glType === gl.UNSIGNED_SHORT_4_4_4_4) internalFormat = gl.RGBA4;\n if (glType === gl.UNSIGNED_SHORT_5_5_5_1) internalFormat = gl.RGB5_A1;\n }\n if (internalFormat === gl.R16F || internalFormat === gl.R32F ||\n internalFormat === gl.RG16F || internalFormat === gl.RG32F ||\n internalFormat === gl.RGBA16F || internalFormat === gl.RGBA32F) {\n getExtension(gl, 'EXT_color_buffer_float');\n }\n return internalFormat;\n}\n\nfunction filterFallback(gl, f) {\n if (f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter) {\n return gl.NEAREST;\n }\n return gl.LINEAR;\n\n}\n\nfunction ensureImageSizePowerOfTwo$1(image) {\n if (!isPowerOfTwo$1(image.width) || !isPowerOfTwo$1(image.height)) {\n const canvas = document.createElement(\"canvas\");\n canvas.width = nextHighestPowerOfTwo$1(image.width);\n canvas.height = nextHighestPowerOfTwo$1(image.height);\n const ctx = canvas.getContext(\"2d\");\n ctx.drawImage(image,\n 0, 0, image.width, image.height,\n 0, 0, canvas.width, canvas.height);\n image = canvas;\n }\n return image;\n}\n\nfunction isPowerOfTwo$1(x) {\n return (x & (x - 1)) === 0;\n}\n\nfunction nextHighestPowerOfTwo$1(x) {\n --x;\n for (let i = 1; i < 32; i <<= 1) {\n x = x | x >> i;\n }\n return x + 1;\n}\n\n/**\n * @desc A 2D texture map.\n *\n * * Textures are attached to {@link Material}s, which are attached to {@link Mesh}es.\n * * To create a Texture from an image file, set {@link Texture#src} to the image file path.\n * * To create a Texture from an HTMLImageElement, set the Texture's {@link Texture#image} to the HTMLImageElement.\n *\n * ## Usage\n *\n * In this example we have a Mesh with a {@link PhongMaterial} which applies diffuse {@link Texture}, and a {@link buildTorusGeometry} which builds a {@link ReadableGeometry}.\n *\n * Note that xeokit will ignore {@link PhongMaterial#diffuse} and {@link PhongMaterial#specular}, since we override those\n * with {@link PhongMaterial#diffuseMap} and {@link PhongMaterial#specularMap}. The {@link Texture} pixel colors directly\n * provide the diffuse and specular components for each fragment across the {@link ReadableGeometry} surface.\n *\n * [[Run this example](/examples/index.html#materials_Texture)]\n *\n * ```` javascript\n * import {Viewer, Mesh, buildTorusGeometry,\n * ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 5];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({\n * center: [0, 0, 0],\n * radius: 1.5,\n * tube: 0.5,\n * radialSegments: 32,\n * tubeSegments: 24,\n * arc: Math.PI * 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * ambient: [0.9, 0.3, 0.9],\n * shininess: 30,\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n *````\n */\nclass Texture extends Component {\n\n /**\n @private\n */\n get type() {\n return \"Texture\";\n }\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this Texture as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for this Texture, unique among all components in the parent scene, generated automatically when omitted.\n * @param {String} [cfg.src=null] Path to image file to load into this Texture. See the {@link Texture#src} property for more info.\n * @param {HTMLImageElement} [cfg.image=null] HTML Image object to load into this Texture. See the {@link Texture#image} property for more info.\n * @param {Number} [cfg.minFilter=LinearMipmapLinearFilter] How the texture is sampled when a texel covers less than one pixel.\n * Supported values are {@link LinearMipmapLinearFilter}, {@link LinearMipMapNearestFilter}, {@link NearestMipMapNearestFilter}, {@link NearestMipMapLinearFilter} and {@link LinearMipMapLinearFilter}.\n * @param {Number} [cfg.magFilter=LinearFilter] How the texture is sampled when a texel covers more than one pixel. Supported values are {@link LinearFilter} and {@link NearestFilter}.\n * @param {Number} [cfg.wrapS=RepeatWrapping] Wrap parameter for texture coordinate *S*. Supported values are {@link ClampToEdgeWrapping}, {@link MirroredRepeatWrapping} and {@link RepeatWrapping}.\n * @param {Number} [cfg.wrapT=RepeatWrapping] Wrap parameter for texture coordinate *T*. Supported values are {@link ClampToEdgeWrapping}, {@link MirroredRepeatWrapping} and {@link RepeatWrapping}..\n * @param {Boolean} [cfg.flipY=false] Flips this Texture's source data along its vertical axis when ````true````.\n * @param {Number} [cfg.encoding=LinearEncoding] Encoding format. Supported values are {@link LinearEncoding} and {@link sRGBEncoding}.\n * @param {Number[]} [cfg.translate=[0,0]] 2D translation vector that will be added to texture's *S* and *T* coordinates.\n * @param {Number[]} [cfg.scale=[1,1]] 2D scaling vector that will be applied to texture's *S* and *T* coordinates.\n * @param {Number} [cfg.rotate=0] Rotation, in degrees, that will be applied to texture's *S* and *T* coordinates.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n texture: new Texture2D({gl: this.scene.canvas.gl}),\n matrix: math.identityMat4(),\n hasMatrix: (cfg.translate && (cfg.translate[0] !== 0 || cfg.translate[1] !== 0)) || (!!cfg.rotate) || (cfg.scale && (cfg.scale[0] !== 0 || cfg.scale[1] !== 0)),\n minFilter: this._checkMinFilter(cfg.minFilter),\n magFilter: this._checkMagFilter(cfg.magFilter),\n wrapS: this._checkWrapS(cfg.wrapS),\n wrapT: this._checkWrapT(cfg.wrapT),\n flipY: this._checkFlipY(cfg.flipY),\n encoding: this._checkEncoding(cfg.encoding)\n });\n\n // Data source\n\n this._src = null;\n this._image = null;\n\n // Transformation\n\n this._translate = math.vec2([0, 0]);\n this._scale = math.vec2([1, 1]);\n this._rotate = math.vec2([0, 0]);\n\n this._matrixDirty = false;\n\n // Transform\n\n this.translate = cfg.translate;\n this.scale = cfg.scale;\n this.rotate = cfg.rotate;\n\n // Data source\n\n if (cfg.src) {\n this.src = cfg.src; // Image file\n } else if (cfg.image) {\n this.image = cfg.image; // Image object\n }\n\n stats.memory.textures++;\n }\n\n _checkMinFilter(value) {\n value = value || LinearMipMapLinearFilter;\n if (value !== LinearFilter &&\n value !== LinearMipMapNearestFilter &&\n value !== LinearMipMapLinearFilter &&\n value !== NearestMipMapLinearFilter &&\n value !== NearestMipMapNearestFilter) {\n this.error(\"Unsupported value for 'minFilter' - supported values are LinearFilter, LinearMipMapNearestFilter, NearestMipMapNearestFilter, \" +\n \"NearestMipMapLinearFilter and LinearMipMapLinearFilter. Defaulting to LinearMipMapLinearFilter.\");\n value = LinearMipMapLinearFilter;\n }\n return value;\n }\n\n _checkMagFilter(value) {\n value = value || LinearFilter;\n if (value !== LinearFilter && value !== NearestFilter) {\n this.error(\"Unsupported value for 'magFilter' - supported values are LinearFilter and NearestFilter. Defaulting to LinearFilter.\");\n value = LinearFilter;\n }\n return value;\n }\n\n _checkWrapS(value) {\n value = value || RepeatWrapping;\n if (value !== ClampToEdgeWrapping && value !== MirroredRepeatWrapping && value !== RepeatWrapping) {\n this.error(\"Unsupported value for 'wrapS' - supported values are ClampToEdgeWrapping, MirroredRepeatWrapping and RepeatWrapping. Defaulting to RepeatWrapping.\");\n value = RepeatWrapping;\n }\n return value;\n }\n\n _checkWrapT(value) {\n value = value || RepeatWrapping;\n if (value !== ClampToEdgeWrapping && value !== MirroredRepeatWrapping && value !== RepeatWrapping) {\n this.error(\"Unsupported value for 'wrapT' - supported values are ClampToEdgeWrapping, MirroredRepeatWrapping and RepeatWrapping. Defaulting to RepeatWrapping.\");\n value = RepeatWrapping;\n }\n return value;\n }\n\n _checkFlipY(value) {\n return !!value;\n }\n\n _checkEncoding(value) {\n value = value || LinearEncoding;\n if (value !== LinearEncoding && value !== sRGBEncoding) {\n this.error(\"Unsupported value for 'encoding' - supported values are LinearEncoding and sRGBEncoding. Defaulting to LinearEncoding.\");\n value = LinearEncoding;\n }\n return value;\n }\n\n _webglContextRestored() {\n this._state.texture = new Texture2D({gl: this.scene.canvas.gl});\n if (this._image) {\n this.image = this._image;\n } else if (this._src) {\n this.src = this._src;\n }\n }\n\n _update() {\n const state = this._state;\n if (this._matrixDirty) {\n let matrix;\n let t;\n if (this._translate[0] !== 0 || this._translate[1] !== 0) {\n matrix = math.translationMat4v([this._translate[0], this._translate[1], 0], this._state.matrix);\n }\n if (this._scale[0] !== 1 || this._scale[1] !== 1) {\n t = math.scalingMat4v([this._scale[0], this._scale[1], 1]);\n matrix = matrix ? math.mulMat4(matrix, t) : t;\n }\n if (this._rotate !== 0) {\n t = math.rotationMat4v(this._rotate * 0.0174532925, [0, 0, 1]);\n matrix = matrix ? math.mulMat4(matrix, t) : t;\n }\n if (matrix) {\n state.matrix = matrix;\n }\n this._matrixDirty = false;\n }\n this.glRedraw();\n }\n\n\n /**\n * Sets an HTML DOM Image object to source this Texture from.\n *\n * Sets {@link Texture#src} null.\n *\n * @type {HTMLImageElement}\n */\n set image(value) {\n this._image = ensureImageSizePowerOfTwo$1(value);\n this._image.crossOrigin = \"Anonymous\";\n this._state.texture.setImage(this._image, this._state);\n this._src = null;\n this.glRedraw();\n }\n\n /**\n * Gets HTML DOM Image object this Texture is sourced from, if any.\n *\n * Returns null if not set.\n *\n * @type {HTMLImageElement}\n */\n get image() {\n return this._image;\n }\n\n /**\n * Sets path to an image file to source this Texture from.\n *\n * Sets {@link Texture#image} null.\n *\n * @type {String}\n */\n set src(src) {\n this.scene.loading++;\n this.scene.canvas.spinner.processes++;\n const self = this;\n let image = new Image();\n image.onload = function () {\n image = ensureImageSizePowerOfTwo$1(image);\n self._state.texture.setImage(image, self._state);\n self.scene.loading--;\n self.glRedraw();\n self.scene.canvas.spinner.processes--;\n };\n image.src = src;\n this._src = src;\n this._image = null;\n }\n\n /**\n * Gets path to the image file this Texture from, if any.\n *\n * Returns null if not set.\n *\n * @type {String}\n */\n get src() {\n return this._src;\n }\n\n /**\n * Sets the 2D translation vector added to this Texture's *S* and *T* UV coordinates.\n *\n * Default value is ````[0, 0]````.\n *\n * @type {Number[]}\n */\n set translate(value) {\n this._translate.set(value || [0, 0]);\n this._matrixDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets the 2D translation vector added to this Texture's *S* and *T* UV coordinates.\n *\n * Default value is ````[0, 0]````.\n *\n * @type {Number[]}\n */\n get translate() {\n return this._translate;\n }\n\n /**\n * Sets the 2D scaling vector that will be applied to this Texture's *S* and *T* UV coordinates.\n *\n * Default value is ````[1, 1]````.\n *\n * @type {Number[]}\n */\n set scale(value) {\n this._scale.set(value || [1, 1]);\n this._matrixDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets the 2D scaling vector that will be applied to this Texture's *S* and *T* UV coordinates.\n *\n * Default value is ````[1, 1]````.\n *\n * @type {Number[]}\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the rotation angles, in degrees, that will be applied to this Texture's *S* and *T* UV coordinates.\n *\n * Default value is ````0````.\n *\n * @type {Number}\n */\n set rotate(value) {\n value = value || 0;\n if (this._rotate === value) {\n return;\n }\n this._rotate = value;\n this._matrixDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets the rotation angles, in degrees, that will be applied to this Texture's *S* and *T* UV coordinates.\n *\n * Default value is ````0````.\n *\n * @type {Number}\n */\n get rotate() {\n return this._rotate;\n }\n\n /**\n * Gets how this Texture is sampled when a texel covers less than one pixel.\n *\n * Options are:\n *\n * * NearestFilter - Uses the value of the texture element that is nearest\n * (in Manhattan distance) to the center of the pixel being textured.\n *\n * * LinearFilter - Uses the weighted average of the four texture elements that are\n * closest to the center of the pixel being textured.\n *\n * * NearestMipMapNearestFilter - Chooses the mipmap that most closely matches the\n * size of the pixel being textured and uses the \"nearest\" criterion (the texture\n * element nearest to the center of the pixel) to produce a texture value.\n *\n * * LinearMipMapNearestFilter - Chooses the mipmap that most closely matches the size of\n * the pixel being textured and uses the \"linear\" criterion (a weighted average of the\n * four texture elements that are closest to the center of the pixel) to produce a\n * texture value.\n *\n * * NearestMipMapLinearFilter - Chooses the two mipmaps that most closely\n * match the size of the pixel being textured and uses the \"nearest\" criterion\n * (the texture element nearest to the center of the pixel) to produce a texture\n * value from each mipmap. The final texture value is a weighted average of those two\n * values.\n *\n * * LinearMipMapLinearFilter - (default) - Chooses the two mipmaps that most closely match the size\n * of the pixel being textured and uses the \"linear\" criterion (a weighted average\n * of the four texture elements that are closest to the center of the pixel) to\n * produce a texture value from each mipmap. The final texture value is a weighted\n * average of those two values.\n *\n * Default value is LinearMipMapLinearFilter.\n *\n * @type {Number}\n */\n get minFilter() {\n return this._state.minFilter;\n }\n\n /**\n * Gets how this Texture is sampled when a texel covers more than one pixel.\n *\n * * NearestFilter - Uses the value of the texture element that is nearest\n * (in Manhattan distance) to the center of the pixel being textured.\n * * LinearFilter - (default) - Uses the weighted average of the four texture elements that are\n * closest to the center of the pixel being textured.\n *\n * Default value is LinearMipMapLinearFilter.\n *\n * @type {Number}\n */\n get magFilter() {\n return this._state.magFilter;\n }\n\n /**\n * Gets the wrap parameter for this Texture's *S* coordinate.\n *\n * Values can be:\n *\n * * ClampToEdgeWrapping - causes *S* coordinates to be clamped to the size of the texture.\n * * MirroredRepeatWrapping - causes the *S* coordinate to be set to the fractional part of the texture coordinate\n * if the integer part of *S* is even; if the integer part of *S* is odd, then the *S* texture coordinate is\n * set to *1 - frac ⁡ S* , where *frac ⁡ S* represents the fractional part of *S*.\n * * RepeatWrapping - (default) - causes the integer part of the *S* coordinate to be ignored; xeokit uses only the\n * fractional part, thereby creating a repeating pattern.\n *\n * Default value is RepeatWrapping.\n *\n * @type {Number}\n */\n get wrapS() {\n return this._state.wrapS;\n }\n\n /**\n * Gets the wrap parameter for this Texture's *T* coordinate.\n *\n * Values can be:\n *\n * * ClampToEdgeWrapping - causes *S* coordinates to be clamped to the size of the texture.\n * * MirroredRepeatWrapping - causes the *S* coordinate to be set to the fractional part of the texture coordinate\n * if the integer part of *S* is even; if the integer part of *S* is odd, then the *S* texture coordinate is\n * set to *1 - frac ⁡ S* , where *frac ⁡ S* represents the fractional part of *S*.\n * * RepeatWrapping - (default) - causes the integer part of the *S* coordinate to be ignored; xeokit uses only the\n * fractional part, thereby creating a repeating pattern.\n *\n * Default value is RepeatWrapping.\n *\n * @type {Number}\n */\n get wrapT() {\n return this._state.wrapT;\n }\n\n /**\n * Gets if this Texture's source data is flipped along its vertical axis.\n *\n * @type {Number}\n */\n get flipY() {\n return this._state.flipY;\n }\n\n /**\n * Gets the Texture's encoding format.\n *\n * @type {Number}\n */\n get encoding() {\n return this._state.encoding;\n }\n\n /**\n * Destroys this Texture\n */\n destroy() {\n super.destroy();\n if (this._state.texture) {\n this._state.texture.destroy();\n }\n this._state.destroy();\n stats.memory.textures--;\n }\n}\n\n/**\n * @desc Configures Fresnel effects for {@link PhongMaterial}s.\n *\n * Fresnels are attached to {@link PhongMaterial}s, which are attached to {@link Mesh}es.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with a {@link PhongMaterial} that applies a Fresnel to its alpha channel to give a glasss-like effect.\n *\n * [[Run this example](/examples/index.html#materials_Fresnel)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildTorusGeometry,\n * ReadableGeometry, PhongMaterial, Texture, Fresnel} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({\n * center: [0, 0, 0],\n * radius: 1.5,\n * tube: 0.5,\n * radialSegments: 32,\n * tubeSegments: 24,\n * arc: Math.PI * 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * alpha: 0.9,\n * alphaMode: \"blend\",\n * ambient: [0.0, 0.0, 0.0],\n * shininess: 30,\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * alphaFresnel: new Fresnel(viewer.scene, {\nv edgeBias: 0.2,\n * centerBias: 0.8,\n * edgeColor: [1.0, 1.0, 1.0],\n * centerColor: [0.0, 0.0, 0.0],\n * power: 2\n * })\n * })\n * });\n * ````\n */\nclass Fresnel extends Component {\n\n /**\n * JavaScript class name for this Component.\n *\n * @type {String}\n */\n get type() {\n return \"Fresnel\";\n }\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this Fresnel as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent scene, generated automatically when omitted.\n * @param {Number[]} [cfg.edgeColor=[ 0.0, 0.0, 0.0 ]] Color used on edges.\n * @param {Number[]} [cfg.centerColor=[ 1.0, 1.0, 1.0 ]] Color used on center.\n * @param {Number} [cfg.edgeBias=0] Bias at the edge.\n * @param {Number} [cfg.centerBias=1] Bias at the center.\n * @param {Number} [cfg.power=0] The power.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({\n edgeColor: math.vec3([0, 0, 0]),\n centerColor: math.vec3([1, 1, 1]),\n edgeBias: 0,\n centerBias: 1,\n power: 1\n });\n\n this.edgeColor = cfg.edgeColor;\n this.centerColor = cfg.centerColor;\n this.edgeBias = cfg.edgeBias;\n this.centerBias = cfg.centerBias;\n this.power = cfg.power;\n }\n\n /**\n * Sets the Fresnel's edge color.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @type {Number[]}\n */\n set edgeColor(value) {\n this._state.edgeColor.set(value || [0.0, 0.0, 0.0]);\n this.glRedraw();\n }\n\n /**\n * Gets the Fresnel's edge color.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @type {Number[]}\n */\n get edgeColor() {\n return this._state.edgeColor;\n }\n\n /**\n * Sets the Fresnel's center color.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @type {Number[]}\n */\n set centerColor(value) {\n this._state.centerColor.set(value || [1.0, 1.0, 1.0]);\n this.glRedraw();\n }\n\n /**\n * Gets the Fresnel's center color.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @type {Number[]}\n */\n get centerColor() {\n return this._state.centerColor;\n }\n\n /**\n * Sets the Fresnel's edge bias.\n *\n * Default value is ````0````.\n *\n * @type {Number}\n */\n set edgeBias(value) {\n this._state.edgeBias = value || 0;\n this.glRedraw();\n }\n\n /**\n * Gets the Fresnel's edge bias.\n *\n * Default value is ````0````.\n *\n * @type {Number}\n */\n get edgeBias() {\n return this._state.edgeBias;\n }\n\n /**\n * Sets the Fresnel's center bias.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n set centerBias(value) {\n this._state.centerBias = (value !== undefined && value !== null) ? value : 1;\n this.glRedraw();\n }\n\n /**\n * Gets the Fresnel's center bias.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n get centerBias() {\n return this._state.centerBias;\n }\n\n /**\n * Sets the Fresnel's power.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n set power(value) {\n this._state.power = (value !== undefined && value !== null) ? value : 1;\n this.glRedraw();\n }\n\n /**\n * Gets the Fresnel's power.\n *\n * Default value is ````1````.\n *\n * @type {Number}\n */\n get power() {\n return this._state.power;\n }\n\n /**\n * Destroys this Fresnel.\n */\n destroy() {\n super.destroy();\n this._state.destroy();\n }\n}\n\nconst memoryStats = stats.memory;\nconst tempAABB$2 = math.AABB3();\n\n/**\n * @desc A {@link Geometry} that keeps its geometry data solely in GPU memory, without retaining it in browser memory.\n *\n * VBOGeometry uses less memory than {@link ReadableGeometry}, which keeps its geometry data in both browser and GPU memory.\n *\n * ## Usage\n *\n * Creating a {@link Mesh} with a VBOGeometry that defines a single triangle, plus a {@link PhongMaterial} with diffuse {@link Texture}:\n *\n * [[Run this example](/examples/index.html#geometry_VBOGeometry)]\n *\n * ````javascript\n * import {Viewer, Mesh, VBOGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * new Mesh(viewer.scene, {\n * geometry: new VBOGeometry(viewer.scene, {\n * primitive: \"triangles\",\n * positions: [0.0, 3, 0.0, -3, -3, 0.0, 3, -3, 0.0],\n * normals: [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0],\n * uv: [0.0, 0.0, 0.5, 1.0, 1.0, 0.0],\n * indices: [0, 1, 2]\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * backfaces: true\n * })\n * });\n * ````\n */\nclass VBOGeometry extends Geometry {\n\n /**\n @private\n */\n get type() {\n return \"VBOGeometry\";\n }\n\n /**\n * @private\n * @returns {Boolean}\n */\n get isVBOGeometry() {\n return true;\n }\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {String} [cfg.primitive=\"triangles\"] The primitive type. Accepted values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'.\n * @param {Number[]} [cfg.positions] Positions array.\n * @param {Number[]} [cfg.normals] Vertex normal vectors array.\n * @param {Number[]} [cfg.uv] UVs array.\n * @param {Number[]} [cfg.colors] Vertex colors.\n * @param {Number[]} [cfg.indices] Indices array.\n * @param {Number} [cfg.edgeThreshold=10] When autogenerating edges for supporting {@link Drawable#edges}, this indicates the threshold angle (in degrees) between the face normals of adjacent triangles below which the edge is discarded.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._state = new RenderState({ // Arrays for emphasis effects are got from xeokit.GeometryLite friend methods\n compressGeometry: true,\n primitive: null, // WebGL enum\n primitiveName: null, // String\n positionsDecodeMatrix: null, // Set when compressGeometry == true\n uvDecodeMatrix: null, // Set when compressGeometry == true\n positionsBuf: null,\n normalsBuf: null,\n colorsbuf: null,\n uvBuf: null,\n indicesBuf: null,\n hash: \"\"\n });\n\n this._numTriangles = 0;\n\n this._edgeThreshold = cfg.edgeThreshold || 10.0;\n this._aabb = null;\n this._obb = math.OBB3();\n\n const state = this._state;\n const gl = this.scene.canvas.gl;\n\n cfg.primitive = cfg.primitive || \"triangles\";\n switch (cfg.primitive) {\n case \"points\":\n state.primitive = gl.POINTS;\n state.primitiveName = cfg.primitive;\n break;\n case \"lines\":\n state.primitive = gl.LINES;\n state.primitiveName = cfg.primitive;\n break;\n case \"line-loop\":\n state.primitive = gl.LINE_LOOP;\n state.primitiveName = cfg.primitive;\n break;\n case \"line-strip\":\n state.primitive = gl.LINE_STRIP;\n state.primitiveName = cfg.primitive;\n break;\n case \"triangles\":\n state.primitive = gl.TRIANGLES;\n state.primitiveName = cfg.primitive;\n break;\n case \"triangle-strip\":\n state.primitive = gl.TRIANGLE_STRIP;\n state.primitiveName = cfg.primitive;\n break;\n case \"triangle-fan\":\n state.primitive = gl.TRIANGLE_FAN;\n state.primitiveName = cfg.primitive;\n break;\n default:\n this.error(\"Unsupported value for 'primitive': '\" + cfg.primitive +\n \"' - supported values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', \" +\n \"'triangle-strip' and 'triangle-fan'. Defaulting to 'triangles'.\");\n state.primitive = gl.TRIANGLES;\n state.primitiveName = cfg.primitive;\n }\n\n if (!cfg.positions) {\n this.error(\"Config expected: positions\");\n return; // TODO: Recover?\n }\n\n if (!cfg.indices) {\n this.error(\"Config expected: indices\");\n return; // TODO: Recover?\n }\n\n var positions;\n\n {\n const positionsDecodeMatrix = cfg.positionsDecodeMatrix;\n\n if (positionsDecodeMatrix) ; else {\n\n // Uncompressed positions\n\n const bounds = geometryCompressionUtils.getPositionsBounds(cfg.positions);\n const result = geometryCompressionUtils.compressPositions(cfg.positions, bounds.min, bounds.max);\n positions = result.quantized;\n state.positionsDecodeMatrix = result.decodeMatrix;\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, positions.length, 3, gl.STATIC_DRAW);\n memoryStats.positions += state.positionsBuf.numItems;\n math.positions3ToAABB3(cfg.positions, this._aabb);\n math.positions3ToAABB3(positions, tempAABB$2, state.positionsDecodeMatrix);\n math.AABB3ToOBB3(tempAABB$2, this._obb);\n }\n }\n\n if (cfg.colors) {\n const colors = cfg.colors.constructor === Float32Array ? cfg.colors : new Float32Array(cfg.colors);\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colors, colors.length, 4, gl.STATIC_DRAW);\n memoryStats.colors += state.colorsBuf.numItems;\n }\n\n if (cfg.uv) {\n const bounds = geometryCompressionUtils.getUVBounds(cfg.uv);\n const result = geometryCompressionUtils.compressUVs(cfg.uv, bounds.min, bounds.max);\n const uv = result.quantized;\n state.uvDecodeMatrix = result.decodeMatrix;\n state.uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uv, uv.length, 2, gl.STATIC_DRAW);\n memoryStats.uvs += state.uvBuf.numItems;\n }\n\n if (cfg.normals) {\n const normals = geometryCompressionUtils.compressNormals(cfg.normals);\n let normalized = state.compressGeometry;\n state.normalsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, normals, normals.length, 3, gl.STATIC_DRAW, normalized);\n memoryStats.normals += state.normalsBuf.numItems;\n }\n\n {\n const indices = (cfg.indices.constructor === Uint32Array || cfg.indices.constructor === Uint16Array) ? cfg.indices : new Uint32Array(cfg.indices);\n state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, indices.length, 1, gl.STATIC_DRAW);\n memoryStats.indices += state.indicesBuf.numItems;\n const edgeIndices = buildEdgeIndices(positions, indices, state.positionsDecodeMatrix, this._edgeThreshold);\n this._edgeIndicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, edgeIndices, edgeIndices.length, 1, gl.STATIC_DRAW);\n\n if (this._state.primitiveName === \"triangles\") {\n this._numTriangles = (cfg.indices.length / 3);\n }\n }\n\n this._buildHash();\n\n memoryStats.meshes++;\n }\n\n _buildHash() {\n const state = this._state;\n const hash = [\"/g\"];\n hash.push(\"/\" + state.primitive + \";\");\n if (state.positionsBuf) {\n hash.push(\"p\");\n }\n if (state.colorsBuf) {\n hash.push(\"c\");\n }\n if (state.normalsBuf || state.autoVertexNormals) {\n hash.push(\"n\");\n }\n if (state.uvBuf) {\n hash.push(\"u\");\n }\n hash.push(\"cp\"); // Always compressed\n hash.push(\";\");\n state.hash = hash.join(\"\");\n }\n\n _getEdgeIndices() {\n return this._edgeIndicesBuf;\n }\n\n /**\n * Gets the primitive type.\n *\n * Possible types are: 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'.\n *\n * @type {String}\n */\n get primitive() {\n return this._state.primitiveName;\n }\n\n /**\n * Gets the local-space axis-aligned 3D boundary (AABB).\n *\n * The AABB is represented by a six-element Float64Array containing the min/max extents of the axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````.\n *\n * @type {Number[]}\n */\n get aabb() {\n return this._aabb;\n }\n\n /**\n * Gets the local-space oriented 3D boundary (OBB).\n *\n * The OBB is represented by a 32-element Float64Array containing the eight vertices of the box, where each vertex is a homogeneous coordinate having [x,y,z,w] elements.\n *\n * @type {Number[]}\n */\n get obb() {\n return this._obb;\n }\n\n /**\n * Approximate number of triangles in this VBOGeometry.\n *\n * Will be zero if {@link VBOGeometry#primitive} is not 'triangles', 'triangle-strip' or 'triangle-fan'.\n *\n * @type {Number}\n */\n get numTriangles() {\n return this._numTriangles;\n }\n\n /** @private */\n _getState() {\n return this._state;\n }\n\n /**\n * Destroys this component.\n */\n destroy() {\n super.destroy();\n const state = this._state;\n if (state.indicesBuf) {\n state.indicesBuf.destroy();\n }\n if (state.positionsBuf) {\n state.positionsBuf.destroy();\n }\n if (state.normalsBuf) {\n state.normalsBuf.destroy();\n }\n if (state.uvBuf) {\n state.uvBuf.destroy();\n }\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n }\n if (this._edgeIndicesBuf) {\n this._edgeIndicesBuf.destroy();\n }\n state.destroy();\n memoryStats.meshes--;\n }\n}\n\n/**\n * @private\n */\nvar K3D = {};\n\nK3D.load = function(path, resp)\n{\n var request = new XMLHttpRequest();\n request.open(\"GET\", path, true);\n request.responseType = \"arraybuffer\";\n request.onload = function(e){resp(e.target.response);};\n request.send();\n};\n\nK3D.save = function(buff, path)\n{\n var dataURI = \"data:application/octet-stream;base64,\" + btoa(K3D.parse._buffToStr(buff));\n window.location.href = dataURI;\n};\n\nK3D.clone = function(o)\n{\n return JSON.parse(JSON.stringify(o));\n};\n\n\n\nK3D.bin = {};\n\nK3D.bin.f = new Float32Array(1);\nK3D.bin.fb = new Uint8Array(K3D.bin.f.buffer);\n\nK3D.bin.rf\t\t= function(buff, off) { var f = K3D.bin.f, fb = K3D.bin.fb; for(var i=0; i<4; i++) fb[i] = buff[off+i]; return f[0]; };\nK3D.bin.rsl\t\t= function(buff, off) { return buff[off] | buff[off+1]<<8; };\nK3D.bin.ril\t\t= function(buff, off) { return buff[off] | buff[off+1]<<8 | buff[off+2]<<16 | buff[off+3]<<24; };\nK3D.bin.rASCII0 = function(buff, off) { var s = \"\"; while(buff[off]!=0) s += String.fromCharCode(buff[off++]); return s; };\n\n\nK3D.bin.wf\t\t= function(buff, off, v) { var f=new Float32Array(buff.buffer, off, 1); f[0]=v; };\nK3D.bin.wsl\t\t= function(buff, off, v) { buff[off]=v; buff[off+1]=v>>8; };\nK3D.bin.wil\t\t= function(buff, off, v) { buff[off]=v; buff[off+1]=v>>8; buff[off+2]=v>>16; buff[off+3]>>24; };\nK3D.parse = {};\n\nK3D.parse._buffToStr = function(buff)\n{\n var a = new Uint8Array(buff);\n var s = \"\";\n for(var i=0; imaxx) maxx = vx;\n if(vymaxy) maxy = vy;\n if(vzmaxz) maxz = vz;\n }\n return {min:{x:minx, y:miny, z:minz}, max:{x:maxx, y:maxy, z:maxz}};\n};\n\n/**\n * @desc Loads {@link Geometry} from 3DS.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with {@link PhongMaterial}, {@link Texture} and a {@link ReadableGeometry} loaded from 3DS.\n *\n * ````javascript\n * import {Viewer, Mesh, load3DSGeometry, ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [40.04, 23.46, 79.06];\n * viewer.scene.camera.look = [-6.48, 13.92, -0.56];\n * viewer.scene.camera.up = [-0.04, 0.98, -0.08];\n *\n * load3DSGeometry(viewer.scene, {\n * src: \"models/3ds/lexus.3ds\",\n * compressGeometry: false\n *\n * }).then(function (geometryCfg) {\n *\n * // Success\n *\n * new Mesh(viewer.scene, {\n *\n * geometry: new ReadableGeometry(viewer.scene, geometryCfg),\n *\n * material: new PhongMaterial(viewer.scene, {\n *\n * emissive: [1, 1, 1],\n * emissiveMap: new Texture({ // .3DS has no normals so relies on emissive illumination\n * src: \"models/3ds/lexus.jpg\"\n * })\n * }),\n *\n * rotation: [-90, 0, 0] // +Z is up for this particular 3DS\n * });\n * }, function () {\n * // Error\n * });\n * ````\n *\n * @function load3DSGeometry\n * @param {Scene} scene Scene we're loading the geometry for.\n * @param {*} cfg Configs, also added to the result object.\n * @param {String} [cfg.src] Path to 3DS file.\n * @returns {Object} Configuration to pass into a {@link Geometry} constructor, containing geometry arrays loaded from the OBJ file.\n */\nfunction load3DSGeometry(scene, cfg = {}) {\n\n return new Promise(function (resolve, reject) {\n\n if (!cfg.src) {\n console.error(\"load3DSGeometry: Parameter expected: src\");\n reject();\n }\n\n var spinner = scene.canvas.spinner;\n spinner.processes++;\n\n utils.loadArraybuffer(cfg.src, function (data) {\n\n if (!data.byteLength) {\n console.error(\"load3DSGeometry: no data loaded\");\n spinner.processes--;\n reject();\n }\n\n var m = K3D.parse.from3DS(data);\t// done !\n\n var mesh = m.edit.objects[0].mesh;\n var positions = mesh.vertices;\n var uv = mesh.uvt;\n var indices = mesh.indices;\n\n spinner.processes--;\n\n resolve(utils.apply(cfg, {\n primitive: \"triangles\",\n positions: positions,\n normals: null,\n uv: uv,\n indices: indices\n }));\n },\n\n function (msg) {\n console.error(\"load3DSGeometry: \" + msg);\n spinner.processes--;\n reject();\n });\n });\n}\n\n/**\n * @desc Loads {@link Geometry} from OBJ.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with {@link MetallicMaterial} and {@link ReadableGeometry} loaded from OBJ.\n *\n * ````javascript\n * import {Viewer, Mesh, loadOBJGeometry, ReadableGeometry,\n * MetallicMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0.57, 1.37, 1.14];\n * viewer.scene.camera.look = [0.04, 0.58, 0.00];\n * viewer.scene.camera.up = [-0.22, 0.84, -0.48];\n *\n * loadOBJGeometry(viewer.scene, {\n *\n * src: \"models/obj/fireHydrant/FireHydrantMesh.obj\",\n * compressGeometry: false\n *\n * }).then(function (geometryCfg) {\n *\n * // Success\n *\n * new Mesh(viewer.scene, {\n *\n * geometry: new ReadableGeometry(viewer.scene, geometryCfg),\n *\n * material: new MetallicMaterial(viewer.scene, {\n *\n * baseColor: [1, 1, 1],\n * metallic: 1.0,\n * roughness: 1.0,\n * \n * baseColorMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Base_Color.png\",\n * encoding: \"sRGB\"\n * }),\n * normalMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Normal_OpenGL.png\"\n * }),\n * roughnessMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Roughness.png\"\n * }),\n * metallicMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Metallic.png\"\n * }),\n * occlusionMap: new Texture(viewer.scene, {\n * src: \"models/obj/fireHydrant/fire_hydrant_Mixed_AO.png\"\n * }),\n * \n * specularF0: 0.7\n * })\n * });\n * }, function () {\n * // Error\n * });\n * ````\n *\n * @function loadOBJGeometry\n * @param {Scene} scene Scene we're loading the geometry for.\n * @param {*} [cfg] Configs, also added to the result object.\n * @param {String} [cfg.src] Path to OBJ file.\n * @returns {Object} Configuration to pass into a {@link Geometry} constructor, containing geometry arrays loaded from the OBJ file.\n */\nfunction loadOBJGeometry(scene, cfg = {}) {\n\n return new Promise(function (resolve, reject) {\n\n if (!cfg.src) {\n console.error(\"loadOBJGeometry: Parameter expected: src\");\n reject();\n }\n\n var spinner = scene.canvas.spinner;\n spinner.processes++;\n\n utils.loadArraybuffer(cfg.src, function (data) {\n\n if (!data.byteLength) {\n console.error(\"loadOBJGeometry: no data loaded\");\n spinner.processes--;\n reject();\n }\n\n var m = K3D.parse.fromOBJ(data);\t// done !\n\n // unwrap simply duplicates some values, so they can be indexed with indices [0,1,2,3 ... ]\n // In some rendering engines, you can have only one index value for vertices, UVs, normals ...,\n // so \"unwrapping\" is a simple solution.\n\n var positions = K3D.edit.unwrap(m.i_verts, m.c_verts, 3);\n var normals = K3D.edit.unwrap(m.i_norms, m.c_norms, 3);\n var uv = K3D.edit.unwrap(m.i_uvt, m.c_uvt, 2);\n var indices = new Int32Array(m.i_verts.length);\n\n for (var i = 0; i < m.i_verts.length; i++) {\n indices[i] = i;\n }\n\n spinner.processes--;\n\n resolve(utils.apply(cfg, {\n primitive: \"triangles\",\n positions: positions,\n normals: normals.length > 0 ? normals : null,\n autoNormals: normals.length === 0,\n uv: uv,\n indices: indices\n }));\n },\n\n function (msg) {\n console.error(\"loadOBJGeometry: \" + msg);\n spinner.processes--;\n reject();\n });\n });\n}\n\n/**\n * @desc Creates a box-shaped lines {@link Geometry}.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with a box-shaped {@link ReadableGeometry} that has lines primitives.\n *\n * [[Run this example](/examples/index.html#geometry_builders_buildBoxLinesGeometry)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildBoxLinesGeometry, ReadableGeometry, PhongMaterial} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildBoxLinesGeometry({\n * center: [0,0,0],\n * xSize: 1, // Half-size on each axis\n * ySize: 1,\n * zSize: 1\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * emissive: [0,1,0]\n * })\n * });\n * ````\n *\n * @function buildBoxLinesGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.center] 3D point indicating the center position.\n * @param {Number} [cfg.xSize=1.0] Half-size on the X-axis.\n * @param {Number} [cfg.ySize=1.0] Half-size on the Y-axis.\n * @param {Number} [cfg.zSize=1.0] Half-size on the Z-axis.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildBoxLinesGeometry(cfg = {}) {\n\n let xSize = cfg.xSize || 1;\n if (xSize < 0) {\n console.error(\"negative xSize not allowed - will invert\");\n xSize *= -1;\n }\n\n let ySize = cfg.ySize || 1;\n if (ySize < 0) {\n console.error(\"negative ySize not allowed - will invert\");\n ySize *= -1;\n }\n\n let zSize = cfg.zSize || 1;\n if (zSize < 0) {\n console.error(\"negative zSize not allowed - will invert\");\n zSize *= -1;\n }\n\n const center = cfg.center;\n const centerX = center ? center[0] : 0;\n const centerY = center ? center[1] : 0;\n const centerZ = center ? center[2] : 0;\n\n const xmin = -xSize + centerX;\n const ymin = -ySize + centerY;\n const zmin = -zSize + centerZ;\n const xmax = xSize + centerX;\n const ymax = ySize + centerY;\n const zmax = zSize + centerZ;\n\n return utils.apply(cfg, {\n primitive: \"lines\",\n positions: [\n xmin, ymin, zmin,\n xmin, ymin, zmax,\n xmin, ymax, zmin,\n xmin, ymax, zmax,\n xmax, ymin, zmin,\n xmax, ymin, zmax,\n xmax, ymax, zmin,\n xmax, ymax, zmax\n ],\n indices: [\n 0, 1,\n 1, 3,\n 3, 2,\n 2, 0,\n 4, 5,\n 5, 7,\n 7, 6,\n 6, 4,\n 0, 4,\n 1, 5,\n 2, 6,\n 3, 7\n ]\n });\n}\n\n/**\n * @desc Creates a box-shaped lines {@link Geometry} from AABB.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with a box-shaped {@link ReadableGeometry} that has lines primitives.\n * This box will be created from AABB of a model.\n *\n * [[Run this example](/examples/index.html#geometry_builders_buildBoxLinesGeometryFromAABB)]\n *\n * ````javascript\n * import {Viewer, Mesh, Node, buildBoxGeometry, buildBoxLinesGeometryFromAABB, ReadableGeometry, PhongMaterial} from \"../../dist/xeokit-sdk.min.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * const boxGeometry = new ReadableGeometry(viewer.scene, buildBoxGeometry({\n * xSize: 1,\n * ySize: 1,\n * zSize: 1\n * }));\n *\n * new Node(viewer.scene, {\n * id: \"table\",\n * isModel: true, // <--------------------- Node represents a model\n * rotation: [0, 50, 0],\n * position: [0, 0, 0],\n * scale: [1, 1, 1],\n *\n * children: [\n *\n * new Mesh(viewer.scene, { // Red table leg\n * id: \"redLeg\",\n * isObject: true, // <---------- Node represents an object\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * geometry: boxGeometry,\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1, 0.3, 0.3]\n * })\n * }),\n *\n * new Mesh(viewer.scene, { // Green table leg\n * id: \"greenLeg\",\n * isObject: true, // <---------- Node represents an object\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * geometry: boxGeometry,\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.3, 1.0, 0.3]\n * })\n * }),\n *\n * new Mesh(viewer.scene, {// Blue table leg\n * id: \"blueLeg\",\n * isObject: true, // <---------- Node represents an object\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * geometry: boxGeometry,\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.3, 0.3, 1.0]\n * })\n * }),\n *\n * new Mesh(viewer.scene, { // Yellow table leg\n * id: \"yellowLeg\",\n * isObject: true, // <---------- Node represents an object\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * geometry: boxGeometry,\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1.0, 1.0, 0.0]\n * })\n * }),\n *\n * new Mesh(viewer.scene, { // Purple table top\n * id: \"tableTop\",\n * isObject: true, // <---------- Node represents an object\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * geometry: boxGeometry,\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [1.0, 0.3, 1.0]\n * })\n * })\n * ]\n * });\n *\n * let aabb = viewer.scene.aabb;\n * console.log(aabb);\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildBoxLinesGeometryFromAABB({\n * id: \"aabb\",\n * aabb: aabb,\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * emissive: [0, 1,]\n * })\n * });\n * ````\n *\n * @function buildBoxLinesGeometryFromAABB\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.aabb] AABB for which box will be created.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildBoxLinesGeometryFromAABB(cfg = {}){\n return buildBoxLinesGeometry({\n id: cfg.id,\n center: [\n (cfg.aabb[0] + cfg.aabb[3]) / 2.0,\n (cfg.aabb[1] + cfg.aabb[4]) / 2.0,\n (cfg.aabb[2] + cfg.aabb[5]) / 2.0,\n ],\n xSize: (Math.abs(cfg.aabb[3] - cfg.aabb[0])) / 2.0,\n ySize: (Math.abs(cfg.aabb[4] - cfg.aabb[1])) / 2.0,\n zSize: (Math.abs(cfg.aabb[5] - cfg.aabb[2])) / 2.0,\n });\n}\n\n/**\n * @desc Creates a grid-shaped {@link Geometry}.\n *\n * ## Usage\n *\n * Creating a {@link Mesh} with a GridGeometry and a {@link PhongMaterial}:\n *\n * [[Run this example](/examples/index.html#geometry_builders_buildGridGeometry)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildGridGeometry, VBOGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 5];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new VBOGeometry(viewer.scene, buildGridGeometry({\n * size: 1000,\n * divisions: 500\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * color: [0.0, 0.0, 0.0],\n * emissive: [0.4, 0.4, 0.4]\n * }),\n * position: [0, -1.6, 0]\n * });\n * ````\n *\n * @function buildGridGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for the {@link Geometry}, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number} [cfg.size=1] Dimension on the X and Z-axis.\n * @param {Number} [cfg.divisions=1] Number of divisions on X and Z axis..\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildGridGeometry(cfg = {}) {\n\n let size = cfg.size || 1;\n if (size < 0) {\n console.error(\"negative size not allowed - will invert\");\n size *= -1;\n }\n\n let divisions = cfg.divisions || 1;\n if (divisions < 0) {\n console.error(\"negative divisions not allowed - will invert\");\n divisions *= -1;\n }\n if (divisions < 1) {\n divisions = 1;\n }\n\n size = size || 10;\n divisions = divisions || 10;\n\n const step = size / divisions;\n const halfSize = size / 2;\n\n const positions = [];\n const indices = [];\n let l = 0;\n\n for (let i = 0, k = -halfSize; i <= divisions; i++, k += step) {\n\n positions.push(-halfSize);\n positions.push(0);\n positions.push(k);\n\n positions.push(halfSize);\n positions.push(0);\n positions.push(k);\n\n positions.push(k);\n positions.push(0);\n positions.push(-halfSize);\n\n positions.push(k);\n positions.push(0);\n positions.push(halfSize);\n\n indices.push(l++);\n indices.push(l++);\n indices.push(l++);\n indices.push(l++);\n }\n\n return utils.apply(cfg, {\n primitive: \"lines\",\n positions: positions,\n indices: indices\n });\n}\n\n/**\n * @desc Creates a plane-shaped {@link Geometry}.\n *\n * ## Usage\n *\n * Creating a {@link Mesh} with a PlaneGeometry and a {@link PhongMaterial} with diffuse {@link Texture}:\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#buildPlaneGeometry)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildPlaneGeometry, ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 5];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildPlaneGeometry({\n * center: [0,0,0],\n * xSize: 2,\n * zSize: 2,\n * xSegments: 10,\n * zSegments: 10\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n * ````\n *\n * @function buildPlaneGeometry\n * @param {*} [cfg] Configs\n * @param {Number[]} [cfg.center] 3D point indicating the center position.\n * @param {String} [cfg.id] Optional ID for the {@link Geometry}, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number} [cfg.xSize=1] Dimension on the X-axis.\n * @param {Number} [cfg.zSize=1] Dimension on the Z-axis.\n * @param {Number} [cfg.xSegments=1] Number of segments on the X-axis.\n * @param {Number} [cfg.zSegments=1] Number of segments on the Z-axis.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildPlaneGeometry(cfg = {}) {\n\n let xSize = cfg.xSize || 1;\n if (xSize < 0) {\n console.error(\"negative xSize not allowed - will invert\");\n xSize *= -1;\n }\n\n let zSize = cfg.zSize || 1;\n if (zSize < 0) {\n console.error(\"negative zSize not allowed - will invert\");\n zSize *= -1;\n }\n\n let xSegments = cfg.xSegments || 1;\n if (xSegments < 0) {\n console.error(\"negative xSegments not allowed - will invert\");\n xSegments *= -1;\n }\n if (xSegments < 1) {\n xSegments = 1;\n }\n\n let zSegments = cfg.xSegments || 1;\n if (zSegments < 0) {\n console.error(\"negative zSegments not allowed - will invert\");\n zSegments *= -1;\n }\n if (zSegments < 1) {\n zSegments = 1;\n }\n\n const center = cfg.center;\n const centerX = center ? center[0] : 0;\n const centerY = center ? center[1] : 0;\n const centerZ = center ? center[2] : 0;\n\n const halfWidth = xSize / 2;\n const halfHeight = zSize / 2;\n\n const planeX = Math.floor(xSegments) || 1;\n const planeZ = Math.floor(zSegments) || 1;\n\n const planeX1 = planeX + 1;\n const planeZ1 = planeZ + 1;\n\n const segmentWidth = xSize / planeX;\n const segmentHeight = zSize / planeZ;\n\n const positions = new Float32Array(planeX1 * planeZ1 * 3);\n const normals = new Float32Array(planeX1 * planeZ1 * 3);\n const uvs = new Float32Array(planeX1 * planeZ1 * 2);\n\n let offset = 0;\n let offset2 = 0;\n\n let iz;\n let ix;\n let x;\n let a;\n let b;\n let c;\n let d;\n\n for (iz = 0; iz < planeZ1; iz++) {\n\n const z = iz * segmentHeight - halfHeight;\n\n for (ix = 0; ix < planeX1; ix++) {\n\n x = ix * segmentWidth - halfWidth;\n\n positions[offset] = x + centerX;\n positions[offset + 1] = centerY;\n positions[offset + 2] = -z + centerZ;\n\n normals[offset + 2] = -1;\n\n uvs[offset2] = (ix) / planeX;\n uvs[offset2 + 1] = ((planeZ - iz) / planeZ);\n\n offset += 3;\n offset2 += 2;\n }\n }\n\n offset = 0;\n\n const indices = new ((positions.length / 3) > 65535 ? Uint32Array : Uint16Array)(planeX * planeZ * 6);\n\n for (iz = 0; iz < planeZ; iz++) {\n\n for (ix = 0; ix < planeX; ix++) {\n\n a = ix + planeX1 * iz;\n b = ix + planeX1 * (iz + 1);\n c = (ix + 1) + planeX1 * (iz + 1);\n d = (ix + 1) + planeX1 * iz;\n\n indices[offset] = d;\n indices[offset + 1] = b;\n indices[offset + 2] = a;\n\n indices[offset + 3] = d;\n indices[offset + 4] = c;\n indices[offset + 5] = b;\n\n offset += 6;\n }\n }\n\n return utils.apply(cfg, {\n positions: positions,\n normals: normals,\n uv: uvs,\n indices: indices\n });\n}\n\n/**\n * @desc Creates a torus-shaped {@link Geometry}.\n *\n * ## Usage\n * Creating a {@link Mesh} with a torus-shaped {@link ReadableGeometry} :\n *\n * [[Run this example](/examples/index.html#geometry_builders_buildTorusGeometry)]\n * \n * ````javascript\n * import {Viewer, Mesh, buildTorusGeometry, ReadableGeometry, PhongMaterial, Texture} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 5];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({\n * center: [0,0,0],\n * radius: 1.0,\n * tube: 0.5,\n * radialSegments: 32,\n * tubeSegments: 24,\n * arc: Math.PI * 2.0\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n * ````\n *\n * @function buildTorusGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for the {@link Geometry}, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.center] 3D point indicating the center position.\n * @param {Number} [cfg.radius=1] The overall radius.\n * @param {Number} [cfg.tube=0.3] The tube radius.\n * @param {Number} [cfg.radialSegments=32] The number of radial segments.\n * @param {Number} [cfg.tubeSegments=24] The number of tubular segments.\n * @param {Number} [cfg.arc=Math.PI*0.5] The length of the arc in radians, where Math.PI*2 is a closed torus.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildTorusGeometry(cfg = {}) {\n\n let radius = cfg.radius || 1;\n if (radius < 0) {\n console.error(\"negative radius not allowed - will invert\");\n radius *= -1;\n }\n radius *= 0.5;\n\n let tube = cfg.tube || 0.3;\n if (tube < 0) {\n console.error(\"negative tube not allowed - will invert\");\n tube *= -1;\n }\n\n let radialSegments = cfg.radialSegments || 32;\n if (radialSegments < 0) {\n console.error(\"negative radialSegments not allowed - will invert\");\n radialSegments *= -1;\n }\n if (radialSegments < 4) {\n radialSegments = 4;\n }\n\n let tubeSegments = cfg.tubeSegments || 24;\n if (tubeSegments < 0) {\n console.error(\"negative tubeSegments not allowed - will invert\");\n tubeSegments *= -1;\n }\n if (tubeSegments < 4) {\n tubeSegments = 4;\n }\n\n let arc = cfg.arc || Math.PI * 2;\n if (arc < 0) {\n console.warn(\"negative arc not allowed - will invert\");\n arc *= -1;\n }\n if (arc > 360) {\n arc = 360;\n }\n\n const center = cfg.center;\n let centerX = center ? center[0] : 0;\n let centerY = center ? center[1] : 0;\n const centerZ = center ? center[2] : 0;\n\n const positions = [];\n const normals = [];\n const uvs = [];\n const indices = [];\n\n let u;\n let v;\n let x;\n let y;\n let z;\n let vec;\n\n let i;\n let j;\n\n for (j = 0; j <= tubeSegments; j++) {\n for (i = 0; i <= radialSegments; i++) {\n\n u = i / radialSegments * arc;\n v = 0.785398 + (j / tubeSegments * Math.PI * 2);\n\n centerX = radius * Math.cos(u);\n centerY = radius * Math.sin(u);\n\n x = (radius + tube * Math.cos(v)) * Math.cos(u);\n y = (radius + tube * Math.cos(v)) * Math.sin(u);\n z = tube * Math.sin(v);\n\n positions.push(x + centerX);\n positions.push(y + centerY);\n positions.push(z + centerZ);\n\n uvs.push(1 - (i / radialSegments));\n uvs.push((j / tubeSegments));\n\n vec = math.normalizeVec3(math.subVec3([x, y, z], [centerX, centerY, centerZ], []), []);\n\n normals.push(vec[0]);\n normals.push(vec[1]);\n normals.push(vec[2]);\n }\n }\n\n let a;\n let b;\n let c;\n let d;\n\n for (j = 1; j <= tubeSegments; j++) {\n for (i = 1; i <= radialSegments; i++) {\n\n a = (radialSegments + 1) * j + i - 1;\n b = (radialSegments + 1) * (j - 1) + i - 1;\n c = (radialSegments + 1) * (j - 1) + i;\n d = (radialSegments + 1) * j + i;\n\n indices.push(a);\n indices.push(b);\n indices.push(c);\n\n indices.push(c);\n indices.push(d);\n indices.push(a);\n }\n }\n\n return utils.apply(cfg, {\n positions: positions,\n normals: normals,\n uv: uvs,\n indices: indices\n });\n}\n\n/**\n * @desc Creates a 3D polyline {@link Geometry}.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with a polyline {@link ReadableGeometry} that has lines primitives.\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#buildPolylineGeometry)]\n *\n * ````javascript\n * //------------------------------------------------------------------------------------------------------------------\n * // Import the modules we need for this example\n * //------------------------------------------------------------------------------------------------------------------\n *\n * import {buildPolylineGeometry, Viewer, Mesh, ReadableGeometry, PhongMaterial} from \"../../dist/xeokit-sdk.min.es.js\";\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a Viewer and arrange the camera\n * //------------------------------------------------------------------------------------------------------------------\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [0, 0, 8];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a mesh with polyline shape\n * //------------------------------------------------------------------------------------------------------------------\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildPolylineGeometry({\n * points: [\n * 0, 2.83654, 0,\n * -0.665144, 1.152063, 0,\n * -2.456516, 1.41827, 0,\n * -1.330288, 0, 0,\n * -2.456516, -1.41827, 0,\n * -0.665144, -1.152063, 0,\n * 0, -2.83654, 0,\n * 0.665144, -1.152063, 0,\n * 2.456516, -1.41827, 0,\n * 1.330288, 0, 0,\n * 2.456516, 1.41827, 0,\n * 0.665144, 1.152063, 0,\n * 0, 2.83654, 0,\n * ]\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * emissive: [0, 1,]\n * })\n * });\n * ````\n *\n * @function buildPolylineGeometry\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.points] 3D points indicating vertices position.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildPolylineGeometry(cfg = {}) {\n\n if (cfg.points.length % 3 !== 0) {\n throw \"Size of points array for given polyline should be divisible by 3\";\n }\n let numberOfPoints = cfg.points.length / 3;\n if (numberOfPoints < 2) {\n throw \"There should be at least 2 points to create a polyline\";\n }\n let indices = [];\n for (let i = 0; i < numberOfPoints - 1; i++) {\n indices.push(i);\n indices.push(i + 1);\n }\n\n return utils.apply(cfg, {\n primitive: \"lines\",\n positions: cfg.points,\n indices: indices,\n });\n}\n\n/**\n * @desc Creates a 3D polyline from curve {@link Geometry}.\n *\n * ## Usage\n *\n * In the example below we'll create a {@link Mesh} with a polyline {@link ReadableGeometry} created from curves.\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#buildPolylineGeometryFromCurve)]\n *\n * ````javascript\n * //------------------------------------------------------------------------------------------------------------------\n * // Import the modules we need for this example\n * //------------------------------------------------------------------------------------------------------------------\n *\n * import {buildPolylineGeometryFromCurve, Viewer, Mesh, PhongMaterial, NavCubePlugin, CubicBezierCurve, SplineCurve, QuadraticBezierCurve, ReadableGeometry} from \"../../dist/xeokit-sdk.min.es.js\";\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a Viewer and arrange the camera\n * //------------------------------------------------------------------------------------------------------------------\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * new NavCubePlugin(viewer, {\n * canvasId: \"myNavCubeCanvas\",\n * visible: true,\n * size: 250,\n * alignment: \"bottomRight\",\n * bottomMargin: 100,\n * rightMargin: 10\n * });\n *\n * viewer.camera.eye = [0, -250, 1];\n * viewer.camera.look = [0, 0, 0];\n * viewer.camera.up = [0, 1, 0];\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a mesh with polyline shape from Spline\n * //------------------------------------------------------------------------------------------------------------------\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildPolylineGeometryFromCurve({\n * id: \"SplineCurve\",\n * curve: new SplineCurve(viewer.scene, {\n * points: [\n * [-65.77614, 0, -88.881992],\n * [90.020852, 0, -61.589088],\n * [-67.766247, 0, -22.071238],\n * [93.148164, 0, -13.826507],\n * [-14.033343, 0, 3.231558],\n * [32.592034, 0, 9.20188],\n * [3.309023, 0, 22.848332],\n * [23.210098, 0, 28.818655],\n * ],\n * }),\n * divisions: 100,\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * emissive: [1, 0, 0]\n * })\n * });\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a mesh with polyline shape from CubicBezier\n * //------------------------------------------------------------------------------------------------------------------\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildPolylineGeometryFromCurve({\n * id: \"CubicBezierCurve\",\n * curve: new CubicBezierCurve(viewer.scene, {\n * v0: [120, 0, 100],\n * v1: [120, 0, 0],\n * v2: [80, 0, 100],\n * v3: [80, 0, 0],\n * }),\n * divisions: 50,\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * emissive: [0, 1, 0]\n * })\n * });\n *\n * //------------------------------------------------------------------------------------------------------------------\n * // Create a mesh with polyline shape from QuadraticBezier\n * //------------------------------------------------------------------------------------------------------------------\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildPolylineGeometryFromCurve({\n * id: \"QuadraticBezierCurve\",\n * curve: new QuadraticBezierCurve(viewer.scene, {\n * v0: [-100, 0, 100],\n * v1: [-50, 0, 150],\n * v2: [-50, 0, 0],\n * }),\n * divisions: 20,\n * })),\n * material: new PhongMaterial(viewer.scene, {\n * emissive: [0, 0, 1]\n * })\n * });\n * ````\n *\n * @function buildPolylineGeometryFromCurve\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Object} [cfg.curve] Curve for which polyline will be created.\n * @param {Number} [cfg.divisions] The number of divisions.\n * @returns {Object} Configuration for a {@link Geometry} subtype.\n */\nfunction buildPolylineGeometryFromCurve(cfg = {}) {\n\n let polylinePoints = cfg.curve.getPoints(cfg.divisions).map(a => [...a]).flat();\n return buildPolylineGeometry({\n id: cfg.id,\n points: polylinePoints\n });\n}\n\n/**\n * A plane-shaped 3D object containing a bitmap image.\n *\n * * Creates a 3D quad containing our bitmap, located and oriented using ````pos````, ````normal```` and ````up```` vectors.\n * * Registered by {@link Bitmap#id} in {@link Scene#bitmaps}.\n * * {@link BCFViewpointsPlugin} will save and load Bitmaps in BCF viewpoints.\n *\n * ## Usage\n *\n * In the example below, we'll load the Schependomlaan model, then use\n * an ````Bitmap```` to show a storey plan next to the model.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_imageInSectionPlane)\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_imageInSectionPlane)]\n *\n * ````javascript\n * import {Viewer, Bitmap, XKTLoaderPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.camera.eye = [-24.65, 21.69, 8.16];\n * viewer.camera.look = [-14.62, 2.16, -1.38];\n * viewer.camera.up = [0.59, 0.57, -0.56];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/Schependomlaan.xkt\",\n * edges: true\n * });\n *\n * new Bitmap(viewer.scene, {\n * src: \"./images/schependomlaanPlanView.png\",\n * visible: true, // Show the Bitmap\n * height: 24.0, // Height of Bitmap\n * pos: [-15, 0, -10], // World-space position of Bitmap's center\n * normal: [0, -1, 0], // Vector perpendicular to Bitmap\n * up: [0, 0, 1], // Direction of Bitmap \"up\"\n * collidable: false, // Bitmap does not contribute to Scene boundary\n * clippable: true, // Bitmap can be clipped by SectionPlanes\n * pickable: true // Allow the ground plane to be picked\n * });\n * ````\n */\nclass Bitmap extends Component {\n\n /**\n * Creates a new Bitmap.\n *\n * Registers the Bitmap in {@link Scene#bitmaps}; causes Scene to fire a \"bitmapCreated\" event.\n *\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this ````Bitmap```` as well.\n * @param {*} [cfg] ````Bitmap```` configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Boolean} [cfg.visible=true] Indicates whether or not this ````Bitmap```` is visible.\n * @param {Number[]} [cfg.pos=[0,0,0]] World-space position of the ````Bitmap````.\n * @param {Number[]} [cfg.normal=[0,0,1]] Normal vector indicating the direction the ````Bitmap```` faces.\n * @param {Number[]} [cfg.up=[0,1,0]] Direction of \"up\" for the ````Bitmap````.\n * @param {Number[]} [cfg.height=1] World-space height of the ````Bitmap````.\n * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] Modelling transform matrix for the ````Bitmap````. Overrides the ````position````, ````height```, ````rotation```` and ````normal```` parameters.\n * @param {Boolean} [cfg.collidable=true] Indicates if the ````Bitmap```` is initially included in boundary calculations.\n * @param {Boolean} [cfg.clippable=true] Indicates if the ````Bitmap```` is initially clippable.\n * @param {Boolean} [cfg.pickable=true] Indicates if the ````Bitmap```` is initially pickable.\n * @param {Number} [cfg.opacity=1.0] ````Bitmap````'s initial opacity factor, multiplies by the rendered fragment alpha.\n * @param {String} [cfg.src] URL of image. Accepted file types are PNG and JPEG.\n * @param {HTMLImageElement} [cfg.image] An ````HTMLImageElement```` to source the image from. Overrides ````src````.\n * @param {String} [cfg.imageData] Image data as a base64 encoded string.\n * @param {String} [cfg.type=\"jpg\"] Image MIME type. Accepted values are \"jpg\" and \"png\". Default is \"jpg\". Normally only needed with ````image```` or ````imageData````. Automatically inferred from file extension of ````src````, if the file has a recognized extension.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._type = cfg.type || (cfg.src ? cfg.src.split('.').pop() : null) || \"jpg\";\n this._pos = math.vec3(cfg.pos || [0, 0, 0]);\n this._up = math.vec3(cfg.up || [0, 1, 0]);\n this._normal = math.vec3(cfg.normal || [0, 0, 1]);\n this._height = cfg.height || 1.0;\n\n this._origin = math.vec3();\n this._rtcPos = math.vec3();\n this._imageSize = math.vec2();\n\n this._texture = new Texture(this, {\n flipY: true\n });\n\n this._image = new Image();\n\n if (this._type !== \"jpg\" && this._type !== \"png\") {\n this.error(`Unsupported type - defaulting to \"jpg\"`);\n this._type = \"jpg\";\n }\n\n this._node = new Node$2(this, {\n matrix: math.inverseMat4(\n math.lookAtMat4v(this._pos, math.subVec3(this._pos, this._normal, math.mat4()),\n this._up,\n math.mat4())),\n children: [\n\n this._bitmapMesh = new Mesh(this, {\n scale: [1, 1, 1],\n rotation: [-90, 0, 0],\n collidable: cfg.collidable,\n pickable: cfg.pickable,\n opacity: cfg.opacity,\n clippable: cfg.clippable,\n\n geometry: new ReadableGeometry(this, buildPlaneGeometry({\n center: [0, 0, 0],\n xSize: 1,\n zSize: 1,\n xSegments: 2,\n zSegments: 2\n })),\n\n material: new PhongMaterial(this, {\n diffuse: [0, 0, 0],\n ambient: [0, 0, 0],\n specular: [0, 0, 0],\n diffuseMap: this._texture,\n emissiveMap: this._texture,\n backfaces: true\n })\n })\n ]\n });\n\n if (cfg.image) {\n this.image = cfg.image;\n } else if (cfg.src) {\n this.src = cfg.src;\n } else if (cfg.imageData) {\n this.imageData = cfg.imageData;\n }\n\n this.scene._bitmapCreated(this);\n }\n\n /**\n * Sets if this ````Bitmap```` is visible or not.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} visible Set ````true```` to make this ````Bitmap```` visible.\n */\n set visible(visible) {\n this._bitmapMesh.visible = visible;\n }\n\n /**\n * Gets if this ````Bitmap```` is visible or not.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} Returns ````true```` if visible.\n */\n get visible() {\n return this._bitmapMesh.visible;\n }\n\n /**\n * Sets an ````HTMLImageElement```` to source the image from.\n *\n * Sets {@link Texture#src} null.\n *\n * You may also need to set {@link Bitmap#type}, if you want to read the image data with {@link Bitmap#imageData}.\n *\n * @type {HTMLImageElement}\n */\n set image(image) {\n this._image = image;\n if (this._image) {\n this._texture.image = this._image;\n this._imageSize[0] = this._image.width;\n this._imageSize[1] = this._image.height;\n this._updateBitmapMeshScale();\n }\n }\n\n /**\n * Gets the ````HTMLImageElement```` the ````Bitmap````'s image is sourced from, if set.\n *\n * Returns null if not set.\n *\n * @type {HTMLImageElement}\n */\n get image() {\n return this._image;\n }\n\n /**\n * Sets an image file path that the ````Bitmap````'s image is sourced from.\n *\n * If the file extension is a recognized MIME type, also sets {@link Bitmap#type} to that MIME type.\n *\n * Accepted file types are PNG and JPEG.\n *\n * @type {String}\n */\n set src(src) {\n if (src) {\n this._image.onload = () => {\n this._texture.image = this._image;\n this._imageSize[0] = this._image.width;\n this._imageSize[1] = this._image.height;\n this._updateBitmapMeshScale();\n };\n this._image.src = src;\n const ext = src.split('.').pop();\n switch (ext) {\n case \"jpeg\":\n case \"jpg\":\n this._type = \"jpg\";\n break;\n case \"png\":\n this._type = \"png\";\n }\n }\n }\n\n /**\n * Gets the image file path that the ````Bitmap````'s image is sourced from, if set.\n *\n * Returns null if not set.\n *\n * @type {String}\n */\n get src() {\n return this._image.src;\n }\n\n /**\n * Sets an image file path that the ````Bitmap````'s image is sourced from.\n *\n * Accepted file types are PNG and JPEG.\n *\n * Sets {@link Texture#image} null.\n *\n * You may also need to set {@link Bitmap#type}, if you want to read the image data with {@link Bitmap#imageData}.\n *\n * @type {String}\n */\n set imageData(imageData) {\n this._image.onload = () => {\n this._texture.image = image;\n this._imageSize[0] = image.width;\n this._imageSize[1] = image.height;\n this._updateBitmapMeshScale();\n };\n this._image.src = imageData;\n }\n\n /**\n * Gets the image file path that the ````Bitmap````'s image is sourced from, if set.\n *\n * Returns null if not set.\n *\n * @type {String}\n */\n get imageData() {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n canvas.width = this._image.width;\n canvas.height = this._image.height;\n context.drawImage(this._image, 0, 0);\n return canvas.toDataURL(this._type === \"jpg\" ? 'image/jpeg' : 'image/png');\n }\n\n /**\n * Sets the MIME type of this Bitmap.\n *\n * This is used by ````Bitmap```` when getting image data with {@link Bitmap#imageData}.\n *\n * Supported values are \"jpg\" and \"png\",\n *\n * Default is \"jpg\".\n *\n * @type {String}\n */\n set type(type) {\n type = type || \"jpg\";\n if (type !== \"png\" || type !== \"jpg\") {\n this.error(\"Unsupported value for `type` - supported types are `jpg` and `png` - defaulting to `jpg`\");\n type = \"jpg\";\n }\n this._type = type;\n }\n\n /**\n * Gets the MIME type of this Bitmap.\n *\n * @type {String}\n */\n get type() {\n return this._type;\n }\n\n /**\n * Gets the World-space position of this ````Bitmap````.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * @returns {Number[]} Current position.\n */\n get pos() {\n return this._pos;\n }\n\n /**\n * Gets the direction of the normal vector that is perpendicular to this ````Bitmap````.\n *\n * @returns {Number[]} value Current normal direction.\n */\n get normal() {\n return this._normal;\n }\n\n /**\n * Gets the \"up\" direction of this ````Bitmap````.\n *\n * @returns {Number[]} value Current \"up\" direction.\n */\n get up() {\n return this._up;\n }\n\n /**\n * Sets the World-space height of the ````Bitmap````.\n *\n * Default value is ````1.0````.\n *\n * @param {Number} height New World-space height of the ````Bitmap````.\n */\n set height(height) {\n this._height = (height === undefined || height === null) ? 1.0 : height;\n if (this._image) {\n this._updateBitmapMeshScale();\n }\n }\n\n /**\n * Gets the World-space height of the ````Bitmap````.\n *\n * Returns {Number} World-space height of the ````Bitmap````.\n */\n get height() {\n return this._height;\n }\n\n /**\n * Sets if this ````Bitmap```` is included in boundary calculations.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set collidable(value) {\n this._bitmapMesh.collidable = (value !== false);\n }\n\n /**\n * Gets if this ````Bitmap```` is included in boundary calculations.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get collidable() {\n return this._bitmapMesh.collidable;\n }\n\n /**\n * Sets if this ````Bitmap```` is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set clippable(value) {\n this._bitmapMesh.clippable = (value !== false);\n }\n\n /**\n * Gets if this ````Bitmap```` is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get clippable() {\n return this._bitmapMesh.clippable;\n }\n\n /**\n * Sets if this ````Bitmap```` is pickable.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set pickable(value) {\n this._bitmapMesh.pickable = (value !== false);\n }\n\n /**\n * Gets if this ````Bitmap```` is pickable.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get pickable() {\n return this._bitmapMesh.pickable;\n }\n\n /**\n * Sets the opacity factor for this ````Bitmap````.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n set opacity(opacity) {\n this._bitmapMesh.opacity = opacity;\n }\n\n /**\n * Gets this ````Bitmap````'s opacity factor.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n get opacity() {\n return this._bitmapMesh.opacity;\n }\n\n /**\n * Destroys this ````Bitmap````.\n *\n * Removes the ```Bitmap```` from {@link Scene#bitmaps}; causes Scene to fire a \"bitmapDestroyed\" event.\n */\n destroy() {\n super.destroy();\n this.scene._bitmapDestroyed(this);\n }\n\n _updateBitmapMeshScale() {\n const aspect = this._imageSize[1] / this._imageSize[0];\n this._bitmapMesh.scale = [this._height / aspect, 1.0, this._height];\n }\n}\n\nconst tempOBB3$1 = math.OBB3();\nconst tempOBB3b = math.OBB3();\nconst tempOBB3c = math.OBB3();\n\n/**\n * A mesh within a {@link SceneModel}.\n *\n * * Created with {@link SceneModel#createMesh}\n * * Belongs to exactly one {@link SceneModelEntity}\n * * Stored by ID in {@link SceneModel#meshes}\n * * Referenced by {@link SceneModelEntity#meshes}\n * * Can have a {@link SceneModelTransform} to dynamically scale, rotate and translate it.\n */\nclass SceneModelMesh {\n\n /**\n * @private\n */\n constructor(model, id, color, opacity, transform, textureSet, layer = null, portionId = 0) {\n\n /**\n * The {@link SceneModel} that owns this SceneModelMesh.\n *\n * @type {SceneModel}\n */\n this.model = model;\n\n /**\n * The {@link SceneModelEntity} that owns this SceneModelMesh.\n *\n * @type {SceneModelEntity}\n */\n this.object = null;\n\n /**\n * @private\n */\n this.parent = null;\n\n /**\n * The {@link SceneModelTransform} that transforms this SceneModelMesh.\n *\n * * This only exists when the SceneModelMesh is instancing its geometry.\n * * These are created with {@link SceneModel#createTransform}\n * * Each of these is also registered in {@link SceneModel#transforms}.\n *\n * @type {SceneModelTransform}\n */\n this.transform = transform;\n\n /**\n * The {@link SceneModelTextureSet} that optionally textures this SceneModelMesh.\n *\n * * This only exists when the SceneModelMesh has texture.\n * * These are created with {@link SceneModel#createTextureSet}\n * * Each of these is also registered in {@link SceneModel#textureSets}.\n *\n * @type {SceneModelTextureSet}\n */\n this.textureSet = textureSet;\n\n this._matrixDirty = false;\n this._matrixUpdateScheduled = false;\n\n /**\n * Unique ID of this SceneModelMesh.\n *\n * The SceneModelMesh is registered against this ID in {@link SceneModel#meshes}.\n */\n this.id = id;\n\n /**\n * @private\n */\n this.obb = null;\n\n this._aabbLocal = null;\n this._aabbWorld = math.AABB3();\n this._aabbWorldDirty = false;\n\n /**\n * @private\n */\n this.layer = layer;\n\n /**\n * @private\n */\n this.portionId = portionId;\n\n this._color = new Uint8Array([color[0], color[1], color[2], opacity]); // [0..255]\n this._colorize = new Uint8Array([color[0], color[1], color[2], opacity]); // [0..255]\n this._colorizing = false;\n this._transparent = (opacity < 255);\n\n /**\n * @private\n */\n this.numTriangles = 0;\n\n /**\n * @private\n * @type {null}\n */\n this.origin = null; // Set By SceneModel\n\n /**\n * The {@link SceneModelEntity} that owns this SceneModelMesh.\n *\n * @type {SceneModelEntity}\n */\n this.entity = null;\n\n if (transform) {\n transform._addMesh(this);\n }\n }\n\n _sceneModelDirty() {\n this._aabbWorldDirty = true;\n this.layer.aabbDirty = true;\n }\n\n _transformDirty() {\n if (!this._matrixDirty && !this._matrixUpdateScheduled) {\n this.model._meshMatrixDirty(this);\n this._matrixDirty = true;\n this._matrixUpdateScheduled = true;\n }\n this._aabbWorldDirty = true;\n this.layer.aabbDirty = true;\n if (this.entity) {\n this.entity._transformDirty();\n }\n }\n\n _updateMatrix() {\n if (this.transform && this._matrixDirty) {\n this.layer.setMatrix(this.portionId, this.transform.worldMatrix);\n }\n this._matrixDirty = false;\n this._matrixUpdateScheduled = false;\n }\n\n _finalize(entityFlags) {\n this.layer.initFlags(this.portionId, entityFlags, this._transparent);\n }\n\n _finalize2() {\n if (this.layer.flushInitFlags) {\n this.layer.flushInitFlags();\n }\n }\n\n _setVisible(entityFlags) {\n this.layer.setVisible(this.portionId, entityFlags, this._transparent);\n }\n\n _setColor(color) {\n this._color[0] = color[0];\n this._color[1] = color[1];\n this._color[2] = color[2];\n if (!this._colorizing) {\n this.layer.setColor(this.portionId, this._color, false);\n }\n }\n\n _setColorize(colorize) {\n const setOpacity = false;\n if (colorize) {\n this._colorize[0] = colorize[0];\n this._colorize[1] = colorize[1];\n this._colorize[2] = colorize[2];\n this.layer.setColor(this.portionId, this._colorize, setOpacity);\n this._colorizing = true;\n } else {\n this.layer.setColor(this.portionId, this._color, setOpacity);\n this._colorizing = false;\n }\n }\n\n _setOpacity(opacity, entityFlags) {\n const newTransparent = (opacity < 255);\n const lastTransparent = this._transparent;\n const changingTransparency = (lastTransparent !== newTransparent);\n this._color[3] = opacity;\n this._colorize[3] = opacity;\n this._transparent = newTransparent;\n if (this._colorizing) {\n this.layer.setColor(this.portionId, this._colorize);\n } else {\n this.layer.setColor(this.portionId, this._color);\n }\n if (changingTransparency) {\n this.layer.setTransparent(this.portionId, entityFlags, newTransparent);\n }\n }\n\n _setOffset(offset) {\n this.layer.setOffset(this.portionId, offset);\n }\n\n _setHighlighted(entityFlags) {\n this.layer.setHighlighted(this.portionId, entityFlags, this._transparent);\n }\n\n _setXRayed(entityFlags) {\n this.layer.setXRayed(this.portionId, entityFlags, this._transparent);\n }\n\n _setSelected(entityFlags) {\n this.layer.setSelected(this.portionId, entityFlags, this._transparent);\n }\n\n _setEdges(entityFlags) {\n this.layer.setEdges(this.portionId, entityFlags, this._transparent);\n }\n\n _setClippable(entityFlags) {\n this.layer.setClippable(this.portionId, entityFlags, this._transparent);\n }\n\n _setCollidable(entityFlags) {\n this.layer.setCollidable(this.portionId, entityFlags);\n }\n\n _setPickable(flags) {\n this.layer.setPickable(this.portionId, flags, this._transparent);\n }\n\n _setCulled(flags) {\n this.layer.setCulled(this.portionId, flags, this._transparent);\n }\n\n /**\n * @private\n */\n canPickTriangle() {\n return false;\n }\n\n /**\n * @private\n */\n drawPickTriangles(renderFlags, frameCtx) {\n // NOP\n }\n\n /**\n * @private\n */\n pickTriangleSurface(pickResult) {\n // NOP\n }\n\n /**\n * @private\n */\n precisionRayPickSurface(worldRayOrigin, worldRayDir, worldSurfacePos, worldSurfaceNormal) {\n return this.layer.precisionRayPickSurface ? this.layer.precisionRayPickSurface(this.portionId, worldRayOrigin, worldRayDir, worldSurfacePos, worldSurfaceNormal) : false;\n }\n\n /**\n * @private\n */\n canPickWorldPos() {\n return true;\n }\n\n /**\n * @private\n */\n drawPickDepths(frameCtx) {\n this.model.drawPickDepths(frameCtx);\n }\n\n /**\n * @private\n */\n drawPickNormals(frameCtx) {\n this.model.drawPickNormals(frameCtx);\n }\n\n /**\n * @private\n */\n delegatePickedEntity() {\n return this.parent;\n }\n\n /**\n * @private\n */\n getEachVertex(callback) {\n this.layer.getEachVertex(this.portionId, callback);\n }\n\n /**\n * @private\n */\n set aabb(aabb) { // Called by SceneModel\n this._aabbLocal = aabb;\n }\n\n /**\n * @private\n */\n get aabb() { // called by SceneModelEntity\n if (this._aabbWorldDirty) {\n math.AABB3ToOBB3(this._aabbLocal, tempOBB3$1);\n if (this.transform) {\n math.transformOBB3(this.transform.worldMatrix, tempOBB3$1, tempOBB3b);\n math.transformOBB3(this.model.worldMatrix, tempOBB3b, tempOBB3c);\n math.OBB3ToAABB3(tempOBB3c, this._aabbWorld);\n } else {\n math.transformOBB3(this.model.worldMatrix, tempOBB3$1, tempOBB3b);\n math.OBB3ToAABB3(tempOBB3b, this._aabbWorld);\n }\n if (this.origin) {\n const origin = this.origin;\n this._aabbWorld[0] += origin[0];\n this._aabbWorld[1] += origin[1];\n this._aabbWorld[2] += origin[2];\n this._aabbWorld[3] += origin[0];\n this._aabbWorld[4] += origin[1];\n this._aabbWorld[5] += origin[2];\n }\n this._aabbWorldDirty = false;\n }\n return this._aabbWorld;\n }\n\n /**\n * @private\n */\n _destroy() {\n this.model.scene._renderer.putPickID(this.pickId);\n }\n}\n\n/**\n * Provides scratch memory for methods like TrianglesBatchingLayer setFlags() and setColors(),\n * so they don't need to allocate temporary arrays that need garbage collection.\n *\n * @private\n */\nclass ScratchMemory {\n\n constructor() {\n this._uint8Arrays = {};\n this._float32Arrays = {};\n }\n\n _clear() {\n this._uint8Arrays = {};\n this._float32Arrays = {};\n }\n\n getUInt8Array(len) {\n let uint8Array = this._uint8Arrays[len];\n if (!uint8Array) {\n uint8Array = new Uint8Array(len);\n this._uint8Arrays[len] = uint8Array;\n }\n return uint8Array;\n }\n\n getFloat32Array(len) {\n let float32Array = this._float32Arrays[len];\n if (!float32Array) {\n float32Array = new Float32Array(len);\n this._float32Arrays[len] = float32Array;\n }\n return float32Array;\n }\n}\n\nconst batchingLayerScratchMemory = new ScratchMemory();\n\nlet countUsers = 0;\n\n/**\n * @private\n */\nfunction getScratchMemory() {\n countUsers++;\n return batchingLayerScratchMemory;\n}\n\n/**\n * @private\n */\nfunction putScratchMemory() {\n if (countUsers === 0) {\n return;\n }\n countUsers--;\n if (countUsers === 0) {\n batchingLayerScratchMemory._clear();\n }\n}\n\n/**\n * @private\n */\nconst RENDER_PASSES = {\n\n // Skipped - suppress rendering\n\n NOT_RENDERED: 0,\n\n // Normal rendering - mutually exclusive modes\n\n COLOR_OPAQUE: 1,\n COLOR_TRANSPARENT: 2,\n\n // Emphasis silhouette rendering - mutually exclusive modes\n\n SILHOUETTE_HIGHLIGHTED: 3,\n SILHOUETTE_SELECTED: 4,\n SILHOUETTE_XRAYED: 5,\n\n // Edges rendering - mutually exclusive modes\n\n EDGES_COLOR_OPAQUE: 6,\n EDGES_COLOR_TRANSPARENT: 7,\n EDGES_HIGHLIGHTED: 8,\n EDGES_SELECTED: 9,\n EDGES_XRAYED: 10,\n\n // Picking\n\n PICK: 11\n};\n\nconst defaultColor$2 = new Float32Array([1, 1, 1, 1]);\nconst edgesDefaultColor = new Float32Array([0, 0, 0, 1]);\n\nconst tempVec4 = math.vec4();\nconst tempVec3a$C = math.vec3();\nconst tempVec3c$t = math.vec3();\nconst tempMat4a$r = math.mat4();\n\n/**\n * @private\n */\nclass VBORenderer {\n constructor(scene, withSAO = false, {instancing = false, edges = false} = {}) {\n this._scene = scene;\n this._withSAO = withSAO;\n this._instancing = instancing;\n this._edges = edges;\n this._hash = this._getHash();\n\n /**\n * Matrices Uniform Block Buffer\n *\n * In shaders, matrices in the Matrices Uniform Block MUST be set in this order:\n * - worldMatrix\n * - viewMatrix\n * - projMatrix\n * - positionsDecodeMatrix\n * - worldNormalMatrix\n * - viewNormalMatrix\n */\n this._matricesUniformBlockBufferBindingPoint = 0;\n\n this._matricesUniformBlockBuffer = this._scene.canvas.gl.createBuffer();\n this._matricesUniformBlockBufferData = new Float32Array(4 * 4 * 6); // there is 6 mat4\n\n /**\n * A Vertex Array Object by Layer\n */\n this._vaoCache = new WeakMap();\n\n this._allocate();\n }\n\n /**\n * Should be overrided by subclasses if it does not only \"depend\" on section planes state.\n * @returns { string }\n */\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n return [\"\"];\n }\n\n _buildFragmentShader() {\n return [\"\"];\n }\n\n _addMatricesUniformBlockLines(src, normals = false) {\n src.push(\"uniform Matrices {\");\n src.push(\" mat4 worldMatrix;\");\n src.push(\" mat4 viewMatrix;\");\n src.push(\" mat4 projMatrix;\");\n src.push(\" mat4 positionsDecodeMatrix;\");\n if (normals) {\n src.push(\" mat4 worldNormalMatrix;\");\n src.push(\" mat4 viewNormalMatrix;\");\n }\n src.push(\"};\");\n return src;\n }\n\n _addRemapClipPosLines(src, viewportSize = 1) {\n src.push(\"uniform vec2 drawingBufferSize;\");\n src.push(\"uniform vec2 pickClipPos;\");\n\n src.push(\"vec4 remapClipPos(vec4 clipPos) {\");\n src.push(\" clipPos.xy /= clipPos.w;\");\n if (viewportSize === 1) {\n src.push(\" clipPos.xy = (clipPos.xy - pickClipPos) * drawingBufferSize;\");\n } else {\n src.push(` clipPos.xy = (clipPos.xy - pickClipPos) * (drawingBufferSize / float(${viewportSize}));`);\n }\n src.push(\" clipPos.xy *= clipPos.w;\");\n src.push(\" return clipPos;\");\n src.push(\"}\");\n return src;\n }\n\n getValid() {\n return this._hash === this._getHash();\n }\n\n setSectionPlanesStateUniforms(layer) {\n const scene = this._scene;\n const {gl} = scene.canvas;\n const {model, layerIndex} = layer;\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n if (scene.crossSections) {\n gl.uniform4fv(this._uSliceColor, scene.crossSections.sliceColor);\n gl.uniform1f(this._uSliceThickness, scene.crossSections.sliceThickness);\n }\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n const origin = layer._state.origin;\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$C);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n }\n\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const lightsState = scene._lightsState;\n\n this._program = new Program(gl, this._buildShader());\n\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n\n const program = this._program;\n\n this._uRenderPass = program.getLocation(\"renderPass\");\n\n this._uColor = program.getLocation(\"color\");\n if (!this._uColor) {\n // some shader may have color as attribute, in this case the uniform must be renamed silhouetteColor\n this._uColor = program.getLocation(\"silhouetteColor\");\n }\n this._uUVDecodeMatrix = program.getLocation(\"uvDecodeMatrix\");\n this._uPickInvisible = program.getLocation(\"pickInvisible\");\n this._uGammaFactor = program.getLocation(\"gammaFactor\");\n\n gl.uniformBlockBinding(\n program.handle,\n gl.getUniformBlockIndex(program.handle, \"Matrices\"),\n this._matricesUniformBlockBufferBindingPoint\n );\n\n this._uShadowViewMatrix = program.getLocation(\"shadowViewMatrix\");\n this._uShadowProjMatrix = program.getLocation(\"shadowProjMatrix\");\n if (scene.logarithmicDepthBufferEnabled) {\n this._uZFar = program.getLocation(\"zFar\");\n }\n\n this._uLightAmbient = program.getLocation(\"lightAmbient\");\n this._uLightColor = [];\n this._uLightDir = [];\n this._uLightPos = [];\n this._uLightAttenuation = [];\n\n // TODO add a gard to prevent light params if not affected by light ?\n const lights = lightsState.lights;\n let light;\n\n for (let i = 0, len = lights.length; i < len; i++) {\n light = lights[i];\n switch (light.type) {\n case \"dir\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = null;\n this._uLightDir[i] = program.getLocation(\"lightDir\" + i);\n break;\n case \"point\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = program.getLocation(\"lightPos\" + i);\n this._uLightDir[i] = null;\n this._uLightAttenuation[i] = program.getLocation(\"lightAttenuation\" + i);\n break;\n case \"spot\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = program.getLocation(\"lightPos\" + i);\n this._uLightDir[i] = program.getLocation(\"lightDir\" + i);\n this._uLightAttenuation[i] = program.getLocation(\"lightAttenuation\" + i);\n break;\n }\n }\n\n if (lightsState.reflectionMaps.length > 0) {\n this._uReflectionMap = \"reflectionMap\";\n }\n\n if (lightsState.lightMaps.length > 0) {\n this._uLightMap = \"lightMap\";\n }\n\n this._uSectionPlanes = [];\n\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n\n this._aPosition = program.getAttribute(\"position\");\n this._aOffset = program.getAttribute(\"offset\");\n this._aNormal = program.getAttribute(\"normal\");\n this._aUV = program.getAttribute(\"uv\");\n this._aColor = program.getAttribute(\"color\");\n this._aMetallicRoughness = program.getAttribute(\"metallicRoughness\");\n this._aFlags = program.getAttribute(\"flags\");\n this._aPickColor = program.getAttribute(\"pickColor\");\n this._uPickZNear = program.getLocation(\"pickZNear\");\n this._uPickZFar = program.getLocation(\"pickZFar\");\n this._uPickClipPos = program.getLocation(\"pickClipPos\");\n this._uDrawingBufferSize = program.getLocation(\"drawingBufferSize\");\n\n this._uColorMap = \"uColorMap\";\n this._uMetallicRoughMap = \"uMetallicRoughMap\";\n this._uEmissiveMap = \"uEmissiveMap\";\n this._uNormalMap = \"uNormalMap\";\n this._uAOMap = \"uAOMap\";\n\n if (this._instancing) {\n\n this._aModelMatrix = program.getAttribute(\"modelMatrix\");\n\n this._aModelMatrixCol0 = program.getAttribute(\"modelMatrixCol0\");\n this._aModelMatrixCol1 = program.getAttribute(\"modelMatrixCol1\");\n this._aModelMatrixCol2 = program.getAttribute(\"modelMatrixCol2\");\n\n this._aModelNormalMatrixCol0 = program.getAttribute(\"modelNormalMatrixCol0\");\n this._aModelNormalMatrixCol1 = program.getAttribute(\"modelNormalMatrixCol1\");\n this._aModelNormalMatrixCol2 = program.getAttribute(\"modelNormalMatrixCol2\");\n }\n\n if (this._withSAO) {\n this._uOcclusionTexture = \"uOcclusionTexture\";\n this._uSAOParams = program.getLocation(\"uSAOParams\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n if (scene.pointsMaterial._state.filterIntensity) {\n this._uIntensityRange = program.getLocation(\"intensityRange\");\n }\n\n this._uPointSize = program.getLocation(\"pointSize\");\n this._uNearPlaneHeight = program.getLocation(\"nearPlaneHeight\");\n\n if (scene.crossSections) {\n this._uSliceColor = program.getLocation(\"sliceColor\");\n this._uSliceThickness = program.getLocation(\"sliceThickness\");\n }\n }\n\n _bindProgram(frameCtx) {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const lightsState = scene._lightsState;\n const lights = lightsState.lights;\n\n program.bind();\n\n frameCtx.textureUnit = 0;\n\n if (this._uLightAmbient) {\n gl.uniform4fv(this._uLightAmbient, lightsState.getAmbientColorAndIntensity());\n }\n\n if (this._uGammaFactor) {\n gl.uniform1f(this._uGammaFactor, scene.gammaFactor);\n }\n\n for (let i = 0, len = lights.length; i < len; i++) {\n\n const light = lights[i];\n\n if (this._uLightColor[i]) {\n gl.uniform4f(this._uLightColor[i], light.color[0], light.color[1], light.color[2], light.intensity);\n }\n if (this._uLightPos[i]) {\n gl.uniform3fv(this._uLightPos[i], light.pos);\n if (this._uLightAttenuation[i]) {\n gl.uniform1f(this._uLightAttenuation[i], light.attenuation);\n }\n }\n if (this._uLightDir[i]) {\n gl.uniform3fv(this._uLightDir[i], light.dir);\n }\n }\n }\n\n _makeVAO(state) {\n const gl = this._scene.canvas.gl;\n const vao = gl.createVertexArray();\n gl.bindVertexArray(vao);\n\n if (this._instancing) {\n this._aModelMatrixCol0.bindArrayBuffer(state.modelMatrixCol0Buf);\n this._aModelMatrixCol1.bindArrayBuffer(state.modelMatrixCol1Buf);\n this._aModelMatrixCol2.bindArrayBuffer(state.modelMatrixCol2Buf);\n\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 1);\n\n if (this._aModelNormalMatrixCol0) {\n this._aModelNormalMatrixCol0.bindArrayBuffer(state.modelNormalMatrixCol0Buf);\n gl.vertexAttribDivisor(this._aModelNormalMatrixCol0.location, 1);\n }\n if (this._aModelNormalMatrixCol1) {\n this._aModelNormalMatrixCol1.bindArrayBuffer(state.modelNormalMatrixCol1Buf);\n gl.vertexAttribDivisor(this._aModelNormalMatrixCol1.location, 1);\n }\n if (this._aModelNormalMatrixCol2) {\n this._aModelNormalMatrixCol2.bindArrayBuffer(state.modelNormalMatrixCol2Buf);\n gl.vertexAttribDivisor(this._aModelNormalMatrixCol2.location, 1);\n }\n\n }\n\n this._aPosition.bindArrayBuffer(state.positionsBuf);\n\n if (this._aUV) {\n this._aUV.bindArrayBuffer(state.uvBuf);\n }\n\n if (this._aNormal) {\n this._aNormal.bindArrayBuffer(state.normalsBuf);\n }\n\n if (this._aMetallicRoughness) {\n this._aMetallicRoughness.bindArrayBuffer(state.metallicRoughnessBuf);\n if (this._instancing) {\n gl.vertexAttribDivisor(this._aMetallicRoughness.location, 1);\n }\n }\n\n if (this._aColor) {\n this._aColor.bindArrayBuffer(state.colorsBuf);\n if (this._instancing && state.colorsBuf) {\n gl.vertexAttribDivisor(this._aColor.location, 1);\n }\n }\n\n if (this._aFlags) {\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n if (this._instancing) {\n gl.vertexAttribDivisor(this._aFlags.location, 1);\n }\n }\n\n if (this._aOffset) {\n this._aOffset.bindArrayBuffer(state.offsetsBuf);\n if (this._instancing) {\n gl.vertexAttribDivisor(this._aOffset.location, 1);\n }\n }\n\n if (this._aPickColor) {\n this._aPickColor.bindArrayBuffer(state.pickColorsBuf);\n if (this._instancing) {\n gl.vertexAttribDivisor(this._aPickColor.location, 1);\n }\n }\n\n if (this._instancing) {\n if (this._edges) {\n state.edgeIndicesBuf.bind();\n } else {\n if (state.indicesBuf) {\n state.indicesBuf.bind();\n }\n }\n } else {\n if (this._edges) {\n state.edgeIndicesBuf.bind();\n } else {\n if (state.indicesBuf) {\n state.indicesBuf.bind();\n }\n }\n }\n\n return vao;\n }\n\n drawLayer(frameCtx, layer, renderPass, {colorUniform = false, incrementDrawState = false} = {}) {\n const maxTextureUnits = WEBGL_INFO.MAX_TEXTURE_IMAGE_UNITS;\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const {_state: state, model} = layer;\n const {textureSet, origin, positionsDecodeMatrix} = state;\n const lightsState = scene._lightsState;\n const pointsMaterial = scene.pointsMaterial;\n const {camera} = model.scene;\n const {viewNormalMatrix, project} = camera;\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n const {position, rotationMatrix, rotationMatrixConjugate, worldNormalMatrix} = model;\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx);\n }\n\n if (this._vaoCache.has(layer)) {\n gl.bindVertexArray(this._vaoCache.get(layer));\n } else {\n this._vaoCache.set(layer, this._makeVAO(state));\n }\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$C;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$t);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n this._matricesUniformBlockBufferData.set(createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$r), offset += mat4Size);\n } else {\n this._matricesUniformBlockBufferData.set(viewMatrix, offset += mat4Size);\n }\n\n this._matricesUniformBlockBufferData.set(frameCtx.pickProjMatrix || project.matrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(positionsDecodeMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(worldNormalMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(viewNormalMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n\n gl.uniform1i(this._uRenderPass, renderPass);\n\n this.setSectionPlanesStateUniforms(layer);\n\n if (scene.logarithmicDepthBufferEnabled) {\n if (this._uLogDepthBufFC) {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n if (this._uZFar) {\n gl.uniform1f(this._uZFar, scene.camera.project.far);\n }\n }\n\n if (this._uPickInvisible) {\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n }\n\n if (this._uPickZNear) {\n gl.uniform1f(this._uPickZNear, frameCtx.pickZNear);\n }\n\n if (this._uPickZFar) {\n gl.uniform1f(this._uPickZFar, frameCtx.pickZFar);\n }\n\n if (this._uPickClipPos) {\n gl.uniform2fv(this._uPickClipPos, frameCtx.pickClipPos);\n }\n\n if (this._uDrawingBufferSize) {\n gl.uniform2f(this._uDrawingBufferSize, gl.drawingBufferWidth, gl.drawingBufferHeight);\n }\n\n if (this._uUVDecodeMatrix) {\n gl.uniformMatrix3fv(this._uUVDecodeMatrix, false, state.uvDecodeMatrix);\n }\n\n if (this._uIntensityRange && pointsMaterial.filterIntensity) {\n gl.uniform2f(this._uIntensityRange, pointsMaterial.minIntensity, pointsMaterial.maxIntensity);\n }\n\n if (this._uPointSize) {\n gl.uniform1f(this._uPointSize, pointsMaterial.pointSize);\n }\n\n if (this._uNearPlaneHeight) {\n const nearPlaneHeight = (scene.camera.projection === \"ortho\") ?\n 1.0\n : (gl.drawingBufferHeight / (2 * Math.tan(0.5 * scene.camera.perspective.fov * Math.PI / 180.0)));\n gl.uniform1f(this._uNearPlaneHeight, nearPlaneHeight);\n }\n\n if (textureSet) {\n const {\n colorTexture,\n metallicRoughnessTexture,\n emissiveTexture,\n normalsTexture,\n occlusionTexture,\n } = textureSet;\n\n if (this._uColorMap && colorTexture) {\n this._program.bindTexture(this._uColorMap, colorTexture.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n }\n if (this._uMetallicRoughMap && metallicRoughnessTexture) {\n this._program.bindTexture(this._uMetallicRoughMap, metallicRoughnessTexture.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n }\n if (this._uEmissiveMap && emissiveTexture) {\n this._program.bindTexture(this._uEmissiveMap, emissiveTexture.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n }\n if (this._uNormalMap && normalsTexture) {\n this._program.bindTexture(this._uNormalMap, normalsTexture.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n }\n if (this._uAOMap && occlusionTexture) {\n this._program.bindTexture(this._uAOMap, occlusionTexture.texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n }\n\n }\n\n if (lightsState.reflectionMaps.length > 0 && lightsState.reflectionMaps[0].texture && this._uReflectionMap) {\n this._program.bindTexture(this._uReflectionMap, lightsState.reflectionMaps[0].texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n }\n\n if (lightsState.lightMaps.length > 0 && lightsState.lightMaps[0].texture && this._uLightMap) {\n this._program.bindTexture(this._uLightMap, lightsState.lightMaps[0].texture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n }\n\n if (this._withSAO) {\n const sao = scene.sao;\n const saoEnabled = sao.possible;\n if (saoEnabled) {\n const viewportWidth = gl.drawingBufferWidth;\n const viewportHeight = gl.drawingBufferHeight;\n tempVec4[0] = viewportWidth;\n tempVec4[1] = viewportHeight;\n tempVec4[2] = sao.blendCutoff;\n tempVec4[3] = sao.blendFactor;\n gl.uniform4fv(this._uSAOParams, tempVec4);\n this._program.bindTexture(this._uOcclusionTexture, frameCtx.occlusionTexture, frameCtx.textureUnit);\n frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;\n frameCtx.bindTexture++;\n }\n }\n\n if (colorUniform) {\n const colorKey = this._edges ? \"edgeColor\" : \"fillColor\";\n const alphaKey = this._edges ? \"edgeAlpha\" : \"fillAlpha\";\n\n if (renderPass === RENDER_PASSES[`${this._edges ? \"EDGES\" : \"SILHOUETTE\"}_XRAYED`]) {\n const material = scene.xrayMaterial._state;\n const color = material[colorKey];\n const alpha = material[alphaKey];\n gl.uniform4f(this._uColor, color[0], color[1], color[2], alpha);\n\n } else if (renderPass === RENDER_PASSES[`${this._edges ? \"EDGES\" : \"SILHOUETTE\"}_HIGHLIGHTED`]) {\n const material = scene.highlightMaterial._state;\n const color = material[colorKey];\n const alpha = material[alphaKey];\n gl.uniform4f(this._uColor, color[0], color[1], color[2], alpha);\n\n } else if (renderPass === RENDER_PASSES[`${this._edges ? \"EDGES\" : \"SILHOUETTE\"}_SELECTED`]) {\n const material = scene.selectedMaterial._state;\n const color = material[colorKey];\n const alpha = material[alphaKey];\n gl.uniform4f(this._uColor, color[0], color[1], color[2], alpha);\n\n } else {\n gl.uniform4fv(this._uColor, this._edges ? edgesDefaultColor : defaultColor$2);\n }\n }\n\n this._draw({state, frameCtx, incrementDrawState});\n\n gl.bindVertexArray(null);\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n stats.memory.programs--;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesBatchingRenderer extends VBORenderer {\n\n constructor(scene, withSAO, {edges = false} = {}) {\n super(scene, withSAO, {instancing: false, edges});\n }\n\n _draw(drawCfg) {\n const {gl} = this._scene.canvas;\n\n const {\n state,\n frameCtx,\n incrementDrawState\n } = drawCfg;\n\n if (this._edges) {\n gl.drawElements(gl.LINES, state.edgeIndicesBuf.numItems, state.edgeIndicesBuf.itemType, 0);\n } else {\n const count = frameCtx.pickElementsCount || state.indicesBuf.numItems;\n const offset = frameCtx.pickElementsOffset ? frameCtx.pickElementsOffset * state.indicesBuf.itemByteSize : 0;\n\n gl.drawElements(gl.TRIANGLES, count, state.indicesBuf.itemType, offset);\n\n if (incrementDrawState) {\n frameCtx.drawElements++;\n }\n }\n }\n}\n\n/**\n * @private\n */\nclass TrianglesColorRenderer$1 extends TrianglesBatchingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n let light;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching draw vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec3 normal;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n this._addMatricesUniformBlockLines(src, true);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vec4 worldNormal = worldNormalMatrix * vec4(octDecode(normal.xy), 0.0); \");\n\n src.push(\"vec3 viewNormal = normalize((viewNormalMatrix * worldNormal).xyz);\");\n\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n\n src.push(\"float lambertian = 1.0;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n\n src.push(\"vec3 rgb = (vec3(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0));\");\n src.push(\"vColor = vec4((lightAmbient.rgb * lightAmbient.a * rgb) + (reflectedColor * rgb), float(color.a) / 255.0);\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching draw fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" outColor = vec4(newColor.rgb * ambient, 1.0);\");\n } else {\n src.push(\" outColor = newColor;\");\n }\n\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesFlatColorRenderer$1 extends TrianglesBatchingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching flat-shading draw vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, float(color.a) / 255.0);\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const lightsState = scene._lightsState;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching flat-shading draw fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec4 lightAmbient;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n\n src.push(\"float lambertian = 1.0;\");\n\n src.push(\"vec3 xTangent = dFdx( vViewPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vViewPosition.xyz );\");\n src.push(\"vec3 viewNormal = normalize( cross( xTangent, yTangent ) );\");\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n\n src.push(\"vec4 fragColor = vec4((lightAmbient.rgb * lightAmbient.a * newColor.rgb) + (reflectedColor * newColor.rgb), newColor.a);\");\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" outColor = vec4(fragColor.rgb * ambient, 1.0);\");\n } else {\n src.push(\" outColor = fragColor;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesSilhouetteRenderer$1 extends TrianglesBatchingRenderer {\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n super.drawLayer(frameCtx, batchingLayer, renderPass, { colorUniform: true });\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching silhouette vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 color;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec4 silhouetteColor;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // silhouetteFlag = NOT_RENDERED | SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | SILHOUETTE_XRAYED\n // renderPass = SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n\n src.push(`int silhouetteFlag = int(flags) >> 4 & 0xF;`);\n src.push(`if (silhouetteFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vColor = vec4(silhouetteColor.r, silhouetteColor.g, silhouetteColor.b, min(silhouetteColor.a, color.a ));\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n let i;\n let len;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching silhouette fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = newColor;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass EdgesRenderer$1 extends TrianglesBatchingRenderer {\n constructor(scene) {\n super(scene, false, {instancing: false, edges: true});\n }\n}\n\n/**\n * @private\n */\nclass EdgesEmphasisRenderer$1 extends EdgesRenderer$1 {\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n super.drawLayer(frameCtx, batchingLayer, renderPass, { colorUniform: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n\n src.push(\"#version 300 es\");\n src.push(\"// EdgesEmphasisRenderer vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n src.push(\"uniform vec4 color;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // edgeFlag = NOT_RENDERED | EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n // renderPass = EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n\n src.push(`int edgeFlag = int(flags) >> 8 & 0xF;`);\n src.push(`if (edgeFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"vColor = vec4(color.r, color.g, color.b, color.a);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// EdgesEmphasisRenderer fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass EdgesColorRenderer$1 extends EdgesRenderer$1 {\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n super.drawLayer(frameCtx, batchingLayer, renderPass, { colorUniform: false });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry edges drawing vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vColor;\");\n src.push(\"void main(void) {\");\n\n // edgeFlag = NOT_RENDERED | EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n // renderPass = EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT\n\n src.push(`int edgeFlag = int(flags) >> 8 & 0xF;`);\n src.push(`if (edgeFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n //src.push(\"vColor = vec4(float(color.r-100.0) / 255.0, float(color.g-100.0) / 255.0, float(color.b-100.0) / 255.0, float(color.a) / 255.0);\");\n src.push(\"vColor = vec4(float(color.r*0.5) / 255.0, float(color.g*0.5) / 255.0, float(color.b*0.5) / 255.0, float(color.a) / 255.0);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry edges drawing fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickMeshRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry picking vertex shader\");\n \n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n src.push(\"in vec4 pickColor;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n this._addRemapClipPosLines(src);\n\n src.push(\"out vec4 vPickColor;\");\n\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vPickColor = vec4(float(pickColor.r) / 255.0, float(pickColor.g) / 255.0, float(pickColor.b) / 255.0, float(pickColor.a) / 255.0);\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry picking fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vPickColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vPickColor; \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickDepthRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching pick depth vertex shader\");\n\n \n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n this._addRemapClipPosLines(src);\n\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching pick depth fragment shader\");\n\n \n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n src.push(\"uniform float pickZNear;\");\n src.push(\"uniform float pickZFar;\");\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"vec4 packDepth(const in float depth) {\");\n src.push(\" const vec4 bitShift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);\");\n src.push(\" const vec4 bitMask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);\");\n src.push(\" vec4 res = fract(depth * bitShift);\");\n src.push(\" res -= res.xxyz * bitMask;\");\n src.push(\" return res;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" float zNormalizedDepth = abs((pickZNear + vViewPosition.z) / (pickZFar - pickZNear));\");\n src.push(\" outColor = packDepth(zNormalizedDepth); \"); // Must be linear depth\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickNormalsRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching pick normals vertex shader\");\n \n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec3 normal;\");\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src, 3);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec3 vWorldNormal;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vec3 worldNormal = octDecode(normal.xy); \");\n src.push(\" vWorldNormal = worldNormal;\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching pick normals fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec3 vWorldNormal;\");\n src.push(\"out highp ivec4 outNormal;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(` outNormal = ivec4(vWorldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesOcclusionRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching occlusion vertex shader\");\n \n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src);\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n // Only opaque objects can be occluders\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching occlusion fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n src.push(\" outColor = vec4(0.0, 0.0, 1.0, 1.0); \"); // Occluders are blue\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesDepthRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching depth vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec2 vHighPrecisionZW;\");\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"vHighPrecisionZW = gl_Position.zw;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = (sectionPlanesState.getNumAllocatedSectionPlanes() > 0);\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching depth fragment shader\");\n\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"const float packUpScale = 256. / 255.;\");\n src.push(\"const float unpackDownscale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unpackFactors = unpackDownscale / vec4( packFactors, 1. );\");\n src.push(\"const float shiftRight8 = 1.0 / 256.;\");\n\n src.push(\"vec4 packDepthToRGBA( const in float v ) {\");\n src.push(\" vec4 r = vec4( fract( v * packFactors ), v );\");\n src.push(\" r.yzw -= r.xyz * shiftRight8;\");\n src.push(\" return r * packUpScale;\");\n src.push(\"}\");\n src.push(\"in vec2 vHighPrecisionZW;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\");\n src.push(\" outColor = vec4(vec3(1.0 - fragCoordZ), 1.0); \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass TrianglesNormalsRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry normals vertex shader\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec3 normal;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src, true);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec3 vViewNormal;\");\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\");\n\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vec4 worldNormal = worldNormalMatrix * vec4(octDecode(normal.xy), 0.0); \");\n src.push(\" vec3 viewNormal = normalize((viewNormalMatrix * worldNormal).xyz);\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\" vViewNormal = viewNormal;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = (sectionPlanesState.getNumAllocatedSectionPlanes() > 0);\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry normals fragment shader\");\n\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec3 vViewNormal;\");\n src.push(\"vec3 packNormalToRGB( const in vec3 normal ) {\");\n src.push(\" return normalize( normal ) * 0.5 + 0.5;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vec4(packNormalToRGB(vViewNormal), 1.0); \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * Renders BatchingLayer fragment depths to a shadow map.\n *\n * @private\n */\nclass TrianglesShadowRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry shadow vertex shader\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n src.push(\"uniform mat4 shadowViewMatrix;\");\n src.push(\"uniform mat4 shadowProjMatrix;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n src.push(` int colorFlag = int(flags) & 0xF;`);\n src.push(\" bool visible = (colorFlag > 0);\");\n src.push(\" bool transparent = ((float(color.a) / 255.0) < 1.0);\");\n src.push(\" if (!visible || transparent) {\");\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\");\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = shadowViewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\" vViewPosition = viewPosition;\");\n src.push(\" gl_Position = shadowProjMatrix * viewPosition;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = (sectionPlanesState.getNumAllocatedSectionPlanes() > 0);\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry shadow fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vViewPosition;\");\n\n src.push(\"vec4 encodeFloat( const in float v ) {\");\n src.push(\" const vec4 bitShift = vec4(256 * 256 * 256, 256 * 256, 256, 1.0);\");\n src.push(\" const vec4 bitMask = vec4(0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\");\n src.push(\" vec4 comp = fract(v * bitShift);\");\n src.push(\" comp -= comp.xxyz * bitMask;\");\n src.push(\" return comp;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n src.push(\" outColor = encodeFloat( gl_FragCoord.z); \");\n src.push(\"}\");\n return src;\n }\n}\n\n// const TEXTURE_DECODE_FUNCS = {};\n// TEXTURE_DECODE_FUNCS[LinearEncoding] = \"linearToLinear\";\n// TEXTURE_DECODE_FUNCS[sRGBEncoding] = \"sRGBToLinear\";\n\n/**\n * @private\n */\nclass TrianglesPBRRenderer$1 extends TrianglesBatchingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene.gammaOutput, scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const clippingCaps = sectionPlanesState.clippingCaps;\n\n const src = [];\n\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching quality draw vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec3 normal;\");\n src.push(\"in vec4 color;\");\n src.push(\"in vec2 uv;\");\n src.push(\"in vec2 metallicRoughness;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n this._addMatricesUniformBlockLines(src, true);\n\n src.push(\"uniform mat3 uvDecodeMatrix;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out vec3 vViewNormal;\");\n src.push(\"out vec4 vColor;\");\n src.push(\"out vec2 vUV;\");\n src.push(\"out vec2 vMetallicRoughness;\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"out vec3 vWorldNormal;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n if (clippingCaps) {\n src.push(\"out vec4 vClipPosition;\");\n }\n }\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\"vec4 worldNormal = worldNormalMatrix * vec4(octDecode(normal.xy), 0.0); \");\n src.push(\"vec3 viewNormal = normalize((viewNormalMatrix * worldNormal).xyz);\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n if (clippingCaps) {\n src.push(\"vClipPosition = clipPos;\");\n }\n }\n\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vViewNormal = viewNormal;\");\n src.push(\"vColor = color;\");\n src.push(\"vUV = (uvDecodeMatrix * vec3(uv, 1.0)).xy;\");\n src.push(\"vMetallicRoughness = metallicRoughness;\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"vWorldNormal = worldNormal.xyz;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n\n const scene = this._scene;\n const gammaOutput = scene.gammaOutput; // If set, then it expects that all textures and colors need to be outputted in premultiplied gamma. Default is false.\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const clippingCaps = sectionPlanesState.clippingCaps;\n const src = [];\n\n src.push('#version 300 es');\n src.push(\"// Triangles batching quality draw fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n src.push(\"uniform sampler2D uColorMap;\");\n src.push(\"uniform sampler2D uMetallicRoughMap;\");\n src.push(\"uniform sampler2D uEmissiveMap;\");\n src.push(\"uniform sampler2D uNormalMap;\");\n src.push(\"uniform sampler2D uAOMap;\");\n\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"in vec3 vViewNormal;\");\n src.push(\"in vec4 vColor;\");\n src.push(\"in vec2 vUV;\");\n src.push(\"in vec2 vMetallicRoughness;\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"in vec3 vWorldNormal;\");\n }\n\n this._addMatricesUniformBlockLines(src, true);\n\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\"uniform samplerCube reflectionMap;\");\n }\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"uniform samplerCube lightMap;\");\n }\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n\n src.push(\"uniform float gammaFactor;\");\n src.push(\"vec4 linearToLinear( in vec4 value ) {\");\n src.push(\" return value;\");\n src.push(\"}\");\n src.push(\"vec4 sRGBToLinear( in vec4 value ) {\");\n src.push(\" return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\");\n src.push(\"}\");\n src.push(\"vec4 gammaToLinear( in vec4 value) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\");\n src.push(\"}\");\n\n if (gammaOutput) {\n src.push(\"vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n if (clippingCaps) {\n src.push(\"in vec4 vClipPosition;\");\n }\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n\n // CONSTANT DEFINITIONS\n\n src.push(\"#define PI 3.14159265359\");\n src.push(\"#define RECIPROCAL_PI 0.31830988618\");\n src.push(\"#define RECIPROCAL_PI2 0.15915494\");\n src.push(\"#define EPSILON 1e-6\");\n\n src.push(\"#define saturate(a) clamp( a, 0.0, 1.0 )\");\n\n // UTILITY DEFINITIONS\n\n src.push(\"vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\");\n src.push(\" vec3 texel = texture( uNormalMap, uv ).xyz;\");\n src.push(\" if (texel.x == 0.0 && texel.y == 0.0 && texel.z == 0.0) {\");\n src.push(\" return surf_norm;\");\n src.push(\" }\");\n src.push(\" vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\");\n src.push(\" vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\");\n src.push(\" vec2 st0 = dFdx( uv.st );\");\n src.push(\" vec2 st1 = dFdy( uv.st );\");\n src.push(\" vec3 S = normalize( q0 * st1.t - q1 * st0.t );\");\n src.push(\" vec3 T = normalize( -q0 * st1.s + q1 * st0.s );\");\n src.push(\" vec3 N = normalize( surf_norm );\");\n src.push(\" vec3 mapN = texel.xyz * 2.0 - 1.0;\");\n src.push(\" mat3 tsn = mat3( S, T, N );\");\n //src.push(\" mapN *= 3.0;\");\n src.push(\" return normalize( tsn * mapN );\");\n src.push(\"}\");\n\n src.push(\"vec3 inverseTransformDirection(in vec3 dir, in mat4 matrix) {\");\n src.push(\" return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\");\n src.push(\"}\");\n\n // STRUCTURES\n\n src.push(\"struct IncidentLight {\");\n src.push(\" vec3 color;\");\n src.push(\" vec3 direction;\");\n src.push(\"};\");\n\n src.push(\"struct ReflectedLight {\");\n src.push(\" vec3 diffuse;\");\n src.push(\" vec3 specular;\");\n src.push(\"};\");\n\n src.push(\"struct Geometry {\");\n src.push(\" vec3 position;\");\n src.push(\" vec3 viewNormal;\");\n src.push(\" vec3 worldNormal;\");\n src.push(\" vec3 viewEyeDir;\");\n src.push(\"};\");\n\n src.push(\"struct Material {\");\n src.push(\" vec3 diffuseColor;\");\n src.push(\" float specularRoughness;\");\n src.push(\" vec3 specularColor;\");\n src.push(\" float shine;\"); // Only used for Phong\n src.push(\"};\");\n\n // IRRADIANCE EVALUATION\n\n src.push(\"float GGXRoughnessToBlinnExponent(const in float ggxRoughness) {\");\n src.push(\" float r = ggxRoughness + 0.0001;\");\n src.push(\" return (2.0 / (r * r) - 2.0);\");\n src.push(\"}\");\n\n src.push(\"float getSpecularMIPLevel(const in float blinnShininessExponent, const in int maxMIPLevel) {\");\n src.push(\" float maxMIPLevelScalar = float( maxMIPLevel );\");\n src.push(\" float desiredMIPLevel = maxMIPLevelScalar - 0.79248 - 0.5 * log2( ( blinnShininessExponent * blinnShininessExponent ) + 1.0 );\");\n src.push(\" return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\");\n src.push(\"}\");\n\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\"vec3 getLightProbeIndirectRadiance(const in vec3 reflectVec, const in float blinnShininessExponent, const in int maxMIPLevel) {\");\n src.push(\" float mipLevel = 0.5 * getSpecularMIPLevel(blinnShininessExponent, maxMIPLevel);\"); //TODO: a random factor - fix this\n src.push(\" vec3 envMapColor = sRGBToLinear(texture(reflectionMap, reflectVec, mipLevel)).rgb;\");\n src.push(\" return envMapColor;\");\n src.push(\"}\");\n }\n\n // SPECULAR BRDF EVALUATION\n\n src.push(\"vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {\");\n src.push(\" float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\");\n src.push(\" return ( 1.0 - specularColor ) * fresnel + specularColor;\");\n src.push(\"}\");\n\n src.push(\"float G_GGX_Smith(const in float alpha, const in float dotNL, const in float dotNV) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );\");\n src.push(\" float gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );\");\n src.push(\" return 1.0 / ( gl * gv );\");\n src.push(\"}\");\n\n src.push(\"float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );\");\n src.push(\" float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );\");\n src.push(\" return 0.5 / max( gv + gl, EPSILON );\");\n src.push(\"}\");\n\n src.push(\"float D_GGX(const in float alpha, const in float dotNH) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float denom = ( dotNH * dotNH) * ( a2 - 1.0 ) + 1.0;\");\n src.push(\" return RECIPROCAL_PI * a2 / ( denom * denom);\");\n src.push(\"}\");\n\n src.push(\"vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in Geometry geometry, const in vec3 specularColor, const in float roughness) {\");\n src.push(\" float alpha = ( roughness * roughness );\");\n src.push(\" vec3 halfDir = normalize( incidentLight.direction + geometry.viewEyeDir );\");\n src.push(\" float dotNL = saturate( dot( geometry.viewNormal, incidentLight.direction ) );\");\n src.push(\" float dotNV = saturate( dot( geometry.viewNormal, geometry.viewEyeDir ) );\");\n src.push(\" float dotNH = saturate( dot( geometry.viewNormal, halfDir ) );\");\n src.push(\" float dotLH = saturate( dot( incidentLight.direction, halfDir ) );\");\n src.push(\" vec3 F = F_Schlick( specularColor, dotLH );\");\n src.push(\" float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\");\n src.push(\" float D = D_GGX( alpha, dotNH );\");\n src.push(\" return F * (G * D);\");\n src.push(\"}\");\n\n src.push(\"vec3 BRDF_Specular_GGX_Environment(const in Geometry geometry, const in vec3 specularColor, const in float roughness) {\");\n src.push(\" float dotNV = saturate(dot(geometry.viewNormal, geometry.viewEyeDir));\");\n src.push(\" const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022);\");\n src.push(\" const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04);\");\n src.push(\" vec4 r = roughness * c0 + c1;\");\n src.push(\" float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;\");\n src.push(\" vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;\");\n src.push(\" return specularColor * AB.x + AB.y;\");\n src.push(\"}\");\n\n if (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0) {\n src.push(\"void computePBRLightMapping(const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n if (lightsState.lightMaps.length > 0) {\n src.push(\" vec3 irradiance = sRGBToLinear(texture(lightMap, geometry.worldNormal)).rgb;\");\n src.push(\" irradiance *= PI;\");\n src.push(\" vec3 diffuseBRDFContrib = (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.diffuse += irradiance * diffuseBRDFContrib;\");\n }\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\" vec3 reflectVec = reflect(geometry.viewEyeDir, geometry.viewNormal);\");\n src.push(\" reflectVec = inverseTransformDirection(reflectVec, viewMatrix);\");\n src.push(\" float blinnExpFromRoughness = GGXRoughnessToBlinnExponent(material.specularRoughness);\");\n src.push(\" vec3 radiance = getLightProbeIndirectRadiance(reflectVec, blinnExpFromRoughness, 8);\");\n src.push(\" vec3 specularBRDFContrib = BRDF_Specular_GGX_Environment(geometry, material.specularColor, material.specularRoughness);\");\n src.push(\" reflectedLight.specular += radiance * specularBRDFContrib;\");\n }\n src.push(\"}\");\n }\n\n // MAIN LIGHTING COMPUTATION FUNCTION\n\n src.push(\"void computePBRLighting(const in IncidentLight incidentLight, const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n src.push(\" float dotNL = saturate(dot(geometry.viewNormal, incidentLight.direction));\");\n src.push(\" vec3 irradiance = dotNL * incidentLight.color * PI;\");\n src.push(\" reflectedLight.diffuse += irradiance * (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.specular += irradiance * BRDF_Specular_GGX(incidentLight, geometry, material.specularColor, material.specularRoughness);\");\n src.push(\"}\");\n\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n if (clippingCaps) {\n src.push(\" if (dist > (0.002 * vClipPosition.w)) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" outColor=vec4(1.0, 0.0, 0.0, 1.0);\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" return;\");\n src.push(\"}\");\n } else {\n src.push(\" if (dist > 0.0) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n src.push(\"}\");\n }\n\n src.push(\"IncidentLight light;\");\n src.push(\"Material material;\");\n src.push(\"Geometry geometry;\");\n src.push(\"ReflectedLight reflectedLight = ReflectedLight(vec3(0.0,0.0,0.0), vec3(0.0,0.0,0.0));\");\n\n src.push(\"vec3 rgb = (vec3(float(vColor.r) / 255.0, float(vColor.g) / 255.0, float(vColor.b) / 255.0));\");\n src.push(\"float opacity = float(vColor.a) / 255.0;\");\n\n src.push(\"vec3 baseColor = rgb;\");\n src.push(\"float specularF0 = 1.0;\");\n src.push(\"float metallic = float(vMetallicRoughness.r) / 255.0;\");\n src.push(\"float roughness = float(vMetallicRoughness.g) / 255.0;\");\n src.push(\"float dielectricSpecular = 0.16 * specularF0 * specularF0;\");\n\n src.push(\"vec4 colorTexel = sRGBToLinear(texture(uColorMap, vUV));\");\n src.push(\"baseColor *= colorTexel.rgb;\");\n // src.push(\"opacity *= colorTexel.a;\");\n\n src.push(\"vec3 metalRoughTexel = texture(uMetallicRoughMap, vUV).rgb;\");\n src.push(\"metallic *= metalRoughTexel.b;\");\n src.push(\"roughness *= metalRoughTexel.g;\");\n\n src.push(\"vec3 viewNormal = perturbNormal2Arb(vViewPosition.xyz, normalize(vViewNormal), vUV );\");\n\n src.push(\"material.diffuseColor = baseColor * (1.0 - dielectricSpecular) * (1.0 - metallic);\");\n src.push(\"material.specularRoughness = clamp(roughness, 0.04, 1.0);\");\n src.push(\"material.specularColor = mix(vec3(dielectricSpecular), baseColor, metallic);\");\n\n src.push(\"geometry.position = vViewPosition.xyz;\");\n src.push(\"geometry.viewNormal = -normalize(viewNormal);\");\n src.push(\"geometry.viewEyeDir = normalize(vViewPosition.xyz);\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"geometry.worldNormal = normalize(vWorldNormal);\");\n }\n\n if (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0) {\n src.push(\"computePBRLightMapping(geometry, material, reflectedLight);\");\n }\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"light.direction = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"light.direction = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"light.direction = normalize(lightPos\" + i + \" - vViewPosition.xyz);\");\n } else {\n src.push(\"light.direction = normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"light.direction = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"light.direction = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n\n src.push(\"light.color = lightColor\" + i + \".rgb * lightColor\" + i + \".a;\"); // a is intensity\n\n src.push(\"computePBRLighting(light, geometry, material, reflectedLight);\");\n }\n\n src.push(\"vec3 emissiveColor = sRGBToLinear(texture(uEmissiveMap, vUV)).rgb;\"); // TODO: correct gamma function\n src.push(\"float aoFactor = texture(uAOMap, vUV).r;\");\n\n src.push(\"vec3 outgoingLight = (lightAmbient.rgb * lightAmbient.a * baseColor * opacity * rgb) + (reflectedLight.diffuse) + (reflectedLight.specular) + emissiveColor;\");\n src.push(\"vec4 fragColor;\");\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" fragColor = vec4(outgoingLight.rgb * ambient * aoFactor, opacity);\");\n } else {\n src.push(\" fragColor = vec4(outgoingLight.rgb * aoFactor, opacity);\");\n }\n\n if (gammaOutput) {\n src.push(\"fragColor = linearToGamma(fragColor, gammaFactor);\");\n }\n\n src.push(\"outColor = fragColor;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickNormalsFlatRenderer$1 extends TrianglesBatchingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching pick flat normals vertex shader\");\n \n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src, 3);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching pick flat normals fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"in vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out highp ivec4 outNormal;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n src.push(\" vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n src.push(\" vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(` outNormal = ivec4(worldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesColorTextureRenderer$1 extends TrianglesBatchingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene.gammaOutput, scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, {incrementDrawState: true});\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching color texture vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in vec2 uv;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform mat3 uvDecodeMatrix;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out vec4 vColor;\");\n src.push(\"out vec2 vUV;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, float(color.a) / 255.0);\");\n src.push(\"vUV = (uvDecodeMatrix * vec3(uv, 1.0)).xy;\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const gammaOutput = scene.gammaOutput; // If set, then it expects that all textures and colors need to be outputted in premultiplied gamma. Default is false.\n const lightsState = scene._lightsState;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles batching color texture fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform sampler2D uColorMap;\");\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n src.push(\"uniform float gammaFactor;\");\n src.push(\"vec4 linearToLinear( in vec4 value ) {\");\n src.push(\" return value;\");\n src.push(\"}\");\n src.push(\"vec4 sRGBToLinear( in vec4 value ) {\");\n src.push(\" return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\");\n src.push(\"}\");\n src.push(\"vec4 gammaToLinear( in vec4 value) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\");\n src.push(\"}\");\n if (gammaOutput) {\n src.push(\"vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n this._addMatricesUniformBlockLines(src);\n src.push(\"uniform vec4 lightAmbient;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"in vec4 vColor;\");\n src.push(\"in vec2 vUV;\");\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n\n src.push(\"float lambertian = 1.0;\");\n\n src.push(\"vec3 xTangent = dFdx( vViewPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vViewPosition.xyz );\");\n src.push(\"vec3 viewNormal = normalize( cross( xTangent, yTangent ) );\");\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n\n src.push(\"vec4 color = vec4((lightAmbient.rgb * lightAmbient.a * newColor.rgb) + (reflectedColor * newColor.rgb), newColor.a);\");\n if (gammaOutput) {\n src.push(\"vec4 colorTexel = color * sRGBToLinear(texture(uColorMap, vUV));\");\n } else {\n src.push(\"vec4 colorTexel = color * texture(uColorMap, vUV);\");\n }\n src.push(\"float opacity = color.a;\");\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n\n src.push(\" outColor = vec4(colorTexel.rgb * ambient, opacity);\");\n } else {\n src.push(\" outColor = vec4(colorTexel.rgb, opacity);\");\n }\n\n if (gammaOutput) {\n src.push(\"outColor = linearToGamma(outColor, gammaFactor);\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n return src;\n }\n}\n\nconst tempVec3a$B = math.vec3();\nconst tempVec3b$x = math.vec3();\nconst tempVec3c$s = math.vec3();\nconst tempVec3d$c = math.vec3();\nconst tempMat4a$q = math.mat4();\n\n/**\n * @private\n */\nclass TrianglesSnapInitRenderer$1 extends VBORenderer {\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = batchingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = batchingLayer._state;\n const origin = batchingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = batchingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(batchingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(batchingLayer));\n } else {\n this._vaoCache.set(batchingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$B;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$x;\n if (origin) {\n const rotatedOrigin = tempVec3c$s;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$q);\n rtcCameraEye = tempVec3d$c;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(batchingLayer);\n //=============================================================\n // TODO: Use drawElements count and offset to draw only one entity\n //=============================================================\n\n state.indicesBuf.bind();\n gl.drawElements(gl.TRIANGLES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0);\n state.indicesBuf.unbind();\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\");\n this._uLayerNumber = program.getLocation(\"layerNumber\");\n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBO SnapBatchingDepthBufInitRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec4 pickColor;\");\n src.push(\"in vec3 position;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n src.push(\"uniform vec2 snapVectorA;\");\n src.push(\"uniform vec2 snapInvVectorAB;\");\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n src.push(\"flat out vec4 vPickColor;\");\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vPickColor = pickColor;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBO SnapBatchingDepthBufInitRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\");\n src.push(\"uniform vec3 coordinateScaler;\");\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in vec4 vPickColor;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"layout(location = 0) out highp ivec4 outCoords;\");\n src.push(\"layout(location = 1) out highp ivec4 outNormal;\");\n src.push(\"layout(location = 2) out lowp uvec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" float dx = dFdx(vFragDepth);\");\n src.push(\" float dy = dFdy(vFragDepth);\");\n src.push(\" float diff = sqrt(dx*dx+dy*dy);\");\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth + diff ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, -layerNumber);\");\n\n src.push(\"vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n src.push(\"vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(`outNormal = ivec4(worldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"outPickColor = uvec4(vPickColor);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$A = math.vec3();\nconst tempVec3b$w = math.vec3();\nconst tempVec3c$r = math.vec3();\nconst tempVec3d$b = math.vec3();\nconst tempMat4a$p = math.mat4();\n\n/**\n * @private\n */\nclass TrianglesSnapRenderer$1 extends VBORenderer{\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + (this._scene.pointsMaterial.hash);\n }\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = batchingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = batchingLayer._state;\n const origin = batchingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = batchingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(batchingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(batchingLayer));\n } else {\n this._vaoCache.set(batchingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$A;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$w;\n if (origin) {\n const rotatedOrigin = tempVec3c$r;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$p);\n rtcCameraEye = tempVec3d$b;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(batchingLayer);\n\n //=============================================================\n // TODO: Use drawElements count and offset to draw only one entity\n //=============================================================\n\n if (frameCtx.snapMode === \"edge\") {\n state.edgeIndicesBuf.bind();\n gl.drawElements(gl.LINES, state.edgeIndicesBuf.numItems, state.edgeIndicesBuf.itemType, 0);\n state.edgeIndicesBuf.unbind(); // needed?\n } else {\n gl.drawArrays(gl.POINTS, 0, state.positionsBuf.numItems);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\"); \n this.uVectorA = program.getLocation(\"snapVectorA\"); \n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\"); \n this._uLayerNumber = program.getLocation(\"layerNumber\"); \n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\"); \n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n scene.pointsMaterial._state;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapBatchingDepthRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\"); \n src.push(\"uniform vec2 snapVectorA;\"); \n src.push(\"uniform vec2 snapInvVectorAB;\"); \n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"gl_PointSize = 1.0;\"); // Windows needs this?\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapBatchingDepthRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\"); \n src.push(\"uniform vec3 coordinateScaler;\"); \n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"out highp ivec4 outCoords;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, layerNumber);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\nclass Renderers$1 {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n if (this._colorRendererWithSAO && (!this._colorRendererWithSAO.getValid())) {\n this._colorRendererWithSAO.destroy();\n this._colorRendererWithSAO = null;\n }\n if (this._flatColorRenderer && (!this._flatColorRenderer.getValid())) {\n this._flatColorRenderer.destroy();\n this._flatColorRenderer = null;\n }\n if (this._flatColorRendererWithSAO && (!this._flatColorRendererWithSAO.getValid())) {\n this._flatColorRendererWithSAO.destroy();\n this._flatColorRendererWithSAO = null;\n }\n if (this._colorTextureRenderer && (!this._colorTextureRenderer.getValid())) {\n this._colorTextureRenderer.destroy();\n this._colorTextureRenderer = null;\n }\n if (this._colorTextureRendererWithSAO && (!this._colorTextureRendererWithSAO.getValid())) {\n this._colorTextureRendererWithSAO.destroy();\n this._colorTextureRendererWithSAO = null;\n }\n if (this._pbrRenderer && (!this._pbrRenderer.getValid())) {\n this._pbrRenderer.destroy();\n this._pbrRenderer = null;\n }\n if (this._pbrRendererWithSAO && (!this._pbrRendererWithSAO.getValid())) {\n this._pbrRendererWithSAO.destroy();\n this._pbrRendererWithSAO = null;\n }\n if (this._depthRenderer && (!this._depthRenderer.getValid())) {\n this._depthRenderer.destroy();\n this._depthRenderer = null;\n }\n if (this._normalsRenderer && (!this._normalsRenderer.getValid())) {\n this._normalsRenderer.destroy();\n this._normalsRenderer = null;\n }\n if (this._silhouetteRenderer && (!this._silhouetteRenderer.getValid())) {\n this._silhouetteRenderer.destroy();\n this._silhouetteRenderer = null;\n }\n if (this._edgesRenderer && (!this._edgesRenderer.getValid())) {\n this._edgesRenderer.destroy();\n this._edgesRenderer = null;\n }\n if (this._edgesColorRenderer && (!this._edgesColorRenderer.getValid())) {\n this._edgesColorRenderer.destroy();\n this._edgesColorRenderer = null;\n }\n if (this._pickMeshRenderer && (!this._pickMeshRenderer.getValid())) {\n this._pickMeshRenderer.destroy();\n this._pickMeshRenderer = null;\n }\n if (this._pickDepthRenderer && (!this._pickDepthRenderer.getValid())) {\n this._pickDepthRenderer.destroy();\n this._pickDepthRenderer = null;\n }\n if (this._pickNormalsRenderer && this._pickNormalsRenderer.getValid() === false) {\n this._pickNormalsRenderer.destroy();\n this._pickNormalsRenderer = null;\n }\n if (this._pickNormalsFlatRenderer && this._pickNormalsFlatRenderer.getValid() === false) {\n this._pickNormalsFlatRenderer.destroy();\n this._pickNormalsFlatRenderer = null;\n }\n if (this._occlusionRenderer && this._occlusionRenderer.getValid() === false) {\n this._occlusionRenderer.destroy();\n this._occlusionRenderer = null;\n }\n if (this._shadowRenderer && (!this._shadowRenderer.getValid())) {\n this._shadowRenderer.destroy();\n this._shadowRenderer = null;\n }\n if (this._snapInitRenderer && (!this._snapInitRenderer.getValid())) {\n this._snapInitRenderer.destroy();\n this._snapInitRenderer = null;\n }\n if (this._snapRenderer && (!this._snapRenderer.getValid())) {\n this._snapRenderer.destroy();\n this._snapRenderer = null;\n }\n }\n\n eagerCreateRenders() {\n\n // Pre-initialize certain renderers that would otherwise be lazy-initialised\n // on user interaction, such as picking or emphasis, so that there is no delay\n // when user first begins interacting with the viewer.\n\n if (!this._silhouetteRenderer) { // Used for highlighting and selection\n this._silhouetteRenderer = new TrianglesSilhouetteRenderer$1(this._scene);\n }\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new TrianglesPickMeshRenderer$1(this._scene);\n }\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new TrianglesPickDepthRenderer$1(this._scene);\n }\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new TrianglesSnapInitRenderer$1(this._scene, false);\n }\n if (!this._snapRenderer) {\n this._snapRenderer = new TrianglesSnapRenderer$1(this._scene);\n }\n }\n\n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new TrianglesColorRenderer$1(this._scene, false);\n }\n return this._colorRenderer;\n }\n\n get colorRendererWithSAO() {\n if (!this._colorRendererWithSAO) {\n this._colorRendererWithSAO = new TrianglesColorRenderer$1(this._scene, true);\n }\n return this._colorRendererWithSAO;\n }\n\n get flatColorRenderer() {\n if (!this._flatColorRenderer) {\n this._flatColorRenderer = new TrianglesFlatColorRenderer$1(this._scene, false);\n }\n return this._flatColorRenderer;\n }\n\n get flatColorRendererWithSAO() {\n if (!this._flatColorRendererWithSAO) {\n this._flatColorRendererWithSAO = new TrianglesFlatColorRenderer$1(this._scene, true);\n }\n return this._flatColorRendererWithSAO;\n }\n\n get colorTextureRenderer() {\n if (!this._colorTextureRenderer) {\n this._colorTextureRenderer = new TrianglesColorTextureRenderer$1(this._scene, false);\n }\n return this._colorTextureRenderer;\n }\n\n get colorTextureRendererWithSAO() {\n if (!this._colorTextureRendererWithSAO) {\n this._colorTextureRendererWithSAO = new TrianglesColorTextureRenderer$1(this._scene, true);\n }\n return this._colorTextureRendererWithSAO;\n }\n\n get pbrRenderer() {\n if (!this._pbrRenderer) {\n this._pbrRenderer = new TrianglesPBRRenderer$1(this._scene, false);\n }\n return this._pbrRenderer;\n }\n\n get pbrRendererWithSAO() {\n if (!this._pbrRendererWithSAO) {\n this._pbrRendererWithSAO = new TrianglesPBRRenderer$1(this._scene, true);\n }\n return this._pbrRendererWithSAO;\n }\n\n get silhouetteRenderer() {\n if (!this._silhouetteRenderer) {\n this._silhouetteRenderer = new TrianglesSilhouetteRenderer$1(this._scene);\n }\n return this._silhouetteRenderer;\n }\n\n get depthRenderer() {\n if (!this._depthRenderer) {\n this._depthRenderer = new TrianglesDepthRenderer$1(this._scene);\n }\n return this._depthRenderer;\n }\n\n get normalsRenderer() {\n if (!this._normalsRenderer) {\n this._normalsRenderer = new TrianglesNormalsRenderer$1(this._scene);\n }\n return this._normalsRenderer;\n }\n\n get edgesRenderer() {\n if (!this._edgesRenderer) {\n this._edgesRenderer = new EdgesEmphasisRenderer$1(this._scene);\n }\n return this._edgesRenderer;\n }\n\n get edgesColorRenderer() {\n if (!this._edgesColorRenderer) {\n this._edgesColorRenderer = new EdgesColorRenderer$1(this._scene);\n }\n return this._edgesColorRenderer;\n }\n\n get pickMeshRenderer() {\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new TrianglesPickMeshRenderer$1(this._scene);\n }\n return this._pickMeshRenderer;\n }\n\n get pickNormalsRenderer() {\n if (!this._pickNormalsRenderer) {\n this._pickNormalsRenderer = new TrianglesPickNormalsRenderer$1(this._scene);\n }\n return this._pickNormalsRenderer;\n }\n\n get pickNormalsFlatRenderer() {\n if (!this._pickNormalsFlatRenderer) {\n this._pickNormalsFlatRenderer = new TrianglesPickNormalsFlatRenderer$1(this._scene);\n }\n return this._pickNormalsFlatRenderer;\n }\n\n get pickDepthRenderer() {\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new TrianglesPickDepthRenderer$1(this._scene);\n }\n return this._pickDepthRenderer;\n }\n\n get occlusionRenderer() {\n if (!this._occlusionRenderer) {\n this._occlusionRenderer = new TrianglesOcclusionRenderer$1(this._scene);\n }\n return this._occlusionRenderer;\n }\n\n get shadowRenderer() {\n if (!this._shadowRenderer) {\n this._shadowRenderer = new TrianglesShadowRenderer$1(this._scene);\n }\n return this._shadowRenderer;\n }\n\n get snapRenderer() {\n if (!this._snapRenderer) {\n this._snapRenderer = new TrianglesSnapRenderer$1(this._scene);\n }\n return this._snapRenderer;\n }\n\n get snapInitRenderer() {\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new TrianglesSnapInitRenderer$1(this._scene);\n }\n return this._snapInitRenderer;\n }\n\n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n if (this._colorRendererWithSAO) {\n this._colorRendererWithSAO.destroy();\n }\n if (this._flatColorRenderer) {\n this._flatColorRenderer.destroy();\n }\n if (this._flatColorRendererWithSAO) {\n this._flatColorRendererWithSAO.destroy();\n }\n if (this._colorTextureRenderer) {\n this._colorTextureRenderer.destroy();\n }\n if (this._colorTextureRendererWithSAO) {\n this._colorTextureRendererWithSAO.destroy();\n }\n if (this._pbrRenderer) {\n this._pbrRenderer.destroy();\n }\n if (this._pbrRendererWithSAO) {\n this._pbrRendererWithSAO.destroy();\n }\n if (this._depthRenderer) {\n this._depthRenderer.destroy();\n }\n if (this._normalsRenderer) {\n this._normalsRenderer.destroy();\n }\n if (this._silhouetteRenderer) {\n this._silhouetteRenderer.destroy();\n }\n if (this._edgesRenderer) {\n this._edgesRenderer.destroy();\n }\n if (this._edgesColorRenderer) {\n this._edgesColorRenderer.destroy();\n }\n if (this._pickMeshRenderer) {\n this._pickMeshRenderer.destroy();\n }\n if (this._pickDepthRenderer) {\n this._pickDepthRenderer.destroy();\n }\n if (this._pickNormalsRenderer) {\n this._pickNormalsRenderer.destroy();\n }\n if (this._pickNormalsFlatRenderer) {\n this._pickNormalsFlatRenderer.destroy();\n }\n if (this._occlusionRenderer) {\n this._occlusionRenderer.destroy();\n }\n if (this._shadowRenderer) {\n this._shadowRenderer.destroy();\n }\n if (this._snapInitRenderer) {\n this._snapInitRenderer.destroy();\n }\n if (this._snapRenderer) {\n this._snapRenderer.destroy();\n }\n }\n}\n\nconst cachdRenderers$1 = {};\n\n/**\n * @private\n */\nfunction getRenderers$7(scene) {\n const sceneId = scene.id;\n let batchingRenderers = cachdRenderers$1[sceneId];\n if (!batchingRenderers) {\n batchingRenderers = new Renderers$1(scene);\n cachdRenderers$1[sceneId] = batchingRenderers;\n batchingRenderers._compile();\n batchingRenderers.eagerCreateRenders();\n scene.on(\"compile\", () => {\n batchingRenderers._compile();\n batchingRenderers.eagerCreateRenders();\n });\n scene.on(\"destroyed\", () => {\n delete cachdRenderers$1[sceneId];\n batchingRenderers._destroy();\n });\n }\n return batchingRenderers;\n}\n\nlet maxDataTextureHeight = 1 << 16;\nlet maxGeometryBatchSize = 5000000;\n\n/**\n * Manages global configurations for all {@link Viewer}s.\n *\n * ## Example\n *\n * In the example below, we'll disable xeokit's double-precision support, which gives a performance and memory boost\n * on low-power devices, but also means that we can no longer render double-precision models without jittering.\n *\n * That's OK if we know that we're not going to view models that are geographically vast, or offset far from the World coordinate origin.\n *\n * [[Run this example](/examples/index.html#Configs_disableDoublePrecisionAndRAF)]\n *\n * ````javascript\n * import {Configs, Viewer, XKTLoaderPlugin} from \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/xeokit-sdk.es.min.js\";\n *\n * // Access xeoit-sdk global configs.\n * // We typically set configs only before we create any Viewers.\n * const configs = new Configs();\n *\n * // Disable 64-bit precision for extra speed.\n * // Only set this config once, before you create any Viewers.\n * configs.doublePrecisionEnabled = false;\n *\n * // Create a Viewer, to which our configs apply\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.camera.eye = [-3.933, 2.855, 27.018];\n * viewer.camera.look = [4.400, 3.724, 8.899];\n * viewer.camera.up = [-0.018, 0.999, 0.039];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * src: \"../assets/models/xkt/v8/ifc/Duplex.ifc.xkt\"\n * });\n * ````\n */\nclass Configs {\n\n /**\n * Creates a Configs.\n */\n constructor() {\n\n }\n\n /**\n * Sets whether double precision mode is enabled for Viewers.\n *\n * When double precision mode is enabled (default), Viewers will accurately render models that contain\n * double-precision coordinates, without jittering.\n *\n * Internally, double precision incurs extra performance and memory overhead, so if we're certain that\n * we're not going to render models that rely on double-precision coordinates, then it's a good idea to disable\n * it, especially on low-power devices.\n *\n * This should only be set once, before creating any Viewers.\n *\n * @returns {Boolean}\n */\n set doublePrecisionEnabled(doublePrecision) {\n math.setDoublePrecisionEnabled(doublePrecision);\n }\n\n /**\n * Gets whether double precision mode is enabled for all Viewers.\n *\n * @returns {Boolean}\n */\n get doublePrecisionEnabled() {\n return math.getDoublePrecisionEnabled();\n }\n\n /**\n * Sets the maximum data texture height.\n *\n * Should be a multiple of 1024. Default is 4096, which is the maximum allowed value.\n */\n set maxDataTextureHeight(value) {\n value = Math.ceil(value / 1024) * 1024;\n if (value > 4096) {\n value = 4096;\n } else if (value < 1024) {\n value = 1024;\n }\n maxDataTextureHeight = value;\n }\n\n /**\n * Sets maximum data texture height.\n * @returns {*|number}\n */\n get maxDataTextureHeight() {\n return maxDataTextureHeight;\n }\n\n /**\n * Sets the maximum batched geometry VBO size.\n *\n * Default value is 5000000, which is the maximum size.\n *\n * Minimum size is 100000.\n */\n set maxGeometryBatchSize(value) {\n if (value < 100000) {\n value = 100000;\n } else if (value > 5000000) {\n value = 5000000;\n }\n maxGeometryBatchSize = value;\n }\n\n /**\n * Gets the maximum batched geometry VBO size.\n */\n get maxGeometryBatchSize() {\n return maxGeometryBatchSize;\n }\n}\n\nconst configs$2 = new Configs();\n\n/**\n * @private\n */\nclass VBOBatchingTrianglesBuffer {\n\n constructor() {\n this.maxVerts = configs$2.maxGeometryBatchSize;\n this.maxIndices = configs$2.maxGeometryBatchSize * 3; // Rough rule-of-thumb\n this.positions = [];\n this.colors = [];\n this.uv = [];\n this.metallicRoughness = [];\n this.normals = [];\n this.pickColors = [];\n this.offsets = [];\n this.indices = [];\n this.edgeIndices = [];\n }\n}\n\nconst translate = math.mat4();\nconst scale = math.mat4();\n\n/**\n * @private\n */\nfunction quantizePositions(positions, aabb, positionsDecodeMatrix) { // http://cg.postech.ac.kr/research/mesh_comp_mobile/mesh_comp_mobile_conference.pdf\n const lenPositions = positions.length;\n const quantizedPositions = new Uint16Array(lenPositions);\n const xmin = aabb[0];\n const ymin = aabb[1];\n const zmin = aabb[2];\n const xwid = aabb[3] - xmin;\n const ywid = aabb[4] - ymin;\n const zwid = aabb[5] - zmin;\n const maxInt = 65525;\n const xMultiplier = maxInt / xwid;\n const yMultiplier = maxInt / ywid;\n const zMultiplier = maxInt / zwid;\n const verify = (num) => num >= 0 ? num : 0;\n for (let i = 0; i < lenPositions; i += 3) {\n quantizedPositions[i + 0] = Math.floor(verify(positions[i + 0] - xmin) * xMultiplier);\n quantizedPositions[i + 1] = Math.floor(verify(positions[i + 1] - ymin) * yMultiplier);\n quantizedPositions[i + 2] = Math.floor(verify(positions[i + 2] - zmin) * zMultiplier);\n }\n math.identityMat4(translate);\n math.translationMat4v(aabb, translate);\n math.identityMat4(scale);\n math.scalingMat4v([xwid / maxInt, ywid / maxInt, zwid / maxInt], scale);\n math.mulMat4(translate, scale, positionsDecodeMatrix);\n return quantizedPositions;\n}\n\n/**\n * @private\n * @param aabb\n * @param positionsDecodeMatrix\n * @returns {*}\n */\nfunction createPositionsDecodeMatrix(aabb, positionsDecodeMatrix) { // http://cg.postech.ac.kr/research/mesh_comp_mobile/mesh_comp_mobile_conference.pdf\n const xmin = aabb[0];\n const ymin = aabb[1];\n const zmin = aabb[2];\n const xwid = aabb[3] - xmin;\n const ywid = aabb[4] - ymin;\n const zwid = aabb[5] - zmin;\n const maxInt = 65525;\n math.identityMat4(translate);\n math.translationMat4v(aabb, translate);\n math.identityMat4(scale);\n math.scalingMat4v([xwid / maxInt, ywid / maxInt, zwid / maxInt], scale);\n math.mulMat4(translate, scale, positionsDecodeMatrix);\n return positionsDecodeMatrix;\n}\n\n/**\n * @private\n */\nfunction transformAndOctEncodeNormals(worldNormalMatrix, normals, lenNormals, compressedNormals, lenCompressedNormals) {\n // http://jcgt.org/published/0003/02/01/\n\n function dot(array, vec3) {\n return array[0] * vec3[0] + array[1] * vec3[1] + array[2] * vec3[2];\n }\n\n let oct, dec, best, currentCos, bestCos;\n let i;\n let localNormal = new Float32Array([0, 0, 0, 0]);\n let worldNormal = new Float32Array([0, 0, 0, 0]);\n for (i = 0; i < lenNormals; i += 3) {\n localNormal[0] = normals[i];\n localNormal[1] = normals[i + 1];\n localNormal[2] = normals[i + 2];\n\n math.transformVec3(worldNormalMatrix, localNormal, worldNormal);\n math.normalizeVec3(worldNormal, worldNormal);\n\n // Test various combinations of ceil and floor to minimize rounding errors\n best = oct = octEncodeVec3(worldNormal, \"floor\", \"floor\");\n dec = octDecodeVec2(oct);\n currentCos = bestCos = dot(worldNormal, dec);\n oct = octEncodeVec3(worldNormal, \"ceil\", \"floor\");\n dec = octDecodeVec2(oct);\n currentCos = dot(worldNormal, dec);\n if (currentCos > bestCos) {\n best = oct;\n bestCos = currentCos;\n }\n oct = octEncodeVec3(worldNormal, \"floor\", \"ceil\");\n dec = octDecodeVec2(oct);\n currentCos = dot(worldNormal, dec);\n if (currentCos > bestCos) {\n best = oct;\n bestCos = currentCos;\n }\n oct = octEncodeVec3(worldNormal, \"ceil\", \"ceil\");\n dec = octDecodeVec2(oct);\n currentCos = dot(worldNormal, dec);\n if (currentCos > bestCos) {\n best = oct;\n bestCos = currentCos;\n }\n compressedNormals[lenCompressedNormals + i + 0] = best[0];\n compressedNormals[lenCompressedNormals + i + 1] = best[1];\n compressedNormals[lenCompressedNormals + i + 2] = 0.0; // Unused\n }\n lenCompressedNormals += lenNormals;\n return lenCompressedNormals;\n}\n\n/**\n * @private\n */\nfunction octEncodeVec3(p, xfunc, yfunc) { // Oct-encode single normal vector in 2 bytes\n let x = p[0] / (Math.abs(p[0]) + Math.abs(p[1]) + Math.abs(p[2]));\n let y = p[1] / (Math.abs(p[0]) + Math.abs(p[1]) + Math.abs(p[2]));\n if (p[2] < 0) {\n let tempx = x;\n let tempy = y;\n tempx = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n tempy = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n x = tempx;\n y = tempy;\n }\n return new Int8Array([\n Math[xfunc](x * 127.5 + (x < 0 ? -1 : 0)),\n Math[yfunc](y * 127.5 + (y < 0 ? -1 : 0))\n ]);\n}\n\n/**\n * @private\n */\nfunction octDecodeVec2(oct) { // Decode an oct-encoded normal\n let x = oct[0];\n let y = oct[1];\n x /= x < 0 ? 127 : 128;\n y /= y < 0 ? 127 : 128;\n const z = 1 - Math.abs(x) - Math.abs(y);\n if (z < 0) {\n x = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);\n y = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);\n }\n const length = Math.sqrt(x * x + y * y + z * z);\n return [\n x / length,\n y / length,\n z / length\n ];\n}\n\nconst tempMat4$1 = math.mat4();\nconst tempMat4b = math.mat4();\nconst tempVec4a$7 = math.vec4([0, 0, 0, 1]);\n\nconst tempVec3a$z = math.vec3();\nconst tempVec3b$v = math.vec3();\nconst tempVec3c$q = math.vec3();\nconst tempVec3d$a = math.vec3();\nconst tempVec3e$1 = math.vec3();\nconst tempVec3f$1 = math.vec3();\nconst tempVec3g$1 = math.vec3();\n\n/**\n * @private\n */\nclass VBOBatchingTrianglesLayer {\n\n /**\n * @param model\n * @param cfg.model\n * @param cfg.autoNormals\n * @param cfg.layerIndex\n * @param cfg.positionsDecodeMatrix\n * @param cfg.uvDecodeMatrix\n * @param cfg.maxGeometryBatchSize\n * @param cfg.origin\n * @param cfg.scratchMemory\n * @param cfg.textureSet\n * @param cfg.solid\n */\n constructor(cfg) {\n\n console.info(\"Creating VBOBatchingTrianglesLayer\");\n\n /**\n * Owner model\n * @type {VBOSceneModel}\n */\n this.model = cfg.model;\n\n /**\n * State sorting key.\n * @type {string}\n */\n this.sortId = \"TrianglesBatchingLayer\"\n + (cfg.solid ? \"-solid\" : \"-surface\")\n + (cfg.autoNormals ? \"-autonormals\" : \"-normals\")\n\n // TODO: These two parts need to be IDs (ie. unique):\n\n + (cfg.textureSet && cfg.textureSet.colorTexture ? \"-colorTexture\" : \"\")\n + (cfg.textureSet && cfg.textureSet.metallicRoughnessTexture ? \"-metallicRoughnessTexture\" : \"\");\n\n /**\n * Index of this TrianglesBatchingLayer in {@link VBOSceneModel#_layerList}.\n * @type {Number}\n */\n this.layerIndex = cfg.layerIndex;\n\n this._renderers = getRenderers$7(cfg.model.scene);\n this._buffer = new VBOBatchingTrianglesBuffer(cfg.maxGeometryBatchSize);\n this._scratchMemory = cfg.scratchMemory;\n\n this._state = new RenderState({\n origin: math.vec3(),\n positionsBuf: null,\n offsetsBuf: null,\n normalsBuf: null,\n colorsBuf: null,\n uvBuf: null,\n metallicRoughnessBuf: null,\n flagsBuf: null,\n indicesBuf: null,\n edgeIndicesBuf: null,\n positionsDecodeMatrix: null,\n uvDecodeMatrix: null,\n textureSet: cfg.textureSet,\n pbrSupported: false // Set in #finalize if we have enough to support quality rendering\n });\n\n // These counts are used to avoid unnecessary render passes\n this._numPortions = 0;\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numEdgesLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n this._modelAABB = math.collapseAABB3(); // Model-space AABB\n this._portions = [];\n this._meshes = [];\n this._numVerts = 0;\n\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n\n this._finalized = false;\n\n if (cfg.positionsDecodeMatrix) {\n this._state.positionsDecodeMatrix = math.mat4(cfg.positionsDecodeMatrix);\n }\n\n if (cfg.uvDecodeMatrix) {\n this._state.uvDecodeMatrix = math.mat3(cfg.uvDecodeMatrix);\n this._preCompressedUVsExpected = true;\n } else {\n this._preCompressedUVsExpected = false;\n }\n\n if (cfg.origin) {\n this._state.origin.set(cfg.origin);\n }\n\n /**\n * When true, this layer contains solid triangle meshes, otherwise this layer contains surface triangle meshes\n * @type {boolean}\n */\n this.solid = !!cfg.solid;\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Tests if there is room for another portion in this TrianglesBatchingLayer.\n *\n * @param lenPositions Number of positions we'd like to create in the portion.\n * @param lenIndices Number of indices we'd like to create in this portion.\n * @returns {Boolean} True if OK to create another portion.\n */\n canCreatePortion(lenPositions, lenIndices) {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n return ((this._buffer.positions.length + lenPositions) < (this._buffer.maxVerts * 3) && (this._buffer.indices.length + lenIndices) < (this._buffer.maxIndices));\n }\n\n /**\n * Creates a new portion within this TrianglesBatchingLayer, returns the new portion ID.\n *\n * Gives the portion the specified geometry, color and matrix.\n *\n * @param mesh The SceneModelMesh that owns the portion\n * @param cfg.positions Flat float Local-space positions array.\n * @param cfg.positionsCompressed Flat quantized positions array - decompressed with TrianglesBatchingLayer positionsDecodeMatrix\n * @param [cfg.normals] Flat float normals array.\n * @param [cfg.uv] Flat UVs array.\n * @param [cfg.uvCompressed]\n * @param [cfg.colors] Flat float colors array.\n * @param [cfg.colorsCompressed]\n * @param cfg.indices Flat int indices array.\n * @param [cfg.edgeIndices] Flat int edges indices array.\n * @param cfg.color Quantized RGB color [0..255,0..255,0..255,0..255]\n * @param cfg.metallic Metalness factor [0..255]\n * @param cfg.roughness Roughness factor [0..255]\n * @param cfg.opacity Opacity [0..255]\n * @param [cfg.meshMatrix] Flat float 4x4 matrix\n * @param cfg.aabb Flat float AABB World-space AABB\n * @param cfg.pickColor Quantized pick color\n * @returns {number} Portion ID\n */\n createPortion(mesh, cfg) {\n\n if (this._finalized) {\n throw \"Already finalized\";\n }\n\n const positions = cfg.positions;\n const positionsCompressed = cfg.positionsCompressed;\n const normals = cfg.normals;\n const normalsCompressed = cfg.normalsCompressed;\n const uv = cfg.uv;\n const uvCompressed = cfg.uvCompressed;\n const colors = cfg.colors;\n const colorsCompressed = cfg.colorsCompressed;\n const indices = cfg.indices;\n const edgeIndices = cfg.edgeIndices;\n const color = cfg.color;\n const metallic = cfg.metallic;\n const roughness = cfg.roughness;\n const opacity = cfg.opacity;\n const meshMatrix = cfg.meshMatrix;\n const pickColor = cfg.pickColor;\n\n const scene = this.model.scene;\n const buffer = this._buffer;\n const vertsBaseIndex = buffer.positions.length / 3;\n\n let numVerts;\n\n math.expandAABB3(this._modelAABB, cfg.aabb);\n\n if (this._state.positionsDecodeMatrix) {\n if (!positionsCompressed) {\n throw \"positionsCompressed expected\";\n }\n numVerts = positionsCompressed.length / 3;\n for (let i = 0, len = positionsCompressed.length; i < len; i++) {\n buffer.positions.push(positionsCompressed[i]);\n }\n } else {\n if (!positions) {\n throw \"positions expected\";\n }\n numVerts = positions.length / 3;\n for (let i = 0, len = positions.length; i < len; i++) {\n buffer.positions.push(positions[i]);\n }\n }\n\n if (normalsCompressed && normalsCompressed.length > 0) {\n for (let i = 0, len = normalsCompressed.length; i < len; i++) {\n buffer.normals.push(normalsCompressed[i]);\n }\n } else if (normals && normals.length > 0) {\n const worldNormalMatrix = tempMat4$1;\n if (meshMatrix) {\n math.inverseMat4(math.transposeMat4(meshMatrix, tempMat4b), worldNormalMatrix); // Note: order of inverse and transpose doesn't matter\n } else {\n math.identityMat4(worldNormalMatrix, worldNormalMatrix);\n }\n transformAndOctEncodeNormals(worldNormalMatrix, normals, normals.length, buffer.normals, buffer.normals.length);\n }\n\n if (colors) {\n for (let i = 0, len = colors.length; i < len; i += 3) {\n buffer.colors.push(colors[i] * 255);\n buffer.colors.push(colors[i + 1] * 255);\n buffer.colors.push(colors[i + 2] * 255);\n buffer.colors.push(255);\n }\n } else if (colorsCompressed) {\n for (let i = 0, len = colors.length; i < len; i += 3) {\n buffer.colors.push(colors[i]);\n buffer.colors.push(colors[i + 1]);\n buffer.colors.push(colors[i + 2]);\n buffer.colors.push(255);\n }\n } else if (color) {\n const r = color[0]; // Color is pre-quantized by VBOSceneModel\n const g = color[1];\n const b = color[2];\n const a = opacity;\n for (let i = 0; i < numVerts; i++) {\n buffer.colors.push(r);\n buffer.colors.push(g);\n buffer.colors.push(b);\n buffer.colors.push(a);\n }\n }\n const metallicValue = (metallic !== null && metallic !== undefined) ? metallic : 0;\n const roughnessValue = (roughness !== null && roughness !== undefined) ? roughness : 255;\n for (let i = 0; i < numVerts; i++) {\n buffer.metallicRoughness.push(metallicValue);\n buffer.metallicRoughness.push(roughnessValue);\n }\n\n if (uv && uv.length > 0) {\n for (let i = 0, len = uv.length; i < len; i++) {\n buffer.uv.push(uv[i]);\n }\n } else if (uvCompressed && uvCompressed.length > 0) {\n for (let i = 0, len = uvCompressed.length; i < len; i++) {\n buffer.uv.push(uvCompressed[i]);\n }\n }\n\n for (let i = 0, len = indices.length; i < len; i++) {\n buffer.indices.push(vertsBaseIndex + indices[i]);\n }\n\n\n if (edgeIndices) {\n for (let i = 0, len = edgeIndices.length; i < len; i++) {\n buffer.edgeIndices.push(vertsBaseIndex + edgeIndices[i]);\n }\n }\n\n {\n const pickColorsBase = buffer.pickColors.length;\n const lenPickColors = numVerts * 4;\n for (let i = pickColorsBase, len = pickColorsBase + lenPickColors; i < len; i += 4) {\n buffer.pickColors.push(pickColor[0]);\n buffer.pickColors.push(pickColor[1]);\n buffer.pickColors.push(pickColor[2]);\n buffer.pickColors.push(pickColor[3]);\n }\n }\n\n if (scene.entityOffsetsEnabled) {\n for (let i = 0; i < numVerts; i++) {\n buffer.offsets.push(0);\n buffer.offsets.push(0);\n buffer.offsets.push(0);\n }\n }\n\n const portionId = this._portions.length;\n\n const portion = {\n vertsBaseIndex: vertsBaseIndex,\n numVerts: numVerts,\n indicesBaseIndex: buffer.indices.length - indices.length,\n numIndices: indices.length,\n };\n\n if (scene.pickSurfacePrecisionEnabled) {\n // Quantized in-memory positions are initialized in finalize()\n\n portion.indices = indices;\n\n if (scene.entityOffsetsEnabled) {\n portion.offset = new Float32Array(3);\n }\n }\n\n this._portions.push(portion);\n this._numPortions++;\n this.model.numPortions++;\n this._numVerts += portion.numVerts;\n this._meshes.push(mesh);\n return portionId;\n }\n\n /**\n * Builds batch VBOs from appended geometries.\n * No more portions can then be created.\n */\n finalize() {\n\n if (this._finalized) {\n return;\n }\n\n const state = this._state;\n const gl = this.model.scene.canvas.gl;\n const buffer = this._buffer;\n\n if (buffer.positions.length > 0) {\n const quantizedPositions = (this._state.positionsDecodeMatrix)\n ? new Uint16Array(buffer.positions)\n : quantizePositions(buffer.positions, this._modelAABB, this._state.positionsDecodeMatrix = math.mat4()); // BOTTLENECK\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, quantizedPositions, quantizedPositions.length, 3, gl.STATIC_DRAW);\n if (this.model.scene.pickSurfacePrecisionEnabled) {\n for (let i = 0, numPortions = this._portions.length; i < numPortions; i++) {\n const portion = this._portions[i];\n const start = portion.vertsBaseIndex * 3;\n const end = start + (portion.numVerts * 3);\n portion.quantizedPositions = quantizedPositions.slice(start, end);\n }\n }\n }\n\n if (buffer.normals.length > 0) { // Normals are already oct-encoded\n const normals = new Int8Array(buffer.normals);\n let normalized = true; // For oct encoded UInts\n state.normalsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, normals, buffer.normals.length, 3, gl.STATIC_DRAW, normalized);\n }\n\n if (buffer.colors.length > 0) { // Colors are already compressed\n const colors = new Uint8Array(buffer.colors);\n let normalized = false;\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colors, buffer.colors.length, 4, gl.DYNAMIC_DRAW, normalized);\n }\n\n if (buffer.uv.length > 0) {\n if (!state.uvDecodeMatrix) {\n const bounds = geometryCompressionUtils.getUVBounds(buffer.uv);\n const result = geometryCompressionUtils.compressUVs(buffer.uv, bounds.min, bounds.max);\n const uv = result.quantized;\n let notNormalized = false;\n state.uvDecodeMatrix = math.mat3(result.decodeMatrix);\n state.uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uv, uv.length, 2, gl.STATIC_DRAW, notNormalized);\n } else {\n let notNormalized = false;\n state.uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, buffer.uv, buffer.uv.length, 2, gl.STATIC_DRAW, notNormalized);\n }\n }\n\n if (buffer.metallicRoughness.length > 0) {\n const metallicRoughness = new Uint8Array(buffer.metallicRoughness);\n let normalized = false;\n state.metallicRoughnessBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, metallicRoughness, buffer.metallicRoughness.length, 2, gl.STATIC_DRAW, normalized);\n }\n\n if (buffer.positions.length > 0) { // Because we build flags arrays here, get their length from the positions array\n const flagsLength = (buffer.positions.length / 3);\n const flags = new Float32Array(flagsLength);\n const notNormalized = false;\n state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, flags, flags.length, 1, gl.DYNAMIC_DRAW, notNormalized);\n }\n\n if (buffer.pickColors.length > 0) {\n const pickColors = new Uint8Array(buffer.pickColors);\n let normalized = false;\n state.pickColorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, pickColors, buffer.pickColors.length, 4, gl.STATIC_DRAW, normalized);\n }\n\n if (this.model.scene.entityOffsetsEnabled) {\n if (buffer.offsets.length > 0) {\n const offsets = new Float32Array(buffer.offsets);\n state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, offsets, buffer.offsets.length, 3, gl.DYNAMIC_DRAW);\n }\n }\n\n if (buffer.indices.length > 0) {\n const indices = new Uint32Array(buffer.indices);\n state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, buffer.indices.length, 1, gl.STATIC_DRAW);\n }\n if (buffer.edgeIndices.length > 0) {\n const edgeIndices = new Uint32Array(buffer.edgeIndices);\n state.edgeIndicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, edgeIndices, buffer.edgeIndices.length, 1, gl.STATIC_DRAW);\n }\n\n this._state.pbrSupported\n = !!state.metallicRoughnessBuf\n && !!state.uvBuf\n && !!state.normalsBuf\n && !!state.textureSet\n && !!state.textureSet.colorTexture\n && !!state.textureSet.metallicRoughnessTexture;\n\n this._state.colorTextureSupported\n = !!state.uvBuf\n && !!state.textureSet\n && !!state.textureSet.colorTexture;\n\n this._buffer = null;\n this._finalized = true;\n }\n\n isEmpty() {\n return (!this._state.indicesBuf);\n }\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n const deferred = true;\n this._setFlags(portionId, flags, meshTransparent, deferred);\n }\n\n flushInitFlags() {\n this._setDeferredFlags();\n }\n\n setVisible(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setHighlighted(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setXRayed(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setSelected(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setEdges(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n } else {\n this._numEdgesLayerPortions--;\n this.model.numEdgesLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags(portionId, flags);\n }\n\n setCulled(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions--;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setColor(portionId, color) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n const portionsIdx = portionId;\n const portion = this._portions[portionsIdx];\n const vertsBaseIndex = portion.vertsBaseIndex;\n const numVerts = portion.numVerts;\n const firstColor = vertsBaseIndex * 4;\n const lenColor = numVerts * 4;\n const tempArray = this._scratchMemory.getUInt8Array(lenColor);\n const r = color[0];\n const g = color[1];\n const b = color[2];\n const a = color[3];\n for (let i = 0; i < lenColor; i += 4) {\n tempArray[i + 0] = r;\n tempArray[i + 1] = g;\n tempArray[i + 2] = b;\n tempArray[i + 3] = a;\n }\n if (this._state.colorsBuf) {\n this._state.colorsBuf.setData(tempArray, firstColor, lenColor);\n }\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n /**\n * flags are 4bits values encoded on a 32bit base. color flag on the first 4 bits, silhouette flag on the next 4 bits and so on for edge, pick and clippable.\n */\n _setFlags(portionId, flags, transparent, deferred = false) {\n\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const portionsIdx = portionId;\n const portion = this._portions[portionsIdx];\n const vertsBaseIndex = portion.vertsBaseIndex;\n const numVerts = portion.numVerts;\n const firstFlag = vertsBaseIndex;\n const lenFlags = numVerts;\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n const edges = !!(flags & ENTITY_FLAGS.EDGES);\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n let colorFlag;\n if (!visible || culled || xrayed\n || (highlighted && !this.model.scene.highlightMaterial.glowThrough)\n || (selected && !this.model.scene.selectedMaterial.glowThrough)) {\n colorFlag = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (transparent) {\n colorFlag = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n colorFlag = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n let silhouetteFlag;\n if (!visible || culled) {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let edgeFlag = 0;\n if (!visible || culled) {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n edgeFlag = RENDER_PASSES.EDGES_SELECTED;\n } else if (highlighted) {\n edgeFlag = RENDER_PASSES.EDGES_HIGHLIGHTED;\n } else if (xrayed) {\n edgeFlag = RENDER_PASSES.EDGES_XRAYED;\n } else if (edges) {\n if (transparent) {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;\n } else {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_OPAQUE;\n }\n } else {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n\n const clippableFlag = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 1 : 0;\n\n if (deferred) {\n // Avoid zillions of individual WebGL bufferSubData calls - buffer them to apply in one shot\n if (!this._deferredFlagValues) {\n this._deferredFlagValues = new Float32Array(this._numVerts);\n }\n for (let i = firstFlag, len = (firstFlag + lenFlags); i < len; i++) {\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n vertFlag |= edgeFlag << 8;\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n this._deferredFlagValues[i] = vertFlag;\n }\n } else if (this._state.flagsBuf) {\n const tempArray = this._scratchMemory.getFloat32Array(lenFlags);\n for (let i = 0; i < lenFlags; i++) {\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n vertFlag |= edgeFlag << 8;\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n tempArray[i] = vertFlag;\n }\n this._state.flagsBuf.setData(tempArray, firstFlag, lenFlags);\n }\n }\n\n _setDeferredFlags() {\n if (this._deferredFlagValues) {\n this._state.flagsBuf.setData(this._deferredFlagValues);\n this._deferredFlagValues = null;\n }\n }\n\n setOffset(portionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (!this.model.scene.entityOffsetsEnabled) {\n this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n return;\n }\n const portionsIdx = portionId;\n const portion = this._portions[portionsIdx];\n const vertsBaseIndex = portion.vertsBaseIndex;\n const numVerts = portion.numVerts;\n const firstOffset = vertsBaseIndex * 3;\n const lenOffsets = numVerts * 3;\n const tempArray = this._scratchMemory.getFloat32Array(lenOffsets);\n const x = offset[0];\n const y = offset[1];\n const z = offset[2];\n for (let i = 0; i < lenOffsets; i += 3) {\n tempArray[i + 0] = x;\n tempArray[i + 1] = y;\n tempArray[i + 2] = z;\n }\n if (this._state.offsetsBuf) {\n this._state.offsetsBuf.setData(tempArray, firstOffset, lenOffsets);\n }\n if (this.model.scene.pickSurfacePrecisionEnabled) {\n portion.offset[0] = offset[0];\n portion.offset[1] = offset[1];\n portion.offset[2] = offset[2];\n }\n }\n\n getEachVertex(portionId, callback) {\n if (!this.model.scene.pickSurfacePrecisionEnabled) {\n return;\n }\n const state = this._state;\n const portion = this._portions[portionId];\n if (!portion) {\n this.model.error(\"portion not found: \" + portionId);\n return;\n }\n const positions = portion.quantizedPositions;\n const origin = state.origin;\n const offset = portion.offset;\n const offsetX = origin[0] + offset[0];\n const offsetY = origin[1] + offset[1];\n const offsetZ = origin[2] + offset[2];\n const worldPos = tempVec4a$7;\n for (let i = 0, len = positions.length; i < len; i += 3) {\n worldPos[0] = positions[i];\n worldPos[1] = positions[i + 1];\n worldPos[2] = positions[i + 2];\n worldPos[3] = 1.0;\n math.decompressPosition(worldPos, state.positionsDecodeMatrix);\n math.transformPoint4(this.model.worldMatrix, worldPos);\n worldPos[0] += offsetX;\n worldPos[1] += offsetY;\n worldPos[2] += offsetZ;\n callback(worldPos);\n }\n }\n\n getElementsCountAndOffset(portionId) {\n let count = null;\n let offset = null;\n const portion = this._portions[portionId];\n\n if (portion) {\n count = portion.numIndices;\n offset = portion.indicesBaseIndex;\n }\n\n return {count, offset}\n }\n\n // ---------------------- COLOR RENDERING -----------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (frameCtx.withSAO && this.model.saoEnabled) {\n if (frameCtx.pbrEnabled && this.model.pbrEnabled && this._state.pbrSupported) {\n if (this._renderers.pbrRendererWithSAO) {\n this._renderers.pbrRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (frameCtx.colorTextureEnabled && this.model.colorTextureEnabled && this._state.colorTextureSupported) {\n if (this._renderers.colorTextureRendererWithSAO) {\n this._renderers.colorTextureRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (this._state.normalsBuf) {\n if (this._renderers.colorRendererWithSAO) {\n this._renderers.colorRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else {\n if (this._renderers.flatColorRendererWithSAO) {\n this._renderers.flatColorRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n } else {\n if (frameCtx.pbrEnabled && this.model.pbrEnabled && this._state.pbrSupported) {\n if (this._renderers.pbrRenderer) {\n this._renderers.pbrRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (frameCtx.colorTextureEnabled && this.model.colorTextureEnabled && this._state.colorTextureSupported) {\n if (this._renderers.colorTextureRenderer) {\n this._renderers.colorTextureRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (this._state.normalsBuf) {\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else {\n if (this._renderers.flatColorRenderer) {\n this._renderers.flatColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n }\n }\n\n _updateBackfaceCull(renderFlags, frameCtx) {\n const backfaces = this.model.backfaces || (!this.solid) || renderFlags.sectioned;\n if (frameCtx.backfaces !== backfaces) {\n const gl = frameCtx.gl;\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (frameCtx.pbrEnabled && this.model.pbrEnabled && this._state.pbrSupported) {\n if (this._renderers.pbrRenderer) {\n this._renderers.pbrRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n } else if (frameCtx.colorTextureEnabled && this.model.colorTextureEnabled && this._state.colorTextureSupported) {\n if (this._renderers.colorTextureRenderer) {\n this._renderers.colorTextureRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n } else if (this._state.normalsBuf) {\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n } else {\n if (this._renderers.flatColorRenderer) {\n this._renderers.flatColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n }\n\n // ---------------------- RENDERING SAO POST EFFECT TARGETS --------------\n\n drawDepth(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.depthRenderer) {\n this._renderers.depthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE); // Assume whatever post-effect uses depth (eg SAO) does not apply to transparent objects\n }\n }\n\n drawNormals(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.normalsRenderer) {\n this._renderers.normalsRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE); // Assume whatever post-effect uses normals (eg SAO) does not apply to transparent objects\n }\n }\n\n // ---------------------- SILHOUETTE RENDERING -----------------------------------\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n }\n }\n\n // ---------------------- EDGES RENDERING -----------------------------------\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numEdgesLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesColorRenderer) {\n this._renderers.edgesColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_COLOR_OPAQUE);\n }\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numEdgesLayerPortions === 0 || this._numTransparentLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesColorRenderer) {\n this._renderers.edgesColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_COLOR_TRANSPARENT);\n }\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_HIGHLIGHTED);\n }\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_SELECTED);\n }\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_XRAYED);\n }\n }\n\n // ---------------------- OCCLUSION CULL RENDERING -----------------------------------\n\n drawOcclusion(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.occlusionRenderer) {\n this._renderers.occlusionRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n // ---------------------- SHADOW BUFFER RENDERING -----------------------------------\n\n drawShadow(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.shadowRenderer) {\n this._renderers.shadowRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n //---- PICKING ----------------------------------------------------------------------------------------------------\n\n drawPickMesh(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.pickMeshRenderer) {\n this._renderers.pickMeshRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickDepths(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.pickDepthRenderer) {\n this._renderers.pickDepthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickNormals(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n\n ////////////////////////////////////////////////////////////////////////////////////////////////////\n // TODO\n // if (this._state.normalsBuf) {\n // if (this._renderers.pickNormalsRenderer) {\n // this._renderers.pickNormalsRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n // }\n ////////////////////////////////////////////////////////////////////////////////////////////////////\n // } else {\n if (this._renderers.pickNormalsFlatRenderer) {\n this._renderers.pickNormalsFlatRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n // }\n }\n\n drawSnapInit(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.snapInitRenderer) {\n this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnap(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.snapRenderer) {\n this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n //------------------------------------------------------------------------------------------------\n\n precisionRayPickSurface(portionId, worldRayOrigin, worldRayDir, worldSurfacePos, worldNormal) {\n\n if (!this.model.scene.pickSurfacePrecisionEnabled) {\n return false;\n }\n\n const state = this._state;\n const portion = this._portions[portionId];\n\n if (!portion) {\n this.model.error(\"portion not found: \" + portionId);\n return false;\n }\n\n const positions = portion.quantizedPositions;\n const indices = portion.indices;\n const origin = state.origin;\n const offset = portion.offset;\n\n const rtcRayOrigin = tempVec3a$z;\n const rtcRayDir = tempVec3b$v;\n\n rtcRayOrigin.set(origin ? math.subVec3(worldRayOrigin, origin, tempVec3c$q) : worldRayOrigin); // World -> RTC\n rtcRayDir.set(worldRayDir);\n\n if (offset) {\n math.subVec3(rtcRayOrigin, offset);\n }\n\n math.transformRay(this.model.worldNormalMatrix, rtcRayOrigin, rtcRayDir, rtcRayOrigin, rtcRayDir); // RTC -> local\n\n const a = tempVec3d$a;\n const b = tempVec3e$1;\n const c = tempVec3f$1;\n\n let gotIntersect = false;\n let closestDist = 0;\n const closestIntersectPos = tempVec3g$1;\n\n for (let i = 0, len = indices.length; i < len; i += 3) {\n\n const ia = indices[i] * 3;\n const ib = indices[i + 1] * 3;\n const ic = indices[i + 2] * 3;\n\n a[0] = positions[ia];\n a[1] = positions[ia + 1];\n a[2] = positions[ia + 2];\n\n b[0] = positions[ib];\n b[1] = positions[ib + 1];\n b[2] = positions[ib + 2];\n\n c[0] = positions[ic];\n c[1] = positions[ic + 1];\n c[2] = positions[ic + 2];\n\n math.decompressPosition(a, state.positionsDecodeMatrix);\n math.decompressPosition(b, state.positionsDecodeMatrix);\n math.decompressPosition(c, state.positionsDecodeMatrix);\n\n if (math.rayTriangleIntersect(rtcRayOrigin, rtcRayDir, a, b, c, closestIntersectPos)) {\n\n math.transformPoint3(this.model.worldMatrix, closestIntersectPos, closestIntersectPos);\n\n if (offset) {\n math.addVec3(closestIntersectPos, offset);\n }\n\n if (origin) {\n math.addVec3(closestIntersectPos, origin);\n }\n\n const dist = Math.abs(math.lenVec3(math.subVec3(closestIntersectPos, worldRayOrigin, [])));\n\n if (!gotIntersect || dist > closestDist) {\n closestDist = dist;\n worldSurfacePos.set(closestIntersectPos);\n if (worldNormal) { // Not that wasteful to eagerly compute - unlikely to hit >2 surfaces on most geometry\n math.triangleNormal(a, b, c, worldNormal);\n }\n gotIntersect = true;\n }\n }\n }\n\n if (gotIntersect && worldNormal) {\n math.transformVec3(this.model.worldNormalMatrix, worldNormal, worldNormal);\n math.normalizeVec3(worldNormal);\n }\n\n return gotIntersect;\n }\n\n // ---------\n\n destroy() {\n const state = this._state;\n if (state.positionsBuf) {\n state.positionsBuf.destroy();\n state.positionsBuf = null;\n }\n if (state.offsetsBuf) {\n state.offsetsBuf.destroy();\n state.offsetsBuf = null;\n }\n if (state.normalsBuf) {\n state.normalsBuf.destroy();\n state.normalsBuf = null;\n }\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n state.colorsBuf = null;\n }\n if (state.metallicRoughnessBuf) {\n state.metallicRoughnessBuf.destroy();\n state.metallicRoughnessBuf = null;\n }\n if (state.flagsBuf) {\n state.flagsBuf.destroy();\n state.flagsBuf = null;\n }\n if (state.pickColorsBuf) {\n state.pickColorsBuf.destroy();\n state.pickColorsBuf = null;\n }\n if (state.indicesBuf) {\n state.indicesBuf.destroy();\n state.indicessBuf = null;\n }\n if (state.edgeIndicesBuf) {\n state.edgeIndicesBuf.destroy();\n state.edgeIndicessBuf = null;\n }\n state.destroy();\n }\n}\n\n/**\n * @private\n */\nclass TrianglesInstancingRenderer extends VBORenderer {\n constructor(scene, withSAO, {edges = false} = {}) {\n super(scene, withSAO, {instancing: true, edges});\n }\n\n _draw(drawCfg) {\n const {gl} = this._scene.canvas;\n\n const {\n state,\n frameCtx,\n incrementDrawState,\n } = drawCfg;\n\n if (this._edges) {\n gl.drawElementsInstanced(gl.LINES, state.edgeIndicesBuf.numItems, state.edgeIndicesBuf.itemType, 0, state.numInstances);\n } else {\n gl.drawElementsInstanced(gl.TRIANGLES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0, state.numInstances);\n if (incrementDrawState) {\n frameCtx.drawElements++;\n }\n }\n }\n}\n\n/**\n * @private\n */\nclass TrianglesColorRenderer extends TrianglesInstancingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n let i;\n let len;\n let light;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry drawing vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec2 normal;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n src.push(\"in vec4 modelNormalMatrixCol0;\");\n src.push(\"in vec4 modelNormalMatrixCol1;\");\n src.push(\"in vec4 modelNormalMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src, true);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n for (i = 0, len = lightsState.lights.length; i < len; i++) {\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE | COLOR_TRANSPARENT\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vec4 modelNormal = vec4(octDecode(normal.xy), 0.0); \");\n src.push(\"vec4 worldNormal = worldNormalMatrix * vec4(dot(modelNormal, modelNormalMatrixCol0), dot(modelNormal, modelNormalMatrixCol1), dot(modelNormal, modelNormalMatrixCol2), 0.0);\");\n src.push(\"vec3 viewNormal = normalize(vec4(viewNormalMatrix * worldNormal).xyz);\");\n\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n\n src.push(\"float lambertian = 1.0;\");\n for (i = 0, len = lightsState.lights.length; i < len; i++) {\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n\n src.push(\"vec3 rgb = (vec3(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0));\");\n src.push(\"vColor = vec4((lightAmbient.rgb * lightAmbient.a * rgb) + (reflectedColor * rgb), float(color.a) / 255.0);\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry drawing fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n\n if (this._withSAO) {\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" outColor = vec4(newColor.rgb * ambient, 1.0);\");\n } else {\n src.push(\" outColor = newColor;\");\n }\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass TrianglesFlatColorRenderer extends TrianglesInstancingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry flat-shading drawing vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE | COLOR_TRANSPARENT\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, float(color.a) / 255.0);\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n let i;\n let len;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry flat-shading drawing fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n for (i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n\n src.push(\"float lambertian = 1.0;\");\n\n src.push(\"vec3 xTangent = dFdx( vViewPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vViewPosition.xyz );\");\n src.push(\"vec3 viewNormal = normalize( cross( xTangent, yTangent ) );\");\n\n for (i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n\n src.push(\"vec4 fragColor = vec4((lightAmbient.rgb * lightAmbient.a * newColor.rgb) + (reflectedColor * newColor.rgb), newColor.a);\");\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" outColor = vec4(fragColor.rgb * ambient, 1.0);\");\n } else {\n src.push(\" outColor = fragColor;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesSilhouetteRenderer extends TrianglesInstancingRenderer {\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n // TODO color uniform true ???\n super.drawLayer(frameCtx, instancingLayer, renderPass, { colorUniform: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing silhouette vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 color;\");\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec4 silhouetteColor;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // silhouetteFlag = NOT_RENDERED | SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n // renderPass = SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n\n src.push(`int silhouetteFlag = int(flags) >> 4 & 0xF;`);\n src.push(`if (silhouetteFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vColor = vec4(silhouetteColor.r, silhouetteColor.g, silhouetteColor.b, min(silhouetteColor.a, float(color.a) / 255.0));\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing fill fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = newColor;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass EdgesRenderer extends TrianglesInstancingRenderer {\n constructor(scene, withSAO) {\n super(scene, withSAO, {instancing: true, edges: true});\n }\n}\n\n/**\n * @private\n */\n\n\nclass EdgesEmphasisRenderer extends EdgesRenderer {\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n super.drawLayer(frameCtx, instancingLayer, renderPass, {colorUniform: true});\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// EdgesEmphasisRenderer vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n src.push(\"uniform vec4 color;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // edgeFlag = NOT_RENDERED | EDGES_COLOR_OPAQUE | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n // renderPass = EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n\n src.push(`int edgeFlag = int(flags) >> 8 & 0xF;`);\n src.push(`if (edgeFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = worldMatrix * positionsDecodeMatrix * vec4(position, 1.0); \");\n\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"vColor = vec4(color.r, color.g, color.b, color.a);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// EdgesEmphasisRenderer fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass EdgesColorRenderer extends EdgesRenderer {\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n super.drawLayer(frameCtx, batchingLayer, renderPass, { colorUniform: false });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// EdgesColorRenderer vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // edgeFlag = NOT_RENDERED | EDGES_COLOR_OPAQUE | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n // renderPass = EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n\n src.push(`int edgeFlag = int(flags) >> 8 & 0xF;`);\n src.push(`if (edgeFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n // src.push(\"vColor = vec4(float(color.r-100.0) / 255.0, float(color.g-100.0) / 255.0, float(color.b-100.0) / 255.0, float(color.a) / 255.0);\");\n src.push(\"vColor = vec4(float(color.r*0.5) / 255.0, float(color.g*0.5) / 255.0, float(color.b*0.5) / 255.0, float(color.a) / 255.0);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// EdgesColorRenderer fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickMeshRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry picking vertex shader\");\n \n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 pickColor;\");\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vPickColor;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\" vPickColor = vec4(float(pickColor.r) / 255.0, float(pickColor.g) / 255.0, float(pickColor.b) / 255.0, float(pickColor.a) / 255.0);\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry picking fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vPickColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = vPickColor; \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickDepthRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry depth vertex shader\");\n\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\" vViewPosition = viewPosition;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry depth fragment shader\");\n\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n src.push(\"uniform float pickZNear;\");\n src.push(\"uniform float pickZFar;\");\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"vec4 packDepth(const in float depth) {\");\n src.push(\" const vec4 bitShift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);\");\n src.push(\" const vec4 bitMask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);\");\n src.push(\" vec4 res = fract(depth * bitShift);\");\n src.push(\" res -= res.xxyz * bitMask;\");\n src.push(\" return res;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" float zNormalizedDepth = abs((pickZNear + vViewPosition.z) / (pickZFar - pickZNear));\");\n src.push(\" outColor = packDepth(zNormalizedDepth); \"); // Must be linear depth\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickNormalsRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry normals vertex shader\");\n \n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec2 normal;\");\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"in vec4 modelNormalMatrixCol0;\");\n src.push(\"in vec4 modelNormalMatrixCol1;\");\n src.push(\"in vec4 modelNormalMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src, 3);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec3 vWorldNormal;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vec4 modelNormal = vec4(octDecode(normal.xy), 0.0); \");\n src.push(\" vec3 worldNormal = vec3(dot(modelNormal, modelNormalMatrixCol0), dot(modelNormal, modelNormalMatrixCol1), dot(modelNormal, modelNormalMatrixCol2));\");\n src.push(\" vWorldNormal = worldNormal;\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry normals fragment shader\");\n\n \n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec3 vWorldNormal;\");\n src.push(\"out highp ivec4 outNormal;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(` outNormal = ivec4(vWorldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesOcclusionRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// TrianglesInstancingOcclusionRenderer vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE | COLOR_TRANSPARENT\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\");\n\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// TrianglesInstancingOcclusionRenderer fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n src.push(\" outColor = vec4(0.0, 0.0, 1.0, 1.0); \"); // Occluders are blue\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass TrianglesDepthRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry depth drawing vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\");\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec2 vHighPrecisionZW;\");\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"vHighPrecisionZW = gl_Position.zw;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n let i;\n let len;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry depth drawing fragment shader\");\n\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec2 vHighPrecisionZW;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\");\n src.push(\" outColor = vec4(vec3(1.0 - fragCoordZ), 1.0); \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesNormalsRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry normals drawing vertex shader\");\n \n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec3 normal;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\");\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src, true);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec3 vViewNormal;\");\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\");\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vec4 worldNormal = worldNormalMatrix * vec4(octDecode(normal.xy), 0.0); \");\n src.push(\" vec3 viewNormal = normalize((viewNormalMatrix * worldNormal).xyz);\");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\" vViewNormal = viewNormal;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry depth drawing fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec3 vViewNormal;\");\n src.push(\"vec3 packNormalToRGB( const in vec3 normal ) {\");\n src.push(\" return normalize( normal ) * 0.5 + 0.5;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vec4(packNormalToRGB(vViewNormal), 1.0); \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * Renders InstancingLayer fragment depths to a shadow map.\n *\n * @private\n */\nclass TrianglesShadowRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry shadow drawing vertex shader\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\");\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform mat4 shadowViewMatrix;\");\n src.push(\"uniform mat4 shadowProjMatrix;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(\"bool visible = (colorFlag > 0);\");\n src.push(\"bool transparent = ((float(color.a) / 255.0) < 1.0);\");\n src.push(`if (!visible || transparent) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = shadowViewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\" gl_Position = shadowProjMatrix * viewPosition;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry depth drawing fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec3 vViewNormal;\");\n src.push(\"vec3 packNormalToRGB( const in vec3 normal ) {\");\n src.push(\" return normalize( normal ) * 0.5 + 0.5;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vec4(packNormalToRGB(vViewNormal), 1.0); \");\n src.push(\"}\");\n return src;\n }\n}\n\nconst TEXTURE_DECODE_FUNCS = {};\nTEXTURE_DECODE_FUNCS[LinearEncoding] = \"linearToLinear\";\nTEXTURE_DECODE_FUNCS[sRGBEncoding] = \"sRGBToLinear\";\n\n/**\n * @private\n */\nclass TrianglesPBRRenderer extends TrianglesInstancingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene.gammaOutput, scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const clippingCaps = sectionPlanesState.clippingCaps;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry quality drawing vertex shader\");\n\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec3 normal;\");\n src.push(\"in vec4 color;\");\n src.push(\"in vec2 uv;\");\n src.push(\"in vec2 metallicRoughness;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n src.push(\"in vec4 modelNormalMatrixCol0;\");\n src.push(\"in vec4 modelNormalMatrixCol1;\");\n src.push(\"in vec4 modelNormalMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src, true);\n\n src.push(\"uniform mat3 uvDecodeMatrix;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out vec3 vViewNormal;\");\n src.push(\"out vec4 vColor;\");\n src.push(\"out vec2 vUV;\");\n src.push(\"out vec2 vMetallicRoughness;\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"out vec3 vWorldNormal;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n if (clippingCaps) {\n src.push(\"out vec4 vClipPosition;\");\n }\n }\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE | COLOR_TRANSPARENT\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vec4 modelNormal = vec4(octDecode(normal.xy), 0.0); \");\n src.push(\"vec4 worldNormal = worldNormalMatrix * vec4(dot(modelNormal, modelNormalMatrixCol0), dot(modelNormal, modelNormalMatrixCol1), dot(modelNormal, modelNormalMatrixCol2), 1.0);\");\n src.push(\"vec3 viewNormal = vec4(viewNormalMatrix * worldNormal).xyz;\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n if (clippingCaps) {\n src.push(\"vClipPosition = clipPos;\");\n }\n }\n\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vViewNormal = viewNormal;\");\n src.push(\"vColor = color;\");\n src.push(\"vUV = (uvDecodeMatrix * vec3(uv, 1.0)).xy;\");\n src.push(\"vMetallicRoughness = metallicRoughness;\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"vWorldNormal = worldNormal.xyz;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n\n const scene = this._scene;\n const gammaOutput = scene.gammaOutput; // If set, then it expects that all textures and colors need to be outputted in premultiplied gamma. Default is false.\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const clippingCaps = sectionPlanesState.clippingCaps;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry quality drawing fragment shader\");\n\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n src.push(\"uniform sampler2D uColorMap;\");\n src.push(\"uniform sampler2D uMetallicRoughMap;\");\n src.push(\"uniform sampler2D uEmissiveMap;\");\n src.push(\"uniform sampler2D uNormalMap;\");\n\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\"uniform samplerCube reflectionMap;\");\n }\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"uniform samplerCube lightMap;\");\n }\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n src.push(\"uniform float gammaFactor;\");\n src.push(\"vec4 linearToLinear( in vec4 value ) {\");\n src.push(\" return value;\");\n src.push(\"}\");\n src.push(\"vec4 sRGBToLinear( in vec4 value ) {\");\n src.push(\" return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\");\n src.push(\"}\");\n src.push(\"vec4 gammaToLinear( in vec4 value) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\");\n src.push(\"}\");\n if (gammaOutput) {\n src.push(\"vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n if (clippingCaps) {\n src.push(\"in vec4 vClipPosition;\");\n }\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"in vec3 vViewNormal;\");\n src.push(\"in vec4 vColor;\");\n src.push(\"in vec2 vUV;\");\n src.push(\"in vec2 vMetallicRoughness;\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"in vec3 vWorldNormal;\");\n }\n\n this._addMatricesUniformBlockLines(src, true);\n\n // CONSTANT DEFINITIONS\n\n src.push(\"#define PI 3.14159265359\");\n src.push(\"#define RECIPROCAL_PI 0.31830988618\");\n src.push(\"#define RECIPROCAL_PI2 0.15915494\");\n src.push(\"#define EPSILON 1e-6\");\n\n src.push(\"#define saturate(a) clamp( a, 0.0, 1.0 )\");\n\n // UTILITY DEFINITIONS\n\n src.push(\"vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\");\n src.push(\" vec3 texel = texture( uNormalMap, uv ).xyz;\");\n src.push(\" if (texel.r == 0.0 && texel.g == 0.0 && texel.b == 0.0) {\");\n src.push(\" return normalize(surf_norm );\");\n src.push(\" }\");\n src.push(\" vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\");\n src.push(\" vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\");\n src.push(\" vec2 st0 = dFdx( uv.st );\");\n src.push(\" vec2 st1 = dFdy( uv.st );\");\n src.push(\" vec3 S = normalize( q0 * st1.t - q1 * st0.t );\");\n src.push(\" vec3 T = normalize( -q0 * st1.s + q1 * st0.s );\");\n src.push(\" vec3 N = normalize( surf_norm );\");\n src.push(\" vec3 mapN = texel.xyz * 2.0 - 1.0;\");\n src.push(\" mat3 tsn = mat3( S, T, N );\");\n // src.push(\" mapN *= 3.0;\");\n src.push(\" return normalize( tsn * mapN );\");\n src.push(\"}\");\n\n src.push(\"vec3 inverseTransformDirection(in vec3 dir, in mat4 matrix) {\");\n src.push(\" return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\");\n src.push(\"}\");\n\n // STRUCTURES\n\n src.push(\"struct IncidentLight {\");\n src.push(\" vec3 color;\");\n src.push(\" vec3 direction;\");\n src.push(\"};\");\n\n src.push(\"struct ReflectedLight {\");\n src.push(\" vec3 diffuse;\");\n src.push(\" vec3 specular;\");\n src.push(\"};\");\n\n src.push(\"struct Geometry {\");\n src.push(\" vec3 position;\");\n src.push(\" vec3 viewNormal;\");\n src.push(\" vec3 worldNormal;\");\n src.push(\" vec3 viewEyeDir;\");\n src.push(\"};\");\n\n src.push(\"struct Material {\");\n src.push(\" vec3 diffuseColor;\");\n src.push(\" float specularRoughness;\");\n src.push(\" vec3 specularColor;\");\n src.push(\" float shine;\"); // Only used for Phong\n src.push(\"};\");\n\n // IRRADIANCE EVALUATION\n\n src.push(\"float GGXRoughnessToBlinnExponent(const in float ggxRoughness) {\");\n src.push(\" float r = ggxRoughness + 0.0001;\");\n src.push(\" return (2.0 / (r * r) - 2.0);\");\n src.push(\"}\");\n\n src.push(\"float getSpecularMIPLevel(const in float blinnShininessExponent, const in int maxMIPLevel) {\");\n src.push(\" float maxMIPLevelScalar = float( maxMIPLevel );\");\n src.push(\" float desiredMIPLevel = maxMIPLevelScalar - 0.79248 - 0.5 * log2( ( blinnShininessExponent * blinnShininessExponent ) + 1.0 );\");\n src.push(\" return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\");\n src.push(\"}\");\n\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\"vec3 getLightProbeIndirectRadiance(const in vec3 reflectVec, const in float blinnShininessExponent, const in int maxMIPLevel) {\");\n src.push(\" float mipLevel = 0.5 * getSpecularMIPLevel(blinnShininessExponent, maxMIPLevel);\"); //TODO: a random factor - fix this\n src.push(\" vec3 envMapColor = \" + TEXTURE_DECODE_FUNCS[lightsState.reflectionMaps[0].encoding] + \"(texture(reflectionMap, reflectVec, mipLevel)).rgb;\");\n src.push(\" return envMapColor;\");\n src.push(\"}\");\n }\n\n // SPECULAR BRDF EVALUATION\n\n src.push(\"vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {\");\n src.push(\" float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\");\n src.push(\" return ( 1.0 - specularColor ) * fresnel + specularColor;\");\n src.push(\"}\");\n\n src.push(\"float G_GGX_Smith(const in float alpha, const in float dotNL, const in float dotNV) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );\");\n src.push(\" float gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );\");\n src.push(\" return 1.0 / ( gl * gv );\");\n src.push(\"}\");\n\n src.push(\"float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );\");\n src.push(\" float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );\");\n src.push(\" return 0.5 / max( gv + gl, EPSILON );\");\n src.push(\"}\");\n\n src.push(\"float D_GGX(const in float alpha, const in float dotNH) {\");\n src.push(\" float a2 = ( alpha * alpha );\");\n src.push(\" float denom = ( dotNH * dotNH) * ( a2 - 1.0 ) + 1.0;\");\n src.push(\" return RECIPROCAL_PI * a2 / ( denom * denom);\");\n src.push(\"}\");\n\n src.push(\"vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in Geometry geometry, const in vec3 specularColor, const in float roughness) {\");\n src.push(\" float alpha = ( roughness * roughness );\");\n src.push(\" vec3 halfDir = normalize( incidentLight.direction + geometry.viewEyeDir );\");\n src.push(\" float dotNL = saturate( dot( geometry.viewNormal, incidentLight.direction ) );\");\n src.push(\" float dotNV = saturate( dot( geometry.viewNormal, geometry.viewEyeDir ) );\");\n src.push(\" float dotNH = saturate( dot( geometry.viewNormal, halfDir ) );\");\n src.push(\" float dotLH = saturate( dot( incidentLight.direction, halfDir ) );\");\n src.push(\" vec3 F = F_Schlick( specularColor, dotLH );\");\n src.push(\" float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\");\n src.push(\" float D = D_GGX( alpha, dotNH );\");\n src.push(\" return F * (G * D);\");\n src.push(\"}\");\n\n src.push(\"vec3 BRDF_Specular_GGX_Environment(const in Geometry geometry, const in vec3 specularColor, const in float roughness) {\");\n src.push(\" float dotNV = saturate(dot(geometry.viewNormal, geometry.viewEyeDir));\");\n src.push(\" const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022);\");\n src.push(\" const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04);\");\n src.push(\" vec4 r = roughness * c0 + c1;\");\n src.push(\" float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;\");\n src.push(\" vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;\");\n src.push(\" return specularColor * AB.x + AB.y;\");\n src.push(\"}\");\n\n if (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0) {\n\n src.push(\"void computePBRLightMapping(const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\" vec3 irradiance = \" + TEXTURE_DECODE_FUNCS[lightsState.lightMaps[0].encoding] + \"(texture(lightMap, geometry.worldNormal)).rgb;\");\n src.push(\" irradiance *= PI;\");\n src.push(\" vec3 diffuseBRDFContrib = (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.diffuse += irradiance * diffuseBRDFContrib;\");\n }\n\n if (lightsState.reflectionMaps.length > 0) {\n src.push(\" vec3 reflectVec = reflect(geometry.viewEyeDir, geometry.viewNormal);\");\n src.push(\" reflectVec = inverseTransformDirection(reflectVec, viewMatrix);\");\n src.push(\" float blinnExpFromRoughness = GGXRoughnessToBlinnExponent(material.specularRoughness);\");\n src.push(\" vec3 radiance = getLightProbeIndirectRadiance(reflectVec, blinnExpFromRoughness, 8);\");\n src.push(\" vec3 specularBRDFContrib = BRDF_Specular_GGX_Environment(geometry, material.specularColor, material.specularRoughness);\");\n src.push(\" reflectedLight.specular += radiance * specularBRDFContrib;\");\n }\n\n src.push(\"}\");\n }\n\n // MAIN LIGHTING COMPUTATION FUNCTION\n\n src.push(\"void computePBRLighting(const in IncidentLight incidentLight, const in Geometry geometry, const in Material material, inout ReflectedLight reflectedLight) {\");\n src.push(\" float dotNL = saturate(dot(geometry.viewNormal, incidentLight.direction));\");\n src.push(\" vec3 irradiance = dotNL * incidentLight.color * PI;\");\n src.push(\" reflectedLight.diffuse += irradiance * (RECIPROCAL_PI * material.diffuseColor);\");\n src.push(\" reflectedLight.specular += irradiance * BRDF_Specular_GGX(incidentLight, geometry, material.specularColor, material.specularRoughness);\");\n src.push(\"}\");\n\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n if (clippingCaps) {\n src.push(\" if (dist > (0.002 * vClipPosition.w)) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" outColor=vec4(1.0, 0.0, 0.0, 1.0);\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" return;\");\n src.push(\"}\");\n } else {\n src.push(\" if (dist > 0.0) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n src.push(\"}\");\n }\n\n src.push(\"IncidentLight light;\");\n src.push(\"Material material;\");\n src.push(\"Geometry geometry;\");\n src.push(\"ReflectedLight reflectedLight = ReflectedLight(vec3(0.0,0.0,0.0), vec3(0.0,0.0,0.0));\");\n\n src.push(\"vec3 rgb = (vec3(float(vColor.r) / 255.0, float(vColor.g) / 255.0, float(vColor.b) / 255.0));\");\n src.push(\"float opacity = float(vColor.a) / 255.0;\");\n\n src.push(\"vec3 baseColor = rgb;\");\n src.push(\"float specularF0 = 1.0;\");\n src.push(\"float metallic = float(vMetallicRoughness.r) / 255.0;\");\n src.push(\"float roughness = float(vMetallicRoughness.g) / 255.0;\");\n src.push(\"float dielectricSpecular = 0.16 * specularF0 * specularF0;\");\n\n src.push(\"vec4 colorTexel = sRGBToLinear(texture(uColorMap, vUV));\");\n src.push(\"baseColor *= colorTexel.rgb;\");\n // src.push(\"opacity = colorTexel.a;\");\n\n src.push(\"vec3 metalRoughTexel = texture(uMetallicRoughMap, vUV).rgb;\");\n src.push(\"metallic *= metalRoughTexel.b;\");\n src.push(\"roughness *= metalRoughTexel.g;\");\n\n src.push(\"vec3 viewNormal = perturbNormal2Arb( vViewPosition.xyz, normalize(vViewNormal), vUV );\");\n\n src.push(\"material.diffuseColor = baseColor * (1.0 - dielectricSpecular) * (1.0 - metallic);\");\n src.push(\"material.specularRoughness = clamp(roughness, 0.04, 1.0);\");\n src.push(\"material.specularColor = mix(vec3(dielectricSpecular), baseColor, metallic);\");\n\n src.push(\"geometry.position = vViewPosition.xyz;\");\n src.push(\"geometry.viewNormal = -normalize(viewNormal);\");\n src.push(\"geometry.viewEyeDir = normalize(vViewPosition.xyz);\");\n\n if (lightsState.lightMaps.length > 0) {\n src.push(\"geometry.worldNormal = normalize(vWorldNormal);\");\n }\n\n if (lightsState.lightMaps.length > 0 || lightsState.reflectionMaps.length > 0) {\n src.push(\"computePBRLightMapping(geometry, material, reflectedLight);\");\n }\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n\n const light = lightsState.lights[i];\n\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"light.direction = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"light.direction = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"light.direction = normalize(lightPos\" + i + \" - vViewPosition.xyz);\");\n } else {\n src.push(\"light.direction = normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"light.direction = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"light.direction = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n\n src.push(\"light.color = lightColor\" + i + \".rgb * lightColor\" + i + \".a;\"); // a is intensity\n\n src.push(\"computePBRLighting(light, geometry, material, reflectedLight);\");\n }\n\n src.push(\"vec3 emissiveColor = sRGBToLinear(texture(uEmissiveMap, vUV)).rgb;\"); // TODO: correct gamma function\n\n src.push(\"vec3 outgoingLight = (lightAmbient.rgb * lightAmbient.a * baseColor * opacity * rgb) + (reflectedLight.diffuse) + (reflectedLight.specular) + emissiveColor;\");\n src.push(\"vec4 fragColor;\");\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" fragColor = vec4(outgoingLight.rgb * ambient, opacity);\");\n } else {\n src.push(\" fragColor = vec4(outgoingLight.rgb, opacity);\");\n }\n\n if (gammaOutput) {\n src.push(\"fragColor = linearToGamma(fragColor, gammaFactor);\");\n }\n\n src.push(\"outColor = fragColor;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass TrianglesPickNormalsFlatRenderer extends TrianglesInstancingRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry normals vertex shader\");\n \n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src, 3);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n\n if (clipping) {\n src.push(\"vFlags = flags;\");\n }\n\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry normals fragment shader\");\n \n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"in vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out highp ivec4 outNormal;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n src.push(\" vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n src.push(\" vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(` outNormal = ivec4(worldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass TrianglesColorTextureRenderer extends TrianglesInstancingRenderer {\n _getHash() {\n const scene = this._scene;\n return [scene.gammaOutput, scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry drawing vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in vec2 uv;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform mat3 uvDecodeMatrix;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out vec4 vColor;\");\n src.push(\"out vec2 vUV;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE | COLOR_TRANSPARENT\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, float(color.a) / 255.0);\");\n src.push(\"vUV = (uvDecodeMatrix * vec3(uv, 1.0)).xy;\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const gammaOutput = scene.gammaOutput; // If set, then it expects that all textures and colors need to be outputted in premultiplied gamma. Default is false.\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n let i;\n let len;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Instancing geometry drawing fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform sampler2D uColorMap;\");\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n src.push(\"uniform float gammaFactor;\");\n src.push(\"vec4 linearToLinear( in vec4 value ) {\");\n src.push(\" return value;\");\n src.push(\"}\");\n src.push(\"vec4 sRGBToLinear( in vec4 value ) {\");\n src.push(\" return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\");\n src.push(\"}\");\n src.push(\"vec4 gammaToLinear( in vec4 value) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\");\n src.push(\"}\");\n if (gammaOutput) {\n src.push(\"vec4 linearToGamma( in vec4 value, in float gammaFactor ) {\");\n src.push(\" return vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\");\n src.push(\"}\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n src.push(\"uniform float sliceThickness;\");\n src.push(\"uniform vec4 sliceColor;\");\n }\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec4 lightAmbient;\");\n for (i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"in vec4 vColor;\");\n src.push(\"in vec2 vUV;\");\n src.push(\"out vec4 outColor;\");\n\n src.push(\"void main(void) {\");\n src.push(\" vec4 newColor;\");\n src.push(\" newColor = vColor;\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > sliceThickness) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\" if (dist > 0.0) { \");\n src.push(\" newColor = sliceColor;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n\n src.push(\"float lambertian = 1.0;\");\n\n src.push(\"vec3 xTangent = dFdx( vViewPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vViewPosition.xyz );\");\n src.push(\"vec3 viewNormal = normalize( cross( xTangent, yTangent ) );\");\n\n for (i = 0, len = lightsState.lights.length; i < len; i++) {\n const light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n\n src.push(\"vec4 color = vec4((lightAmbient.rgb * lightAmbient.a * newColor.rgb) + (reflectedColor * newColor.rgb), newColor.a);\");\n if (gammaOutput) {\n src.push(\"vec4 colorTexel = color * sRGBToLinear(texture(uColorMap, vUV));\");\n } else {\n src.push(\"vec4 colorTexel = color * texture(uColorMap, vUV);\");\n }\n src.push(\"float opacity = color.a;\");\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" outColor = vec4(newColor.rgb * colorTexel.rgb * ambient, opacity);\");\n } else {\n src.push(\" outColor = vec4(newColor.rgb * colorTexel.rgb, opacity);\");\n }\n\n if (gammaOutput) {\n src.push(\"outColor = linearToGamma(outColor, gammaFactor);\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n src.push(\"}\");\n return src;\n }\n}\n\nconst tempVec3a$y = math.vec3();\nconst tempVec3b$u = math.vec3();\nconst tempVec3c$p = math.vec3();\nconst tempVec3d$9 = math.vec3();\nconst tempMat4a$o = math.mat4();\n\n/**\n * @private\n */\nclass TrianglesSnapInitRenderer extends VBORenderer {\n\n constructor(scene) {\n super(scene, false, { instancing: true });\n }\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = instancingLayer.model;\n const scene = model.scene;\n const gl = scene.canvas.gl;\n const camera = scene.camera;\n const state = instancingLayer._state;\n const origin = instancingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = instancingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(instancingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(instancingLayer));\n } else {\n this._vaoCache.set(instancingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$y;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$u;\n if (origin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$p);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$o);\n rtcCameraEye = tempVec3d$9;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(instancingLayer);\n\n this._aModelMatrixCol0.bindArrayBuffer(state.modelMatrixCol0Buf);\n this._aModelMatrixCol1.bindArrayBuffer(state.modelMatrixCol1Buf);\n this._aModelMatrixCol2.bindArrayBuffer(state.modelMatrixCol2Buf);\n\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 1);\n\n\n if (this._aFlags) {\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n gl.vertexAttribDivisor(this._aFlags.location, 1);\n }\n\n state.indicesBuf.bind();\n gl.drawElementsInstanced(gl.TRIANGLES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0, state.numInstances);\n state.indicesBuf.unbind();\n\n // Cleanup\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 0);\n if (this._aFlags) {\n gl.vertexAttribDivisor(this._aFlags.location, 0);\n }\n if (this._aOffset) {\n gl.vertexAttribDivisor(this._aOffset.location, 0);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\");\n this._uLayerNumber = program.getLocation(\"layerNumber\");\n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthBufInitRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec4 pickColor;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n src.push(\"uniform vec2 snapVectorA;\");\n src.push(\"uniform vec2 snapInvVectorAB;\");\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n src.push(\"flat out vec4 vPickColor;\");\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vPickColor = pickColor;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points instancing pick depth fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\");\n src.push(\"uniform vec3 coordinateScaler;\");\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in vec4 vPickColor;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"layout(location = 0) out highp ivec4 outCoords;\");\n src.push(\"layout(location = 1) out highp ivec4 outNormal;\");\n src.push(\"layout(location = 2) out lowp uvec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n {\n src.push(\" float dx = dFdx(vFragDepth);\");\n src.push(\" float dy = dFdy(vFragDepth);\");\n src.push(\" float diff = sqrt(dx*dx+dy*dy);\");\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth + diff ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, -layerNumber);\");\n\n src.push(\"vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n src.push(\"vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(`outNormal = ivec4(worldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"outPickColor = uvec4(vPickColor);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$x = math.vec3();\nconst tempVec3b$t = math.vec3();\nconst tempVec3c$o = math.vec3();\nconst tempVec3d$8 = math.vec3();\nconst tempMat4a$n = math.mat4();\n\n/**\n * @private\n */\nclass TrianglesSnapRenderer extends VBORenderer {\n\n constructor(scene) {\n super(scene, false, { instancing: true });\n }\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate(instancingLayer);\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = instancingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = instancingLayer._state;\n const origin = instancingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = instancingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(instancingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(instancingLayer));\n } else {\n this._vaoCache.set(instancingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$x;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$t;\n if (origin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$o);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$n);\n rtcCameraEye = tempVec3d$8;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(instancingLayer);\n\n\n this._aModelMatrixCol0.bindArrayBuffer(state.modelMatrixCol0Buf);\n this._aModelMatrixCol1.bindArrayBuffer(state.modelMatrixCol1Buf);\n this._aModelMatrixCol2.bindArrayBuffer(state.modelMatrixCol2Buf);\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 1);\n\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n gl.vertexAttribDivisor(this._aFlags.location, 1);\n\n if (frameCtx.snapMode === \"edge\") {\n state.edgeIndicesBuf.bind();\n gl.drawElementsInstanced(gl.LINES, state.edgeIndicesBuf.numItems, state.edgeIndicesBuf.itemType, 0, state.numInstances);\n state.edgeIndicesBuf.unbind(); // needed?\n } else {\n gl.drawArraysInstanced(gl.POINTS, 0, state.positionsBuf.numItems, state.numInstances);\n }\n // Cleanup\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 0);\n gl.vertexAttribDivisor(this._aFlags.location, 0);\n if (this._aOffset) {\n gl.vertexAttribDivisor(this._aOffset.location, 0);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\"); \n this.uVectorA = program.getLocation(\"snapVectorA\"); \n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\"); \n this._uLayerNumber = program.getLocation(\"layerNumber\"); \n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\"); \n }\n\n _bindProgram() {\n this._program.bind();\n\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\"); \n src.push(\"uniform vec2 snapVectorA;\"); \n src.push(\"uniform vec2 snapInvVectorAB;\"); \n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"gl_PointSize = 1.0;\"); // Windows needs this?\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\"); \n src.push(\"uniform vec3 coordinateScaler;\"); \n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"out highp ivec4 outCoords;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, layerNumber);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\nclass Renderers {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n if (this._colorRendererWithSAO && (!this._colorRendererWithSAO.getValid())) {\n this._colorRendererWithSAO.destroy();\n this._colorRendererWithSAO = null;\n }\n if (this._flatColorRenderer && (!this._flatColorRenderer.getValid())) {\n this._flatColorRenderer.destroy();\n this._flatColorRenderer = null;\n }\n if (this._flatColorRendererWithSAO && (!this._flatColorRendererWithSAO.getValid())) {\n this._flatColorRendererWithSAO.destroy();\n this._flatColorRendererWithSAO = null;\n }\n if (this._pbrRenderer && (!this._pbrRenderer.getValid())) {\n this._pbrRenderer.destroy();\n this._pbrRenderer = null;\n }\n if (this._pbrRendererWithSAO && (!this._pbrRendererWithSAO.getValid())) {\n this._pbrRendererWithSAO.destroy();\n this._pbrRendererWithSAO = null;\n }\n if (this._colorTextureRenderer && (!this._colorTextureRenderer.getValid())) {\n this._colorTextureRenderer.destroy();\n this._colorTextureRenderer = null;\n }\n if (this._colorTextureRendererWithSAO && (!this._colorTextureRendererWithSAO.getValid())) {\n this._colorTextureRendererWithSAO.destroy();\n this._colorTextureRendererWithSAO = null;\n }\n if (this._depthRenderer && (!this._depthRenderer.getValid())) {\n this._depthRenderer.destroy();\n this._depthRenderer = null;\n }\n if (this._normalsRenderer && (!this._normalsRenderer.getValid())) {\n this._normalsRenderer.destroy();\n this._normalsRenderer = null;\n }\n if (this._silhouetteRenderer && (!this._silhouetteRenderer.getValid())) {\n this._silhouetteRenderer.destroy();\n this._silhouetteRenderer = null;\n }\n if (this._edgesRenderer && (!this._edgesRenderer.getValid())) {\n this._edgesRenderer.destroy();\n this._edgesRenderer = null;\n }\n if (this._edgesColorRenderer && (!this._edgesColorRenderer.getValid())) {\n this._edgesColorRenderer.destroy();\n this._edgesColorRenderer = null;\n }\n if (this._pickMeshRenderer && (!this._pickMeshRenderer.getValid())) {\n this._pickMeshRenderer.destroy();\n this._pickMeshRenderer = null;\n }\n if (this._pickDepthRenderer && (!this._pickDepthRenderer.getValid())) {\n this._pickDepthRenderer.destroy();\n this._pickDepthRenderer = null;\n }\n if (this._pickNormalsRenderer && this._pickNormalsRenderer.getValid() === false) {\n this._pickNormalsRenderer.destroy();\n this._pickNormalsRenderer = null;\n }\n if (this._pickNormalsFlatRenderer && (!this._pickNormalsFlatRenderer.getValid())) {\n this._pickNormalsFlatRenderer.destroy();\n this._pickNormalsFlatRenderer = null;\n }\n if (this._occlusionRenderer && this._occlusionRenderer.getValid() === false) {\n this._occlusionRenderer.destroy();\n this._occlusionRenderer = null;\n }\n if (this._shadowRenderer && (!this._shadowRenderer.getValid())) {\n this._shadowRenderer.destroy();\n this._shadowRenderer = null;\n }\n if (this._snapInitRenderer && (!this._snapInitRenderer.getValid())) {\n this._snapInitRenderer.destroy();\n this._snapInitRenderer = null;\n }\n if (this._snapRenderer && (!this._snapRenderer.getValid())) {\n this._snapRenderer.destroy();\n this._snapRenderer = null;\n }\n }\n\n eagerCreateRenders() {\n\n // Pre-initialize certain renderers that would otherwise be lazy-initialised\n // on user interaction, such as picking or emphasis, so that there is no delay\n // when user first begins interacting with the viewer.\n\n if (!this._silhouetteRenderer) { // Used for highlighting and selection\n this._silhouetteRenderer = new TrianglesSilhouetteRenderer(this._scene);\n }\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new TrianglesPickMeshRenderer(this._scene);\n }\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new TrianglesPickDepthRenderer(this._scene);\n }\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new TrianglesSnapInitRenderer(this._scene, false);\n }\n if (!this._snapRenderer) {\n this._snapRenderer = new TrianglesSnapRenderer(this._scene);\n }\n }\n\n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new TrianglesColorRenderer(this._scene, false);\n }\n return this._colorRenderer;\n }\n\n get colorRendererWithSAO() {\n if (!this._colorRendererWithSAO) {\n this._colorRendererWithSAO = new TrianglesColorRenderer(this._scene, true);\n }\n return this._colorRendererWithSAO;\n }\n\n get flatColorRenderer() {\n if (!this._flatColorRenderer) {\n this._flatColorRenderer = new TrianglesFlatColorRenderer(this._scene, false);\n }\n return this._flatColorRenderer;\n }\n\n get flatColorRendererWithSAO() {\n if (!this._flatColorRendererWithSAO) {\n this._flatColorRendererWithSAO = new TrianglesFlatColorRenderer(this._scene, true);\n }\n return this._flatColorRendererWithSAO;\n }\n\n get pbrRenderer() {\n if (!this._pbrRenderer) {\n this._pbrRenderer = new TrianglesPBRRenderer(this._scene, false);\n }\n return this._pbrRenderer;\n }\n\n get pbrRendererWithSAO() {\n if (!this._pbrRendererWithSAO) {\n this._pbrRendererWithSAO = new TrianglesPBRRenderer(this._scene, true);\n }\n return this._pbrRendererWithSAO;\n }\n\n get colorTextureRenderer() {\n if (!this._colorTextureRenderer) {\n this._colorTextureRenderer = new TrianglesColorTextureRenderer(this._scene, false);\n }\n return this._colorTextureRenderer;\n }\n\n get colorTextureRendererWithSAO() {\n if (!this._colorTextureRendererWithSAO) {\n this._colorTextureRendererWithSAO = new TrianglesColorTextureRenderer(this._scene, true);\n }\n return this._colorTextureRendererWithSAO;\n }\n\n get silhouetteRenderer() {\n if (!this._silhouetteRenderer) {\n this._silhouetteRenderer = new TrianglesSilhouetteRenderer(this._scene);\n }\n return this._silhouetteRenderer;\n }\n\n get depthRenderer() {\n if (!this._depthRenderer) {\n this._depthRenderer = new TrianglesDepthRenderer(this._scene);\n }\n return this._depthRenderer;\n }\n\n get normalsRenderer() {\n if (!this._normalsRenderer) {\n this._normalsRenderer = new TrianglesNormalsRenderer(this._scene);\n }\n return this._normalsRenderer;\n }\n\n get edgesRenderer() {\n if (!this._edgesRenderer) {\n this._edgesRenderer = new EdgesEmphasisRenderer(this._scene);\n }\n return this._edgesRenderer;\n }\n\n get edgesColorRenderer() {\n if (!this._edgesColorRenderer) {\n this._edgesColorRenderer = new EdgesColorRenderer(this._scene);\n }\n return this._edgesColorRenderer;\n }\n\n get pickMeshRenderer() {\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new TrianglesPickMeshRenderer(this._scene);\n }\n return this._pickMeshRenderer;\n }\n\n get pickNormalsRenderer() {\n if (!this._pickNormalsRenderer) {\n this._pickNormalsRenderer = new TrianglesPickNormalsRenderer(this._scene);\n }\n return this._pickNormalsRenderer;\n }\n\n get pickNormalsFlatRenderer() {\n if (!this._pickNormalsFlatRenderer) {\n this._pickNormalsFlatRenderer = new TrianglesPickNormalsFlatRenderer(this._scene);\n }\n return this._pickNormalsFlatRenderer;\n }\n\n get pickDepthRenderer() {\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new TrianglesPickDepthRenderer(this._scene);\n }\n return this._pickDepthRenderer;\n }\n\n get occlusionRenderer() {\n if (!this._occlusionRenderer) {\n this._occlusionRenderer = new TrianglesOcclusionRenderer(this._scene);\n }\n return this._occlusionRenderer;\n }\n\n get shadowRenderer() {\n if (!this._shadowRenderer) {\n this._shadowRenderer = new TrianglesShadowRenderer(this._scene);\n }\n return this._shadowRenderer;\n }\n\n get snapInitRenderer() {\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new TrianglesSnapInitRenderer(this._scene, false);\n }\n return this._snapInitRenderer;\n }\n\n get snapRenderer() {\n if (!this._snapRenderer) {\n this._snapRenderer = new TrianglesSnapRenderer(this._scene);\n }\n return this._snapRenderer;\n }\n\n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n if (this._colorRendererWithSAO) {\n this._colorRendererWithSAO.destroy();\n }\n if (this._flatColorRenderer) {\n this._flatColorRenderer.destroy();\n }\n if (this._flatColorRendererWithSAO) {\n this._flatColorRendererWithSAO.destroy();\n }\n if (this._pbrRenderer) {\n this._pbrRenderer.destroy();\n }\n if (this._pbrRendererWithSAO) {\n this._pbrRendererWithSAO.destroy();\n }\n if (this._colorTextureRenderer) {\n this._colorTextureRenderer.destroy();\n }\n if (this._colorTextureRendererWithSAO) {\n this._colorTextureRendererWithSAO.destroy();\n }\n if (this._depthRenderer) {\n this._depthRenderer.destroy();\n }\n if (this._normalsRenderer) {\n this._normalsRenderer.destroy();\n }\n if (this._silhouetteRenderer) {\n this._silhouetteRenderer.destroy();\n }\n if (this._edgesRenderer) {\n this._edgesRenderer.destroy();\n }\n if (this._edgesColorRenderer) {\n this._edgesColorRenderer.destroy();\n }\n if (this._pickMeshRenderer) {\n this._pickMeshRenderer.destroy();\n }\n if (this._pickDepthRenderer) {\n this._pickDepthRenderer.destroy();\n }\n if (this._pickNormalsRenderer) {\n this._pickNormalsRenderer.destroy();\n }\n if (this._pickNormalsFlatRenderer) {\n this._pickNormalsFlatRenderer.destroy();\n }\n if (this._occlusionRenderer) {\n this._occlusionRenderer.destroy();\n }\n if (this._shadowRenderer) {\n this._shadowRenderer.destroy();\n }\n if (this._snapInitRenderer) {\n this._snapInitRenderer.destroy();\n }\n if (this._snapRenderer) {\n this._snapRenderer.destroy();\n }\n }\n}\n\nconst cachedRenderers$5 = {};\n\n/**\n * @private\n */\nfunction getRenderers$6(scene) {\n const sceneId = scene.id;\n let instancingRenderers = cachedRenderers$5[sceneId];\n if (!instancingRenderers) {\n instancingRenderers = new Renderers(scene);\n cachedRenderers$5[sceneId] = instancingRenderers;\n instancingRenderers._compile();\n instancingRenderers.eagerCreateRenders();\n scene.on(\"compile\", () => {\n instancingRenderers._compile();\n instancingRenderers.eagerCreateRenders();\n });\n scene.on(\"destroyed\", () => {\n delete cachedRenderers$5[sceneId];\n instancingRenderers._destroy();\n });\n }\n return instancingRenderers;\n}\n\nconst tempUint8Vec4$2 = new Uint8Array(4);\nconst tempFloat32$2 = new Float32Array(1);\nconst tempVec4a$6 = math.vec4([0, 0, 0, 1]);\nconst tempVec3fa$2 = new Float32Array(3);\n\nconst tempVec3a$w = math.vec3();\nconst tempVec3b$s = math.vec3();\nconst tempVec3c$n = math.vec3();\nconst tempVec3d$7 = math.vec3();\nconst tempVec3e = math.vec3();\nconst tempVec3f = math.vec3();\nconst tempVec3g = math.vec3();\n\nconst tempFloat32Vec4$2 = new Float32Array(4);\n\n/**\n * @private\n */\nclass VBOInstancingTrianglesLayer {\n\n /**\n * @param cfg\n * @param cfg.layerIndex\n * @param cfg.model\n * @param cfg.geometry\n * @param cfg.textureSet\n * @param cfg.origin\n */\n constructor(cfg) {\n\n console.info(\"Creating VBOInstancingTrianglesLayer\");\n\n /**\n * Owner model\n * @type {VBOSceneModel}\n */\n this.model = cfg.model;\n\n /**\n * State sorting key.\n * @type {string}\n */\n this.sortId = \"TrianglesInstancingLayer\" + (cfg.solid ? \"-solid\" : \"-surface\") + (cfg.normals ? \"-normals\" : \"-autoNormals\");\n\n /**\n * Index of this InstancingLayer in VBOSceneModel#_layerList\n * @type {Number}\n */\n this.layerIndex = cfg.layerIndex;\n\n this._renderers = getRenderers$6(cfg.model.scene);\n\n this._aabb = math.collapseAABB3();\n\n this._state = new RenderState({\n numInstances: 0,\n obb: math.OBB3(),\n origin: math.vec3(),\n geometry: cfg.geometry,\n textureSet: cfg.textureSet,\n pbrSupported: false, // Set in #finalize if we have enough to support quality rendering\n positionsDecodeMatrix: cfg.geometry.positionsDecodeMatrix, // So we can null the geometry for GC\n colorsBuf: null,\n metallicRoughnessBuf: null,\n flagsBuf: null,\n offsetsBuf: null,\n modelMatrixBuf: null,\n modelMatrixCol0Buf: null,\n modelMatrixCol1Buf: null,\n modelMatrixCol2Buf: null,\n modelNormalMatrixCol0Buf: null,\n modelNormalMatrixCol1Buf: null,\n modelNormalMatrixCol2Buf: null,\n pickColorsBuf: null\n });\n\n // These counts are used to avoid unnecessary render passes\n this._numPortions = 0;\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numEdgesLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n /** @private */\n this.numIndices = cfg.geometry.numIndices;\n\n // Vertex arrays\n this._colors = [];\n this._metallicRoughness = [];\n this._pickColors = [];\n this._offsets = [];\n\n // Modeling matrix per instance, array for each column\n\n this._modelMatrix = [];\n\n this._modelMatrixCol0 = [];\n this._modelMatrixCol1 = [];\n this._modelMatrixCol2 = [];\n\n // Modeling normal matrix per instance, array for each column\n this._modelNormalMatrixCol0 = [];\n this._modelNormalMatrixCol1 = [];\n this._modelNormalMatrixCol2 = [];\n\n this._portions = [];\n this._meshes = [];\n\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n\n if (cfg.origin) {\n this._state.origin.set(cfg.origin);\n }\n\n this._finalized = false;\n\n /**\n * When true, this layer contains solid triangle meshes, otherwise this layer contains surface triangle meshes\n * @type {boolean}\n */\n this.solid = !!cfg.solid;\n\n /**\n * The number of indices in this layer.\n * @type {number|*}\n */\n this.numIndices = cfg.geometry.numIndices;\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Creates a new portion within this InstancingLayer, returns the new portion ID.\n *\n * The portion will instance this InstancingLayer's geometry.\n *\n * Gives the portion the specified color and matrix.\n *\n * @param mesh The SceneModelMesh that owns the portion\n * @param cfg Portion params\n * @param cfg.color Color [0..255,0..255,0..255]\n * @param cfg.metallic Metalness factor [0..255]\n * @param cfg.roughness Roughness factor [0..255]\n * @param cfg.opacity Opacity [0..255].\n * @param cfg.meshMatrix Flat float 4x4 matrix.\n * @param [cfg.worldMatrix] Flat float 4x4 matrix.\n * @param cfg.pickColor Quantized pick color\n * @returns {number} Portion ID.\n */\n createPortion(mesh, cfg) {\n\n const color = cfg.color;\n const metallic = cfg.metallic;\n const roughness = cfg.roughness;\n const opacity = cfg.opacity !== null && cfg.opacity !== undefined ? cfg.opacity : 255;\n const meshMatrix = cfg.meshMatrix;\n const pickColor = cfg.pickColor;\n\n if (this._finalized) {\n throw \"Already finalized\";\n }\n\n const r = color[0]; // Color is pre-quantized by SceneModel\n const g = color[1];\n const b = color[2];\n\n this._colors.push(r);\n this._colors.push(g);\n this._colors.push(b);\n this._colors.push(opacity);\n\n this._metallicRoughness.push((metallic !== null && metallic !== undefined) ? metallic : 0);\n this._metallicRoughness.push((roughness !== null && roughness !== undefined) ? roughness : 255);\n\n if (this.model.scene.entityOffsetsEnabled) {\n this._offsets.push(0);\n this._offsets.push(0);\n this._offsets.push(0);\n }\n\n this._modelMatrixCol0.push(meshMatrix[0]);\n this._modelMatrixCol0.push(meshMatrix[4]);\n this._modelMatrixCol0.push(meshMatrix[8]);\n this._modelMatrixCol0.push(meshMatrix[12]);\n\n this._modelMatrixCol1.push(meshMatrix[1]);\n this._modelMatrixCol1.push(meshMatrix[5]);\n this._modelMatrixCol1.push(meshMatrix[9]);\n this._modelMatrixCol1.push(meshMatrix[13]);\n\n this._modelMatrixCol2.push(meshMatrix[2]);\n this._modelMatrixCol2.push(meshMatrix[6]);\n this._modelMatrixCol2.push(meshMatrix[10]);\n this._modelMatrixCol2.push(meshMatrix[14]);\n\n if (this._state.geometry.normals) {\n\n // Note: order of inverse and transpose doesn't matter\n\n let transposedMat = math.transposeMat4(meshMatrix, math.mat4()); // TODO: Use cached matrix\n let normalMatrix = math.inverseMat4(transposedMat);\n\n this._modelNormalMatrixCol0.push(normalMatrix[0]);\n this._modelNormalMatrixCol0.push(normalMatrix[4]);\n this._modelNormalMatrixCol0.push(normalMatrix[8]);\n this._modelNormalMatrixCol0.push(normalMatrix[12]);\n\n this._modelNormalMatrixCol1.push(normalMatrix[1]);\n this._modelNormalMatrixCol1.push(normalMatrix[5]);\n this._modelNormalMatrixCol1.push(normalMatrix[9]);\n this._modelNormalMatrixCol1.push(normalMatrix[13]);\n\n this._modelNormalMatrixCol2.push(normalMatrix[2]);\n this._modelNormalMatrixCol2.push(normalMatrix[6]);\n this._modelNormalMatrixCol2.push(normalMatrix[10]);\n this._modelNormalMatrixCol2.push(normalMatrix[14]);\n }\n\n // Per-vertex pick colors\n\n this._pickColors.push(pickColor[0]);\n this._pickColors.push(pickColor[1]);\n this._pickColors.push(pickColor[2]);\n this._pickColors.push(pickColor[3]);\n\n this._state.numInstances++;\n\n const portionId = this._portions.length;\n\n const portion = {};\n\n if (this.model.scene.pickSurfacePrecisionEnabled) {\n portion.matrix = meshMatrix.slice();\n portion.inverseMatrix = null; // Lazy-computed in precisionRayPickSurface\n portion.normalMatrix = null; // Lazy-computed in precisionRayPickSurface\n }\n\n this._portions.push(portion);\n\n this._numPortions++;\n this.model.numPortions++;\n this._meshes.push(mesh);\n return portionId;\n }\n\n finalize() {\n\n if (this._finalized) {\n return;\n }\n\n const state = this._state;\n const geometry = state.geometry;\n const textureSet = state.textureSet;\n const gl = this.model.scene.canvas.gl;\n const colorsLength = this._colors.length;\n const flagsLength = colorsLength / 4;\n\n if (colorsLength > 0) {\n let notNormalized = false;\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Uint8Array(this._colors), this._colors.length, 4, gl.DYNAMIC_DRAW, notNormalized);\n this._colors = []; // Release memory\n }\n\n if (this._metallicRoughness.length > 0) {\n const metallicRoughness = new Uint8Array(this._metallicRoughness);\n let normalized = false;\n state.metallicRoughnessBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, metallicRoughness, this._metallicRoughness.length, 2, gl.STATIC_DRAW, normalized);\n }\n\n if (flagsLength > 0) {\n // Because we only build flags arrays here,\n // get their length from the colors array\n let notNormalized = false;\n state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(flagsLength), flagsLength, 1, gl.DYNAMIC_DRAW, notNormalized);\n }\n\n if (this.model.scene.entityOffsetsEnabled) {\n if (this._offsets.length > 0) {\n const notNormalized = false;\n state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._offsets), this._offsets.length, 3, gl.DYNAMIC_DRAW, notNormalized);\n this._offsets = []; // Release memory\n }\n }\n\n if (geometry.positionsCompressed && geometry.positionsCompressed.length > 0) {\n const normalized = false;\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, geometry.positionsCompressed, geometry.positionsCompressed.length, 3, gl.STATIC_DRAW, normalized);\n state.positionsDecodeMatrix = math.mat4(geometry.positionsDecodeMatrix);\n }\n // if (geometry.normalsCompressed && geometry.normalsCompressed.length > 0) {\n // const normalized = true; // For oct-encoded UInt8\n // state.normalsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, geometry.normalsCompressed, geometry.normalsCompressed.length, 3, gl.STATIC_DRAW, normalized);\n // }\n if (geometry.colorsCompressed && geometry.colorsCompressed.length > 0) {\n const colorsCompressed = new Uint8Array(geometry.colorsCompressed);\n const notNormalized = false;\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colorsCompressed, colorsCompressed.length, 4, gl.STATIC_DRAW, notNormalized);\n }\n if (geometry.uvCompressed && geometry.uvCompressed.length > 0) {\n const uvCompressed = geometry.uvCompressed;\n state.uvDecodeMatrix = geometry.uvDecodeMatrix;\n state.uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uvCompressed, uvCompressed.length, 2, gl.STATIC_DRAW, false);\n }\n if (geometry.indices && geometry.indices.length > 0) {\n state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(geometry.indices), geometry.indices.length, 1, gl.STATIC_DRAW);\n state.numIndices = geometry.indices.length;\n }\n if (geometry.primitive === \"triangles\" || geometry.primitive === \"solid\" || geometry.primitive === \"surface\") {\n state.edgeIndicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(geometry.edgeIndices), geometry.edgeIndices.length, 1, gl.STATIC_DRAW);\n }\n\n if (this._modelMatrixCol0.length > 0) {\n\n const normalized = false;\n\n state.modelMatrixCol0Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol0), this._modelMatrixCol0.length, 4, gl.STATIC_DRAW, normalized);\n state.modelMatrixCol1Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol1), this._modelMatrixCol1.length, 4, gl.STATIC_DRAW, normalized);\n state.modelMatrixCol2Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol2), this._modelMatrixCol2.length, 4, gl.STATIC_DRAW, normalized);\n this._modelMatrixCol0 = [];\n this._modelMatrixCol1 = [];\n this._modelMatrixCol2 = [];\n\n if (state.normalsBuf) {\n state.modelNormalMatrixCol0Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelNormalMatrixCol0), this._modelNormalMatrixCol0.length, 4, gl.STATIC_DRAW, normalized);\n state.modelNormalMatrixCol1Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelNormalMatrixCol1), this._modelNormalMatrixCol1.length, 4, gl.STATIC_DRAW, normalized);\n state.modelNormalMatrixCol2Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelNormalMatrixCol2), this._modelNormalMatrixCol2.length, 4, gl.STATIC_DRAW, normalized);\n this._modelNormalMatrixCol0 = [];\n this._modelNormalMatrixCol1 = [];\n this._modelNormalMatrixCol2 = [];\n }\n }\n\n if (this._pickColors.length > 0) {\n const normalized = false;\n state.pickColorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Uint8Array(this._pickColors), this._pickColors.length, 4, gl.STATIC_DRAW, normalized);\n this._pickColors = []; // Release memory\n }\n\n state.pbrSupported\n = !!state.metallicRoughnessBuf\n && !!state.uvBuf\n && !!state.normalsBuf\n && !!textureSet\n && !!textureSet.colorTexture\n && !!textureSet.metallicRoughnessTexture;\n\n state.colorTextureSupported\n = !!state.uvBuf\n && !!textureSet\n && !!textureSet.colorTexture;\n\n this._state.geometry = null;\n\n this._finalized = true;\n }\n\n // The following setters are called by VBOSceneModelMesh, in turn called by VBOSceneModelNode, only after the layer is finalized.\n // It's important that these are called after finalize() in order to maintain integrity of counts like _numVisibleLayerPortions etc.\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setVisible(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setHighlighted(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setXRayed(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setSelected(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setEdges(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n } else {\n this._numEdgesLayerPortions--;\n this.model.numEdgesLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags(portionId, flags);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setCulled(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions--;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setColor(portionId, color) { // RGBA color is normalized as ints\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n tempUint8Vec4$2[0] = color[0];\n tempUint8Vec4$2[1] = color[1];\n tempUint8Vec4$2[2] = color[2];\n tempUint8Vec4$2[3] = color[3];\n if (this._state.colorsBuf) {\n this._state.colorsBuf.setData(tempUint8Vec4$2, portionId * 4);\n }\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n // setMatrix(portionId, matrix) {\n //\n // if (!this._finalized) {\n // throw \"Not finalized\";\n // }\n //\n // var offset = portionId * 4;\n //\n // tempFloat32Vec4[0] = matrix[0];\n // tempFloat32Vec4[1] = matrix[4];\n // tempFloat32Vec4[2] = matrix[8];\n // tempFloat32Vec4[3] = matrix[12];\n //\n // this._state.modelMatrixCol0Buf.setData(tempFloat32Vec4, offset);\n //\n // tempFloat32Vec4[0] = matrix[1];\n // tempFloat32Vec4[1] = matrix[5];\n // tempFloat32Vec4[2] = matrix[9];\n // tempFloat32Vec4[3] = matrix[13];\n //\n // this._state.modelMatrixCol1Buf.setData(tempFloat32Vec4, offset);\n //\n // tempFloat32Vec4[0] = matrix[2];\n // tempFloat32Vec4[1] = matrix[6];\n // tempFloat32Vec4[2] = matrix[10];\n // tempFloat32Vec4[3] = matrix[14];\n //\n // this._state.modelMatrixCol2Buf.setData(tempFloat32Vec4, offset);\n // }\n\n /**\n * flags are 4bits values encoded on a 32bit base. color flag on the first 4 bits, silhouette flag on the next 4 bits and so on for edge, pick and clippable.\n */\n _setFlags(portionId, flags, meshTransparent) {\n\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n const edges = !!(flags & ENTITY_FLAGS.EDGES);\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n let colorFlag;\n if (!visible || culled || xrayed\n || (highlighted && !this.model.scene.highlightMaterial.glowThrough)\n || (selected && !this.model.scene.selectedMaterial.glowThrough)) {\n colorFlag = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (meshTransparent) {\n colorFlag = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n colorFlag = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n let silhouetteFlag;\n if (!visible || culled) {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let edgeFlag = 0;\n if (!visible || culled) {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n edgeFlag = RENDER_PASSES.EDGES_SELECTED;\n } else if (highlighted) {\n edgeFlag = RENDER_PASSES.EDGES_HIGHLIGHTED;\n } else if (xrayed) {\n edgeFlag = RENDER_PASSES.EDGES_XRAYED;\n } else if (edges) {\n if (meshTransparent) {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;\n } else {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_OPAQUE;\n }\n } else {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n const pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n\n const clippableFlag = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 1 : 0;\n\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n vertFlag |= edgeFlag << 8;\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n tempFloat32$2[0] = vertFlag;\n\n if (this._state.flagsBuf) {\n this._state.flagsBuf.setData(tempFloat32$2, portionId);\n }\n }\n\n setOffset(portionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (!this.model.scene.entityOffsetsEnabled) {\n this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n return;\n }\n tempVec3fa$2[0] = offset[0];\n tempVec3fa$2[1] = offset[1];\n tempVec3fa$2[2] = offset[2];\n if (this._state.offsetsBuf) {\n this._state.offsetsBuf.setData(tempVec3fa$2, portionId * 3);\n }\n }\n\n getEachVertex(portionId, callback) {\n if (!this.model.scene.pickSurfacePrecisionEnabled) {\n return false;\n }\n const state = this._state;\n const geometry = state.geometry;\n const portion = this._portions[portionId];\n if (!portion) {\n this.model.error(\"portion not found: \" + portionId);\n return;\n }\n const positions = geometry.quantizedPositions;\n const origin = state.origin;\n const offset = portion.offset;\n const offsetX = origin[0] + offset[0];\n const offsetY = origin[1] + offset[1];\n const offsetZ = origin[2] + offset[2];\n const worldPos = tempVec4a$6;\n const portionMatrix = portion.matrix;\n const sceneModelPatrix = this.model.sceneModelMatrix;\n const positionsDecodeMatrix = state.positionsDecodeMatrix;\n for (let i = 0, len = positions.length; i < len; i += 3) {\n worldPos[0] = positions[i];\n worldPos[1] = positions[i + 1];\n worldPos[2] = positions[i + 2];\n math.decompressPosition(worldPos, positionsDecodeMatrix);\n math.transformPoint3(portionMatrix, worldPos);\n math.transformPoint3(sceneModelPatrix, worldPos);\n worldPos[0] += offsetX;\n worldPos[1] += offsetY;\n worldPos[2] += offsetZ;\n callback(worldPos);\n }\n }\n\n setMatrix(portionId, matrix) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n ////////////////////////////////////////\n // TODO: Update portion matrix\n ////////////////////////////////////////\n\n var offset = portionId * 4;\n\n tempFloat32Vec4$2[0] = matrix[0];\n tempFloat32Vec4$2[1] = matrix[4];\n tempFloat32Vec4$2[2] = matrix[8];\n tempFloat32Vec4$2[3] = matrix[12];\n\n this._state.modelMatrixCol0Buf.setData(tempFloat32Vec4$2, offset);\n\n tempFloat32Vec4$2[0] = matrix[1];\n tempFloat32Vec4$2[1] = matrix[5];\n tempFloat32Vec4$2[2] = matrix[9];\n tempFloat32Vec4$2[3] = matrix[13];\n\n this._state.modelMatrixCol1Buf.setData(tempFloat32Vec4$2, offset);\n\n tempFloat32Vec4$2[0] = matrix[2];\n tempFloat32Vec4$2[1] = matrix[6];\n tempFloat32Vec4$2[2] = matrix[10];\n tempFloat32Vec4$2[3] = matrix[14];\n\n this._state.modelMatrixCol2Buf.setData(tempFloat32Vec4$2, offset);\n }\n\n // ---------------------- COLOR RENDERING -----------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (frameCtx.withSAO && this.model.saoEnabled) {\n if (frameCtx.pbrEnabled && this.model.pbrEnabled && this._state.pbrSupported) {\n if (this._renderers.pbrRendererWithSAO) {\n this._renderers.pbrRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (frameCtx.colorTextureEnabled && this.model.colorTextureEnabled && this._state.colorTextureSupported) {\n if (this._renderers.colorTextureRendererWithSAO) {\n this._renderers.colorTextureRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (this._state.normalsBuf) {\n if (this._renderers.colorRendererWithSAO) {\n this._renderers.colorRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else {\n if (this._renderers.flatColorRendererWithSAO) {\n this._renderers.flatColorRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n } else if (frameCtx.pbrEnabled && this.model.pbrEnabled && this._state.pbrSupported) {\n if (this._renderers.pbrRenderer) {\n this._renderers.pbrRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (frameCtx.colorTextureEnabled && this.model.colorTextureEnabled && this._state.colorTextureSupported) {\n if (this._renderers.colorTextureRenderer) {\n this._renderers.colorTextureRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else if (this._state.normalsBuf) {\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else {\n if (this._renderers.flatColorRenderer) {\n this._renderers.flatColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n }\n\n _updateBackfaceCull(renderFlags, frameCtx) {\n const backfaces = this.model.backfaces || (!this.solid) || renderFlags.sectioned;\n if (frameCtx.backfaces !== backfaces) {\n const gl = frameCtx.gl;\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (frameCtx.pbrEnabled && this.model.pbrEnabled && this._state.pbrSupported) {\n if (this._renderers.pbrRenderer) {\n this._renderers.pbrRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n } else if (frameCtx.colorTextureEnabled && this.model.colorTextureEnabled && this._state.colorTextureSupported) {\n if (this._renderers.colorTextureRenderer) {\n this._renderers.colorTextureRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n } else if (this._state.normalsBuf) {\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n } else {\n if (this._renderers.flatColorRenderer) {\n this._renderers.flatColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n }\n\n // ---------------------- RENDERING SAO POST EFFECT TARGETS --------------\n\n drawDepth(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.depthRenderer) {\n this._renderers.depthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE); // Assume whatever post-effect uses depth (eg SAO) does not apply to transparent objects\n }\n }\n\n drawNormals(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.normalsRenderer) {\n this._renderers.normalsRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE); // Assume whatever post-effect uses normals (eg SAO) does not apply to transparent objects\n }\n }\n\n // ---------------------- SILHOUETTE RENDERING -----------------------------------\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n }\n }\n\n // ---------------------- EDGES RENDERING -----------------------------------\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numEdgesLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesColorRenderer) {\n this._renderers.edgesColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_COLOR_OPAQUE);\n }\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numEdgesLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesColorRenderer) {\n this._renderers.edgesColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_COLOR_TRANSPARENT);\n }\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_XRAYED);\n }\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_HIGHLIGHTED);\n }\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_SELECTED);\n }\n }\n\n // ---------------------- OCCLUSION CULL RENDERING -----------------------------------\n\n drawOcclusion(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.occlusionRenderer) {\n // Only opaque, filled objects can be occluders\n this._renderers.occlusionRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n // ---------------------- SHADOW BUFFER RENDERING -----------------------------------\n\n drawShadow(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.shadowRenderer) {\n this._renderers.shadowRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n //---- PICKING ----------------------------------------------------------------------------------------------------\n\n drawPickMesh(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.pickMeshRenderer) {\n this._renderers.pickMeshRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickDepths(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.pickDepthRenderer) {\n this._renderers.pickDepthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickNormals(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n\n ////////////////////////////////////////////////////////////////////////////////////////////////////\n // TODO\n // if (this._state.normalsBuf) {\n // if (this._renderers.pickNormalsRenderer) {\n // this._renderers.pickNormalsRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n // }\n ////////////////////////////////////////////////////////////////////////////////////////////////////\n // } else {\n if (this._renderers.pickNormalsFlatRenderer) {\n this._renderers.pickNormalsFlatRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n // }\n }\n\n drawSnapInit(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.snapInitRenderer) {\n this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnap(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.snapRenderer) {\n this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n //-----------------------------------------------------------------------------------------\n\n precisionRayPickSurface(portionId, worldRayOrigin, worldRayDir, worldSurfacePos, worldNormal) {\n\n if (!this.model.scene.pickSurfacePrecisionEnabled) {\n return false;\n }\n\n const geometry = this._state.geometry;\n const state = this._state;\n const portion = this._portions[portionId];\n\n if (!portion) {\n this.model.error(\"portion not found: \" + portionId);\n return false;\n }\n\n if (!portion.inverseMatrix) {\n portion.inverseMatrix = math.inverseMat4(portion.matrix, math.mat4());\n }\n\n if (worldNormal && !portion.normalMatrix) {\n portion.normalMatrix = math.transposeMat4(portion.inverseMatrix, math.mat4());\n }\n\n const quantizedPositions = geometry.quantizedPositions;\n const indices = geometry.indices;\n const origin = state.origin;\n const offset = portion.offset;\n\n const rtcRayOrigin = tempVec3a$w;\n const rtcRayDir = tempVec3b$s;\n\n rtcRayOrigin.set(origin ? math.subVec3(worldRayOrigin, origin, tempVec3c$n) : worldRayOrigin); // World -> RTC\n rtcRayDir.set(worldRayDir);\n\n if (offset) {\n math.subVec3(rtcRayOrigin, offset);\n }\n\n math.transformRay(this.model.worldNormalMatrix, rtcRayOrigin, rtcRayDir, rtcRayOrigin, rtcRayDir);\n\n math.transformRay(portion.inverseMatrix, rtcRayOrigin, rtcRayDir, rtcRayOrigin, rtcRayDir);\n\n const a = tempVec3d$7;\n const b = tempVec3e;\n const c = tempVec3f;\n\n let gotIntersect = false;\n let closestDist = 0;\n const closestIntersectPos = tempVec3g;\n\n for (let i = 0, len = indices.length; i < len; i += 3) {\n\n const ia = indices[i + 0] * 3;\n const ib = indices[i + 1] * 3;\n const ic = indices[i + 2] * 3;\n\n a[0] = quantizedPositions[ia];\n a[1] = quantizedPositions[ia + 1];\n a[2] = quantizedPositions[ia + 2];\n\n b[0] = quantizedPositions[ib];\n b[1] = quantizedPositions[ib + 1];\n b[2] = quantizedPositions[ib + 2];\n\n c[0] = quantizedPositions[ic];\n c[1] = quantizedPositions[ic + 1];\n c[2] = quantizedPositions[ic + 2];\n\n const {positionsDecodeMatrix} = state.geometry;\n\n math.decompressPosition(a, positionsDecodeMatrix);\n math.decompressPosition(b, positionsDecodeMatrix);\n math.decompressPosition(c, positionsDecodeMatrix);\n\n if (math.rayTriangleIntersect(rtcRayOrigin, rtcRayDir, a, b, c, closestIntersectPos)) {\n\n math.transformPoint3(portion.matrix, closestIntersectPos, closestIntersectPos);\n\n math.transformPoint3(this.model.worldMatrix, closestIntersectPos, closestIntersectPos);\n\n if (offset) {\n math.addVec3(closestIntersectPos, offset);\n }\n\n if (origin) {\n math.addVec3(closestIntersectPos, origin);\n }\n\n const dist = Math.abs(math.lenVec3(math.subVec3(closestIntersectPos, worldRayOrigin, [])));\n\n if (!gotIntersect || dist > closestDist) {\n closestDist = dist;\n worldSurfacePos.set(closestIntersectPos);\n if (worldNormal) { // Not that wasteful to eagerly compute - unlikely to hit >2 surfaces on most geometry\n math.triangleNormal(a, b, c, worldNormal);\n }\n gotIntersect = true;\n }\n }\n }\n\n if (gotIntersect && worldNormal) {\n math.transformVec3(portion.normalMatrix, worldNormal, worldNormal);\n math.transformVec3(this.model.worldNormalMatrix, worldNormal, worldNormal);\n math.normalizeVec3(worldNormal);\n }\n\n return gotIntersect;\n }\n\n destroy() {\n const state = this._state;\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n state.colorsBuf = null;\n }\n if (state.metallicRoughnessBuf) {\n state.metallicRoughnessBuf.destroy();\n state.metallicRoughnessBuf = null;\n }\n if (state.flagsBuf) {\n state.flagsBuf.destroy();\n state.flagsBuf = null;\n }\n if (state.offsetsBuf) {\n state.offsetsBuf.destroy();\n state.offsetsBuf = null;\n }\n if (state.modelMatrixCol0Buf) {\n state.modelMatrixCol0Buf.destroy();\n state.modelMatrixCol0Buf = null;\n }\n if (state.modelMatrixCol1Buf) {\n state.modelMatrixCol1Buf.destroy();\n state.modelMatrixCol1Buf = null;\n }\n if (state.modelMatrixCol2Buf) {\n state.modelMatrixCol2Buf.destroy();\n state.modelMatrixCol2Buf = null;\n }\n if (state.modelNormalMatrixCol0Buf) {\n state.modelNormalMatrixCol0Buf.destroy();\n state.modelNormalMatrixCol0Buf = null;\n }\n if (state.modelNormalMatrixCol1Buf) {\n state.modelNormalMatrixCol1Buf.destroy();\n state.modelNormalMatrixCol1Buf = null;\n }\n if (state.modelNormalMatrixCol2Buf) {\n state.modelNormalMatrixCol2Buf.destroy();\n state.modelNormalMatrixCol2Buf = null;\n }\n if (state.pickColorsBuf) {\n state.pickColorsBuf.destroy();\n state.pickColorsBuf = null;\n }\n state.destroy();\n this._state = null;\n }\n}\n\n/**\n * @private\n */\n\n\nclass VBOSceneModelLineBatchingRenderer extends VBORenderer {\n _draw(drawCfg) {\n const {gl} = this._scene.canvas;\n\n const {\n state,\n frameCtx,\n incrementDrawState\n } = drawCfg;\n\n gl.drawElements(gl.LINES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0);\n\n if (incrementDrawState) {\n frameCtx.drawElements++;\n }\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingLinesColorRenderer extends VBOSceneModelLineBatchingRenderer {\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines batching color vertex shader\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vColor;\");\n src.push(\"void main(void) {\");\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\"vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, float(color.a) / 255.0);\");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines batching color fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n src.push(\" outColor = vColor;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingLinesSilhouetteRenderer extends VBOSceneModelLineBatchingRenderer {\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n super.drawLayer(frameCtx, batchingLayer, renderPass, { colorUniform: true });\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines batching silhouette vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec4 color;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"void main(void) {\");\n\n // silhouetteFlag = NOT_RENDERED | SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n // renderPass = SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n\n src.push(`int silhouetteFlag = int(flags) >> 4 & 0xF;`);\n src.push(`if (silhouetteFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines batching silhouette fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"uniform vec4 color;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = color;\");\n src.push(\"}\");\n return src;\n }\n}\n\nconst tempVec3a$v = math.vec3();\nconst tempVec3b$r = math.vec3();\nconst tempVec3c$m = math.vec3();\nconst tempVec3d$6 = math.vec3();\nconst tempMat4a$m = math.mat4();\n\n/**\n * @private\n */\nclass VBOBatchingLinesSnapInitRenderer extends VBORenderer {\n drawLayer(frameCtx, batchingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = batchingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = batchingLayer._state;\n const origin = batchingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = batchingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(batchingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(batchingLayer));\n } else {\n this._vaoCache.set(batchingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$v;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$r;\n if (origin) {\n const rotatedOrigin = tempVec3c$m;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$m);\n rtcCameraEye = tempVec3d$6;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(batchingLayer);\n //=============================================================\n // TODO: Use drawElements count and offset to draw only one entity\n //=============================================================\n\n state.indicesBuf.bind();\n gl.drawElements(gl.LINES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0);\n state.indicesBuf.unbind();\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\");\n this._uLayerNumber = program.getLocation(\"layerNumber\");\n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBO SnapBatchingDepthBufInitRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec4 pickColor;\");\n src.push(\"in vec3 position;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n src.push(\"uniform vec2 snapVectorA;\");\n src.push(\"uniform vec2 snapInvVectorAB;\");\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n src.push(\"flat out vec4 vPickColor;\");\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vPickColor = pickColor;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBO SnapBatchingDepthBufInitRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\");\n src.push(\"uniform vec3 coordinateScaler;\");\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in vec4 vPickColor;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"layout(location = 0) out highp ivec4 outCoords;\");\n src.push(\"layout(location = 1) out highp ivec4 outNormal;\");\n src.push(\"layout(location = 2) out lowp uvec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" float dx = dFdx(vFragDepth);\");\n src.push(\" float dy = dFdy(vFragDepth);\");\n src.push(\" float diff = sqrt(dx*dx+dy*dy);\");\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth + diff ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, -layerNumber);\");\n\n src.push(\"vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n src.push(\"vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(`outNormal = ivec4(worldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"outPickColor = uvec4(vPickColor);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$u = math.vec3();\nconst tempVec3b$q = math.vec3();\nconst tempVec3c$l = math.vec3();\nconst tempVec3d$5 = math.vec3();\nconst tempMat4a$l = math.mat4();\n\n/**\n * @private\n */\nclass VBOBatchingLinesSnapRenderer extends VBORenderer{\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + (this._scene.pointsMaterial.hash);\n }\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = batchingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = batchingLayer._state;\n const origin = batchingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = batchingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(batchingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(batchingLayer));\n } else {\n this._vaoCache.set(batchingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$u;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$q;\n if (origin) {\n const rotatedOrigin = tempVec3c$l;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$l);\n rtcCameraEye = tempVec3d$5;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(batchingLayer);\n\n //=============================================================\n // TODO: Use drawElements count and offset to draw only one entity\n //=============================================================\n\n if (frameCtx.snapMode === \"edge\") {\n state.indicesBuf.bind();\n gl.drawElements(gl.LINES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0);\n state.indicesBuf.unbind(); // needed?\n } else {\n gl.drawArrays(gl.POINTS, 0, state.positionsBuf.numItems);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\"); \n this.uVectorA = program.getLocation(\"snapVectorA\"); \n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\"); \n this._uLayerNumber = program.getLocation(\"layerNumber\"); \n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\"); \n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n scene.pointsMaterial._state;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapBatchingDepthRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\"); \n src.push(\"uniform vec2 snapVectorA;\"); \n src.push(\"uniform vec2 snapInvVectorAB;\"); \n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"gl_PointSize = 1.0;\"); // Windows needs this?\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapBatchingDepthRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\"); \n src.push(\"uniform vec3 coordinateScaler;\"); \n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"out highp ivec4 outCoords;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, layerNumber);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingLinesRenderers {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n if (this._silhouetteRenderer && (!this._silhouetteRenderer.getValid())) {\n this._silhouetteRenderer.destroy();\n this._silhouetteRenderer = null;\n }\n if (this._snapInitRenderer && (!this._snapInitRenderer.getValid())) {\n this._snapInitRenderer.destroy();\n this._snapInitRenderer = null;\n }\n if (this._snapRenderer && (!this._snapRenderer.getValid())) {\n this._snapRenderer.destroy();\n this._snapRenderer = null;\n }\n }\n\n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new VBOBatchingLinesColorRenderer(this._scene, false);\n }\n return this._colorRenderer;\n }\n\n get silhouetteRenderer() {\n if (!this._silhouetteRenderer) {\n this._silhouetteRenderer = new VBOBatchingLinesSilhouetteRenderer(this._scene);\n }\n return this._silhouetteRenderer;\n }\n\n get snapInitRenderer() {\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new VBOBatchingLinesSnapInitRenderer(this._scene, false);\n }\n return this._snapInitRenderer;\n }\n\n get snapRenderer() {\n if (!this._snapRenderer) {\n this._snapRenderer = new VBOBatchingLinesSnapRenderer(this._scene);\n }\n return this._snapRenderer;\n }\n\n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n if (this._silhouetteRenderer) {\n this._silhouetteRenderer.destroy();\n }\n if (this._snapInitRenderer) {\n this._snapInitRenderer.destroy();\n }\n if (this._snapRenderer) {\n this._snapRenderer.destroy();\n }\n }\n}\n\nconst cachedRenderers$4 = {};\n\n/**\n * @private\n */\nfunction getRenderers$5(scene) {\n const sceneId = scene.id;\n let batchingRenderers = cachedRenderers$4[sceneId];\n if (!batchingRenderers) {\n batchingRenderers = new VBOBatchingLinesRenderers(scene);\n cachedRenderers$4[sceneId] = batchingRenderers;\n batchingRenderers._compile();\n scene.on(\"compile\", () => {\n batchingRenderers._compile();\n });\n scene.on(\"destroyed\", () => {\n delete cachedRenderers$4[sceneId];\n batchingRenderers._destroy();\n });\n }\n return batchingRenderers;\n}\n\n/**\n * @private\n */\nclass VBOBatchingLinesBuffer {\n\n constructor(maxGeometryBatchSize = 5000000) {\n\n if (maxGeometryBatchSize > 5000000) {\n maxGeometryBatchSize = 5000000;\n }\n\n this.maxVerts = maxGeometryBatchSize;\n this.maxIndices = maxGeometryBatchSize * 3; // Rough rule-of-thumb\n this.positions = [];\n this.colors = [];\n this.offsets = [];\n this.indices = [];\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingLinesLayer {\n\n /**\n * @param model\n * @param cfg\n * @param cfg.layerIndex\n * @param cfg.positionsDecodeMatrix\n * @param cfg.maxGeometryBatchSize\n * @param cfg.origin\n * @param cfg.scratchMemory\n */\n constructor(cfg) {\n\n console.info(\"Creating VBOBatchingLinesLayer\");\n\n /**\n * Index of this LinesBatchingLayer in {@link VBOSceneModel#_layerList}.\n * @type {Number}\n */\n this.layerIndex = cfg.layerIndex;\n\n this._renderers = getRenderers$5(cfg.model.scene);\n this.model = cfg.model;\n this._buffer = new VBOBatchingLinesBuffer(cfg.maxGeometryBatchSize);\n this._scratchMemory = cfg.scratchMemory;\n\n this._state = new RenderState({\n positionsBuf: null,\n offsetsBuf: null,\n colorsBuf: null,\n flagsBuf: null,\n indicesBuf: null,\n positionsDecodeMatrix: math.mat4(),\n origin: null\n });\n\n // These counts are used to avoid unnecessary render passes\n this._numPortions = 0;\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numEdgesLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n this._modelAABB = math.collapseAABB3(); // Model-space AABB\n this._portions = [];\n this._meshes = [];\n this._numVerts = 0;\n\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n\n this._finalized = false;\n\n if (cfg.positionsDecodeMatrix) {\n this._state.positionsDecodeMatrix.set(cfg.positionsDecodeMatrix);\n this._preCompressedPositionsExpected = true;\n } else {\n this._preCompressedPositionsExpected = false;\n }\n\n if (cfg.origin) {\n this._state.origin = math.vec3(cfg.origin);\n }\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Tests if there is room for another portion in this LinesBatchingLayer.\n *\n * @param lenPositions Number of positions we'd like to create in the portion.\n * @param lenIndices Number of indices we'd like to create in this portion.\n * @returns {Boolean} True if OK to create another portion.\n */\n canCreatePortion(lenPositions, lenIndices) {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n return ((this._buffer.positions.length + lenPositions) < (this._buffer.maxVerts * 3) && (this._buffer.indices.length + lenIndices) < (this._buffer.maxIndices));\n }\n\n /**\n * Creates a new portion within this LinesBatchingLayer, returns the new portion ID.\n *\n * Gives the portion the specified geometry, color and matrix.\n *\n * @param mesh The SceneModelMesh that owns the portion\n * @param cfg.positions Flat float Local-space positions array.\n * @param cfg.positionsCompressed Flat quantized positions array - decompressed with TrianglesBatchingLayer positionsDecodeMatrix\n * @param cfg.indices Flat int indices array.\n * @param cfg.color Quantized RGB color [0..255,0..255,0..255,0..255]\n * @param cfg.opacity Opacity [0..255]\n * @param [cfg.meshMatrix] Flat float 4x4 matrix\n * @param cfg.aabb Flat float AABB World-space AABB\n * @param cfg.pickColor Quantized pick color\n * @returns {number} Portion ID\n */\n createPortion(mesh, cfg) {\n\n if (this._finalized) {\n throw \"Already finalized\";\n }\n\n const positions = cfg.positions;\n const positionsCompressed = cfg.positionsCompressed;\n const indices = cfg.indices;\n const color = cfg.color;\n const opacity = cfg.opacity;\n\n const buffer = this._buffer;\n const positionsIndex = buffer.positions.length;\n const vertsIndex = positionsIndex / 3;\n\n let numVerts;\n\n math.expandAABB3(this._modelAABB, cfg.aabb);\n\n if (this._preCompressedPositionsExpected) {\n if (!positionsCompressed) {\n throw \"positionsCompressed expected\";\n }\n numVerts = positionsCompressed.length / 3;\n for (let i = 0, len = positionsCompressed.length; i < len; i++) {\n buffer.positions.push(positionsCompressed[i]);\n }\n } else {\n if (!positions) {\n throw \"positions expected\";\n }\n numVerts = positions.length / 3;\n for (let i = 0, len = positions.length; i < len; i++) {\n buffer.positions.push(positions[i]);\n }\n }\n\n if (color) {\n\n const r = color[0]; // Color is pre-quantized by VBOSceneModel\n const g = color[1];\n const b = color[2];\n const a = opacity;\n\n for (let i = 0; i < numVerts; i++) {\n buffer.colors.push(r);\n buffer.colors.push(g);\n buffer.colors.push(b);\n buffer.colors.push(a);\n }\n }\n\n if (indices) {\n for (let i = 0, len = indices.length; i < len; i++) {\n buffer.indices.push(indices[i] + vertsIndex);\n }\n }\n\n if (this.model.scene.entityOffsetsEnabled) {\n for (let i = 0; i < numVerts; i++) {\n buffer.offsets.push(0);\n buffer.offsets.push(0);\n buffer.offsets.push(0);\n }\n }\n\n const portionId = this._portions.length / 2;\n\n this._portions.push(vertsIndex);\n this._portions.push(numVerts);\n\n this._numPortions++;\n this.model.numPortions++;\n\n this._numVerts += numVerts;\n\n this._meshes.push(mesh);\n\n return portionId;\n }\n\n /**\n * Builds batch VBOs from appended geometries.\n * No more portions can then be created.\n */\n finalize() {\n\n if (this._finalized) {\n return;\n }\n\n const state = this._state;\n const gl = this.model.scene.canvas.gl;\n const buffer = this._buffer;\n\n if (buffer.positions.length > 0) {\n if (this._preCompressedPositionsExpected) {\n const positions = new Uint16Array(buffer.positions);\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, buffer.positions.length, 3, gl.STATIC_DRAW);\n } else {\n const positions = new Float32Array(buffer.positions);\n const quantizedPositions = quantizePositions(positions, this._modelAABB, state.positionsDecodeMatrix);\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, quantizedPositions, buffer.positions.length, 3, gl.STATIC_DRAW);\n }\n }\n\n if (buffer.colors.length > 0) {\n const colors = new Uint8Array(buffer.colors);\n let normalized = false;\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colors, buffer.colors.length, 4, gl.DYNAMIC_DRAW, normalized);\n }\n\n if (buffer.colors.length > 0) { // Because we build flags arrays here, get their length from the colors array\n const flagsLength = buffer.colors.length / 4;\n const flags = new Float32Array(flagsLength);\n let notNormalized = false;\n state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, flags, flags.length, 1, gl.DYNAMIC_DRAW, notNormalized);\n }\n\n if (this.model.scene.entityOffsetsEnabled) {\n if (buffer.offsets.length > 0) {\n const offsets = new Float32Array(buffer.offsets);\n state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, offsets, buffer.offsets.length, 3, gl.DYNAMIC_DRAW);\n }\n }\n\n if (buffer.indices.length > 0) {\n const indices = new Uint32Array(buffer.indices);\n state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, buffer.indices.length, 1, gl.STATIC_DRAW);\n }\n\n this._buffer = null;\n this._finalized = true;\n }\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n const deferred = true;\n this._setFlags(portionId, flags, meshTransparent, deferred);\n }\n\n flushInitFlags() {\n this._setDeferredFlags();\n }\n\n setVisible(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setHighlighted(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setXRayed(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setSelected(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setEdges(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n } else {\n this._numEdgesLayerPortions--;\n this.model.numEdgesLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags(portionId, flags);\n }\n\n setCulled(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions--;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setColor(portionId, color) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n const portionsIdx = portionId * 2;\n const vertexBase = this._portions[portionsIdx];\n const numVerts = this._portions[portionsIdx + 1];\n const firstColor = vertexBase * 4;\n const lenColor = numVerts * 4;\n const tempArray = this._scratchMemory.getUInt8Array(lenColor);\n const r = color[0];\n const g = color[1];\n const b = color[2];\n const a = color[3];\n for (let i = 0; i < lenColor; i += 4) {\n tempArray[i + 0] = r;\n tempArray[i + 1] = g;\n tempArray[i + 2] = b;\n tempArray[i + 3] = a;\n }\n this._state.colorsBuf.setData(tempArray, firstColor, lenColor);\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n /**\n * flags are 4bits values encoded on a 32bit base. color flag on the first 4 bits, silhouette flag on the next 4 bits and so on for edge, pick and clippable.\n */\n _setFlags(portionId, flags, transparent, deferred = false) {\n\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const portionsIdx = portionId * 2;\n const vertexBase = this._portions[portionsIdx];\n const numVerts = this._portions[portionsIdx + 1];\n const firstFlag = vertexBase;\n const lenFlags = numVerts;\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n // no edges\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n let colorFlag;\n if (!visible || culled || xrayed\n || (highlighted && !this.model.scene.highlightMaterial.glowThrough)\n || (selected && !this.model.scene.selectedMaterial.glowThrough)) {\n colorFlag = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (transparent) {\n colorFlag = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n colorFlag = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n let silhouetteFlag;\n if (!visible || culled) {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n\n const clippableFlag = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 1 : 0;\n\n if (deferred) {\n // Avoid zillions of individual WebGL bufferSubData calls - buffer them to apply in one shot\n if (!this._deferredFlagValues) {\n this._deferredFlagValues = new Float32Array(this._numVerts);\n }\n for (let i = firstFlag, len = (firstFlag + lenFlags); i < len; i++) {\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n // no edges\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n this._deferredFlagValues[i] = vertFlag;\n }\n } else if (this._state.flagsBuf) {\n const tempArray = this._scratchMemory.getFloat32Array(lenFlags);\n for (let i = 0; i < lenFlags; i++) {\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n // no edges\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n tempArray[i] = vertFlag;\n }\n this._state.flagsBuf.setData(tempArray, firstFlag, lenFlags);\n }\n }\n\n _setDeferredFlags() {\n if (this._deferredFlagValues) {\n this._state.flagsBuf.setData(this._deferredFlagValues);\n this._deferredFlagValues = null;\n }\n }\n\n setOffset(portionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (!this.model.scene.entityOffsetsEnabled) {\n this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n return;\n }\n const portionsIdx = portionId * 2;\n const vertexBase = this._portions[portionsIdx];\n const numVerts = this._portions[portionsIdx + 1];\n const firstOffset = vertexBase * 3;\n const lenOffsets = numVerts * 3;\n const tempArray = this._scratchMemory.getFloat32Array(lenOffsets);\n const x = offset[0];\n const y = offset[1];\n const z = offset[2];\n for (let i = 0; i < lenOffsets; i += 3) {\n tempArray[i + 0] = x;\n tempArray[i + 1] = y;\n tempArray[i + 2] = z;\n }\n this._state.offsetsBuf.setData(tempArray, firstOffset, lenOffsets);\n }\n\n //-- RENDERING ----------------------------------------------------------------------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n\n drawDepth(renderFlags, frameCtx) {\n }\n\n drawNormals(renderFlags, frameCtx) {\n }\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n }\n }\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n }\n\n drawPickMesh(frameCtx) {\n }\n\n drawPickDepths(frameCtx) {\n }\n\n drawPickNormals(frameCtx) {\n }\n\n drawSnapInit(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapInitRenderer) {\n this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnap(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapRenderer) {\n this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawOcclusion(frameCtx) {\n }\n\n drawShadow(frameCtx) {\n }\n\n destroy() {\n const state = this._state;\n if (state.positionsBuf) {\n state.positionsBuf.destroy();\n state.positionsBuf = null;\n }\n if (state.offsetsBuf) {\n state.offsetsBuf.destroy();\n state.offsetsBuf = null;\n }\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n state.colorsBuf = null;\n }\n if (state.flagsBuf) {\n state.flagsBuf.destroy();\n state.flagsBuf = null;\n }\n if (state.indicesBuf) {\n state.indicesBuf.destroy();\n state.indicesBuf = null;\n }\n state.destroy();\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingLinesRenderer extends VBORenderer {\n constructor(scene, withSAO) {\n super(scene, withSAO, {instancing: true});\n }\n\n _draw(drawCfg) {\n const {gl} = this._scene.canvas;\n\n const {\n state,\n frameCtx,\n incrementDrawState,\n } = drawCfg;\n\n gl.drawElementsInstanced(gl.LINES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0, state.numInstances);\n\n if (incrementDrawState) {\n frameCtx.drawElements++;\n }\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingLinesColorRenderer extends VBOInstancingLinesRenderer {\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, {incrementDrawState: true});\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines instancing color vertex shader\");\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE | COLOR_TRANSPARENT\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, float(color.a) / 255.0);\");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n let i;\n let len;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines instancing color fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n\n if (this._withSAO) {\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBAToDepth(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" outColor = vec4(vColor.rgb * ambient, vColor.a);\");\n } else {\n src.push(\" outColor = vColor;\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingLinesSilhouetteRenderer extends VBOInstancingLinesRenderer {\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n super.drawLayer(frameCtx, instancingLayer, renderPass, { colorUniform: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines instancing silhouette vertex shader\");\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n src.push(\"uniform vec4 color;\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"void main(void) {\");\n\n // silhouetteFlag = NOT_RENDERED | SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n // renderPass = SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n\n src.push(`int silhouetteFlag = int(flags) >> 4 & 0xF;`);\n src.push(`if (silhouetteFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Lines instancing silhouette fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"uniform vec4 color;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = color;\");\n src.push(\"}\");\n return src;\n }\n}\n\nconst tempVec3a$t = math.vec3();\nconst tempVec3b$p = math.vec3();\nconst tempVec3c$k = math.vec3();\nmath.vec3();\nconst tempMat4a$k = math.mat4();\n\n/**\n * @private\n */\nclass VBOInstancingLinesSnapInitRenderer extends VBORenderer {\n\n constructor(scene) {\n super(scene, false, { instancing: true });\n }\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = instancingLayer.model;\n const scene = model.scene;\n const gl = scene.canvas.gl;\n const camera = scene.camera;\n const state = instancingLayer._state;\n const origin = instancingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = instancingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(instancingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(instancingLayer));\n } else {\n this._vaoCache.set(instancingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$t;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$p;\n if (origin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$k);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$k);\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(instancingLayer);\n\n this._aModelMatrixCol0.bindArrayBuffer(state.modelMatrixCol0Buf);\n this._aModelMatrixCol1.bindArrayBuffer(state.modelMatrixCol1Buf);\n this._aModelMatrixCol2.bindArrayBuffer(state.modelMatrixCol2Buf);\n\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 1);\n\n\n if (this._aFlags) {\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n gl.vertexAttribDivisor(this._aFlags.location, 1);\n }\n\n state.indicesBuf.bind();\n gl.drawElementsInstanced(gl.LINES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0, state.numInstances);\n state.indicesBuf.unbind();\n\n // Cleanup\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 0);\n if (this._aFlags) {\n gl.vertexAttribDivisor(this._aFlags.location, 0);\n }\n if (this._aOffset) {\n gl.vertexAttribDivisor(this._aOffset.location, 0);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\");\n this._uLayerNumber = program.getLocation(\"layerNumber\");\n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthBufInitRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec4 pickColor;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec2 snapVectorA;\");\n src.push(\"uniform vec2 snapInvVectorAB;\");\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n src.push(\"flat out vec4 vPickColor;\");\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vPickColor = pickColor;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points instancing pick depth fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\");\n src.push(\"uniform vec3 coordinateScaler;\");\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in vec4 vPickColor;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"layout(location = 0) out highp ivec4 outCoords;\");\n ///////////////////////////////////////////\n // TODO: normal placeholder?\n // Primitive type?\n ///////////////////////////////////////////\n src.push(\"layout(location = 2) out lowp uvec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n {\n src.push(\" float dx = dFdx(vFragDepth);\");\n src.push(\" float dy = dFdy(vFragDepth);\");\n src.push(\" float diff = sqrt(dx*dx+dy*dy);\");\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth + diff ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, -layerNumber);\");\n src.push(\"outPickColor = uvec4(vPickColor);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$s = math.vec3();\nconst tempVec3b$o = math.vec3();\nconst tempVec3c$j = math.vec3();\nmath.vec3();\nconst tempMat4a$j = math.mat4();\n\n/**\n * @private\n */\nclass VBOInstancingLinesSnapRenderer extends VBORenderer {\n\n constructor(scene) {\n super(scene, false, { instancing: true });\n }\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate(instancingLayer);\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = instancingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = instancingLayer._state;\n const origin = instancingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = instancingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(instancingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(instancingLayer));\n } else {\n this._vaoCache.set(instancingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$s;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$o;\n if (origin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$j);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$j);\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(instancingLayer);\n\n\n this._aModelMatrixCol0.bindArrayBuffer(state.modelMatrixCol0Buf);\n this._aModelMatrixCol1.bindArrayBuffer(state.modelMatrixCol1Buf);\n this._aModelMatrixCol2.bindArrayBuffer(state.modelMatrixCol2Buf);\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 1);\n\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n gl.vertexAttribDivisor(this._aFlags.location, 1);\n\n if (frameCtx.snapMode === \"edge\") {\n state.indicesBuf.bind();\n gl.drawElementsInstanced(gl.LINES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0, state.numInstances);\n state.indicesBuf.unbind(); // needed?\n } else {\n gl.drawArraysInstanced(gl.POINTS, 0, state.positionsBuf.numItems, state.numInstances);\n }\n // Cleanup\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 0);\n gl.vertexAttribDivisor(this._aFlags.location, 0);\n if (this._aOffset) {\n gl.vertexAttribDivisor(this._aOffset.location, 0);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\"); \n this._uLayerNumber = program.getLocation(\"layerNumber\"); \n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\"); \n }\n\n _bindProgram() {\n this._program.bind();\n\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec2 snapVectorA;\"); \n src.push(\"uniform vec2 snapInvVectorAB;\"); \n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"gl_PointSize = 1.0;\"); // Windows needs this?\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\"); \n src.push(\"uniform vec3 coordinateScaler;\"); \n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"out highp ivec4 outCoords;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, layerNumber);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingLinesRenderers {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n if (this._silhouetteRenderer && (!this._silhouetteRenderer.getValid())) {\n this._silhouetteRenderer.destroy();\n this._silhouetteRenderer = null;\n }\n if (this._snapInitRenderer && (!this._snapInitRenderer.getValid())) {\n this._snapInitRenderer.destroy();\n this._snapInitRenderer = null;\n }\n if (this._snapRenderer && (!this._snapRenderer.getValid())) {\n this._snapRenderer.destroy();\n this._snapRenderer = null;\n }\n }\n\n eagerCreateRenders() {\n\n // Pre-initialize renderers that would otherwise be lazy-initialised\n // on user interaction, such as picking or emphasis, so that there is no delay\n // when user first begins interacting with the viewer.\n\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new VBOInstancingLinesSnapInitRenderer(this._scene, false);\n }\n if (!this._snapRenderer) {\n this._snapRenderer = new VBOInstancingLinesSnapRenderer(this._scene);\n }\n }\n\n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new VBOInstancingLinesColorRenderer(this._scene);\n }\n return this._colorRenderer;\n }\n\n get silhouetteRenderer() {\n if (!this._silhouetteRenderer) {\n this._silhouetteRenderer = new VBOInstancingLinesSilhouetteRenderer(this._scene);\n }\n return this._silhouetteRenderer;\n }\n\n get snapInitRenderer() {\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new VBOInstancingLinesSnapInitRenderer(this._scene, false);\n }\n return this._snapInitRenderer;\n }\n\n get snapRenderer() {\n if (!this._snapRenderer) {\n this._snapRenderer = new VBOInstancingLinesSnapRenderer(this._scene);\n }\n return this._snapRenderer;\n }\n\n\n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n if (this._silhouetteRenderer) {\n this._silhouetteRenderer.destroy();\n }\n if (this._snapInitRenderer) {\n this._snapInitRenderer.destroy();\n }\n if (this._snapRenderer) {\n this._snapRenderer.destroy();\n }\n }\n}\n\nconst cachedRenderers$3 = {};\n\n/**\n * @private\n */\nfunction getRenderers$4(scene) {\n const sceneId = scene.id;\n let instancingRenderers = cachedRenderers$3[sceneId];\n if (!instancingRenderers) {\n instancingRenderers = new VBOInstancingLinesRenderers(scene);\n cachedRenderers$3[sceneId] = instancingRenderers;\n instancingRenderers._compile();\n scene.on(\"compile\", () => {\n instancingRenderers._compile();\n });\n scene.on(\"destroyed\", () => {\n delete cachedRenderers$3[sceneId];\n instancingRenderers._destroy();\n });\n }\n return instancingRenderers;\n}\n\n/*\n\nfunction getSnapInstancingRenderers(scene) {\n const sceneId = scene.id;\n let instancingRenderers = cachedRenderers[sceneId];\n if (!instancingRenderers) {\n instancingRenderers = new VBOInstancingLineSnapRenderers(scene);\n cachedRenderers[sceneId] = instancingRenderers;\n instancingRenderers._compile();\n instancingRenderers.eagerCreateRenders();\n scene.on(\"compile\", () => {\n instancingRenderers._compile();\n instancingRenderers.eagerCreateRenders();\n });\n scene.on(\"destroyed\", () => {\n delete cachedRenderers[sceneId];\n instancingRenderers._destroy();\n });\n }\n return instancingRenderers;\n}\n\n */\n\nconst tempUint8Vec4$1 = new Uint8Array(4);\nconst tempFloat32$1 = new Float32Array(1);\n\nconst tempVec3fa$1 = new Float32Array(3);\n\nconst tempFloat32Vec4$1 = new Float32Array(4);\n\n/**\n * @private\n */\nclass VBOInstancingLinesLayer {\n\n /**\n * @param cfg\n * @param cfg.layerIndex\n * @param cfg.model\n * @param cfg.geometry\n * @param cfg.material\n * @param cfg.origin\n */\n constructor(cfg) {\n\n console.info(\"VBOInstancingLinesLayer\");\n\n /**\n * Owner model\n * @type {VBOSceneModel}\n */\n this.model = cfg.model;\n\n /**\n * Shared material\n * @type {VBOSceneModelGeometry}\n */\n this.material = cfg.material;\n\n /**\n * State sorting key.\n * @type {string}\n */\n this.sortId = \"LinesInstancingLayer\";\n\n /**\n * Index of this InstancingLayer in VBOSceneModel#_layerList\n * @type {Number}\n */\n this.layerIndex = cfg.layerIndex;\n\n this._renderers = getRenderers$4(cfg.model.scene);\n\n this._aabb = math.collapseAABB3();\n\n this._state = new RenderState({\n obb: math.OBB3(),\n numInstances: 0,\n origin: null,\n geometry: cfg.geometry,\n positionsDecodeMatrix: cfg.geometry.positionsDecodeMatrix, // So we can null the geometry for GC\n positionsBuf: null,\n colorsBuf: null,\n flagsBuf: null,\n offsetsBuf: null,\n modelMatrixCol0Buf: null,\n modelMatrixCol1Buf: null,\n modelMatrixCol2Buf: null\n });\n\n // These counts are used to avoid unnecessary render passes\n this._numPortions = 0;\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numEdgesLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n /** @private */\n this.numIndices = cfg.geometry.numIndices;\n\n // Vertex arrays\n this._colors = [];\n this._offsets = [];\n\n // Modeling matrix per instance, array for each column\n this._modelMatrixCol0 = [];\n this._modelMatrixCol1 = [];\n this._modelMatrixCol2 = [];\n\n this._portions = [];\n this._meshes = [];\n\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n\n if (cfg.origin) {\n this._state.origin = math.vec3(cfg.origin);\n }\n\n this._finalized = false;\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Creates a new portion within this InstancingLayer, returns the new portion ID.\n *\n * The portion will instance this InstancingLayer's geometry.\n *\n * Gives the portion the specified color and matrix.\n *\n * @param mesh The SceneModelMesh that owns the portion\n * @param cfg Portion params\n * @param cfg.color Color [0..255,0..255,0..255]\n * @param cfg.opacity Opacity [0..255].\n * @param cfg.meshMatrix Flat float 4x4 matrix.\n * @returns {number} Portion ID.\n */\n createPortion(mesh, cfg) {\n\n const color = cfg.color;\n const opacity = cfg.opacity;\n const meshMatrix = cfg.meshMatrix;\n\n if (this._finalized) {\n throw \"Already finalized\";\n }\n\n const r = color[0]; // Color is pre-quantized by VBOSceneModel\n const g = color[1];\n const b = color[2];\n color[3];\n\n this._colors.push(r);\n this._colors.push(g);\n this._colors.push(b);\n this._colors.push(opacity);\n\n if (this.model.scene.entityOffsetsEnabled) {\n this._offsets.push(0);\n this._offsets.push(0);\n this._offsets.push(0);\n }\n\n this._modelMatrixCol0.push(meshMatrix[0]);\n this._modelMatrixCol0.push(meshMatrix[4]);\n this._modelMatrixCol0.push(meshMatrix[8]);\n this._modelMatrixCol0.push(meshMatrix[12]);\n\n this._modelMatrixCol1.push(meshMatrix[1]);\n this._modelMatrixCol1.push(meshMatrix[5]);\n this._modelMatrixCol1.push(meshMatrix[9]);\n this._modelMatrixCol1.push(meshMatrix[13]);\n\n this._modelMatrixCol2.push(meshMatrix[2]);\n this._modelMatrixCol2.push(meshMatrix[6]);\n this._modelMatrixCol2.push(meshMatrix[10]);\n this._modelMatrixCol2.push(meshMatrix[14]);\n\n this._state.numInstances++;\n\n const portionId = this._portions.length;\n this._portions.push({});\n\n this._numPortions++;\n this.model.numPortions++;\n\n this._meshes.push(mesh);\n\n return portionId;\n }\n\n finalize() {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n const gl = this.model.scene.canvas.gl;\n const state = this._state;\n const geometry = state.geometry;\n const colorsLength = this._colors.length;\n const flagsLength = colorsLength / 4;\n if (colorsLength > 0) {\n let notNormalized = false;\n this._state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Uint8Array(this._colors), this._colors.length, 4, gl.DYNAMIC_DRAW, notNormalized);\n this._colors = []; // Release memory\n }\n if (flagsLength > 0) {\n // Because we only build flags arrays here, \n // get their length from the colors array\n let notNormalized = false;\n this._state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(flagsLength), flagsLength, 1, gl.DYNAMIC_DRAW, notNormalized);\n }\n if (this.model.scene.entityOffsetsEnabled) {\n if (this._offsets.length > 0) {\n const notNormalized = false;\n this._state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._offsets), this._offsets.length, 3, gl.DYNAMIC_DRAW, notNormalized);\n this._offsets = []; // Release memory\n }\n }\n if (geometry.colorsCompressed && geometry.colorsCompressed.length > 0) {\n const colorsCompressed = new Uint8Array(geometry.colorsCompressed);\n const notNormalized = false;\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colorsCompressed, colorsCompressed.length, 4, gl.STATIC_DRAW, notNormalized);\n }\n if (geometry.positionsCompressed && geometry.positionsCompressed.length > 0) {\n const normalized = false;\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, geometry.positionsCompressed, geometry.positionsCompressed.length, 3, gl.STATIC_DRAW, normalized);\n state.positionsDecodeMatrix = math.mat4(geometry.positionsDecodeMatrix);\n }\n if (geometry.indices && geometry.indices.length > 0) {\n state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(geometry.indices), geometry.indices.length, 1, gl.STATIC_DRAW);\n state.numIndices = geometry.indices.length;\n }\n if (this._modelMatrixCol0.length > 0) {\n const normalized = false;\n this._state.modelMatrixCol0Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol0), this._modelMatrixCol0.length, 4, gl.STATIC_DRAW, normalized);\n this._state.modelMatrixCol1Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol1), this._modelMatrixCol1.length, 4, gl.STATIC_DRAW, normalized);\n this._state.modelMatrixCol2Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol2), this._modelMatrixCol2.length, 4, gl.STATIC_DRAW, normalized);\n this._modelMatrixCol0 = [];\n this._modelMatrixCol1 = [];\n this._modelMatrixCol2 = [];\n }\n this._state.geometry = null;\n this._finalized = true;\n }\n\n // The following setters are called by VBOSceneModelMesh, in turn called by VBOSceneModelNode, only after the layer is finalized.\n // It's important that these are called after finalize() in order to maintain integrity of counts like _numVisibleLayerPortions etc.\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setVisible(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setHighlighted(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setXRayed(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setSelected(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setEdges(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n } else {\n this._numEdgesLayerPortions--;\n this.model.numEdgesLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags(portionId, flags);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setCulled(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions--;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setColor(portionId, color) { // RGBA color is normalized as ints\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n tempUint8Vec4$1[0] = color[0];\n tempUint8Vec4$1[1] = color[1];\n tempUint8Vec4$1[2] = color[2];\n tempUint8Vec4$1[3] = color[3];\n this._state.colorsBuf.setData(tempUint8Vec4$1, portionId * 4, 4);\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n /**\n * flags are 4bits values encoded on a 32bit base. color flag on the first 4 bits, silhouette flag on the next 4 bits and so on for edge, pick and clippable.\n */\n _setFlags(portionId, flags, meshTransparent) {\n\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n const edges = !!(flags & ENTITY_FLAGS.EDGES);\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n let colorFlag;\n if (!visible || culled || xrayed\n || (highlighted && !this.model.scene.highlightMaterial.glowThrough)\n || (selected && !this.model.scene.selectedMaterial.glowThrough)) {\n colorFlag = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (meshTransparent) {\n colorFlag = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n colorFlag = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n let silhouetteFlag;\n if (!visible || culled) {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let edgeFlag = 0;\n if (!visible || culled) {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n edgeFlag = RENDER_PASSES.EDGES_SELECTED;\n } else if (highlighted) {\n edgeFlag = RENDER_PASSES.EDGES_HIGHLIGHTED;\n } else if (xrayed) {\n edgeFlag = RENDER_PASSES.EDGES_XRAYED;\n } else if (edges) {\n if (meshTransparent) {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;\n } else {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_OPAQUE;\n }\n } else {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n\n const clippableFlag = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 255 : 0;\n\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n vertFlag |= edgeFlag << 8;\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n tempFloat32$1[0] = vertFlag;\n\n this._state.flagsBuf.setData(tempFloat32$1, portionId);\n }\n\n setOffset(portionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (!this.model.scene.entityOffsetsEnabled) {\n this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n return;\n }\n tempVec3fa$1[0] = offset[0];\n tempVec3fa$1[1] = offset[1];\n tempVec3fa$1[2] = offset[2];\n this._state.offsetsBuf.setData(tempVec3fa$1, portionId * 3, 3);\n }\n\n setMatrix(portionId, matrix) {\n\n ////////////////////////////////////////\n // TODO: Update portion matrix\n ////////////////////////////////////////\n\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n const offset = portionId * 4;\n\n tempFloat32Vec4$1[0] = matrix[0];\n tempFloat32Vec4$1[1] = matrix[4];\n tempFloat32Vec4$1[2] = matrix[8];\n tempFloat32Vec4$1[3] = matrix[12];\n\n this._state.modelMatrixCol0Buf.setData(tempFloat32Vec4$1, offset);\n\n tempFloat32Vec4$1[0] = matrix[1];\n tempFloat32Vec4$1[1] = matrix[5];\n tempFloat32Vec4$1[2] = matrix[9];\n tempFloat32Vec4$1[3] = matrix[13];\n\n this._state.modelMatrixCol1Buf.setData(tempFloat32Vec4$1, offset);\n\n tempFloat32Vec4$1[0] = matrix[2];\n tempFloat32Vec4$1[1] = matrix[6];\n tempFloat32Vec4$1[2] = matrix[10];\n tempFloat32Vec4$1[3] = matrix[14];\n\n this._state.modelMatrixCol2Buf.setData(tempFloat32Vec4$1, offset);\n }\n\n // ---------------------- NORMAL RENDERING -----------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n\n // ---------------------- RENDERING SAO POST EFFECT TARGETS --------------\n\n drawDepth(renderFlags, frameCtx) {\n }\n\n drawNormals(renderFlags, frameCtx) {\n }\n\n // ---------------------- EMPHASIS RENDERING -----------------------------------\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n }\n }\n\n // ---------------------- EDGES RENDERING -----------------------------------\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n }\n\n\n drawSnapInit(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapInitRenderer) {\n this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnap(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapRenderer) {\n this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n // ---------------------- OCCLUSION CULL RENDERING -----------------------------------\n\n drawOcclusion(renderFlags, frameCtx) {\n }\n\n // ---------------------- SHADOW BUFFER RENDERING -----------------------------------\n\n drawShadow(renderFlags, frameCtx) {\n }\n\n //---- PICKING ----------------------------------------------------------------------------------------------------\n\n drawPickMesh(renderFlags, frameCtx) {\n }\n\n drawPickDepths(renderFlags, frameCtx) {\n }\n\n drawPickNormals(renderFlags, frameCtx) {\n }\n\n\n destroy() {\n const state = this._state;\n if (state.positionsBuf) {\n state.positionsBuf.destroy();\n state.positionsBuf = null;\n }\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n state.colorsBuf = null;\n }\n if (state.flagsBuf) {\n state.flagsBuf.destroy();\n state.flagsBuf = null;\n }\n if (state.offsetsBuf) {\n state.offsetsBuf.destroy();\n state.offsetsBuf = null;\n }\n if (state.modelMatrixCol0Buf) {\n state.modelMatrixCol0Buf.destroy();\n state.modelMatrixCol0Buf = null;\n }\n if (state.modelMatrixCol1Buf) {\n state.modelMatrixCol1Buf.destroy();\n state.modelMatrixCol1Buf = null;\n }\n if (state.modelMatrixCol2Buf) {\n state.modelMatrixCol2Buf.destroy();\n state.modelMatrixCol2Buf = null;\n }\n state.destroy();\n }\n}\n\n/**\n * @private\n */\n\n\nclass VBOBatchingPointsRenderer extends VBORenderer {\n _draw(drawCfg) {\n const {gl} = this._scene.canvas;\n\n const {\n state,\n frameCtx,\n incrementDrawState,\n } = drawCfg;\n\n gl.drawArrays(gl.POINTS, 0, state.positionsBuf.numItems);\n\n if (incrementDrawState) {\n frameCtx.drawArrays++;\n }\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingPointsColorRenderer extends VBOBatchingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points batching color vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (pointsMaterial.filterIntensity) {\n src.push(\"uniform vec2 intensityRange;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n if (pointsMaterial.filterIntensity) {\n src.push(\"float intensity = float(color.a) / 255.0;\");\n src.push(\"if (intensity < intensityRange[0] || intensity > intensityRange[1]) {\");\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n }\n\n src.push(\"vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\"worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, 1.0);\");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n if (pointsMaterial.filterIntensity) {\n src.push(\"}\");\n }\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points batching color fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n src.push(\" outColor = vColor;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingPointsSilhouetteRenderer extends VBOBatchingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n drawLayer(frameCtx, pointsBatchingLayer, renderPass) {\n super.drawLayer(frameCtx, pointsBatchingLayer, renderPass, { colorUniform: true });\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points batching silhouette vertex shader\");\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec4 color;\");\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"void main(void) {\");\n\n // silhouetteFlag = NOT_RENDERED | SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | SILHOUETTE_XRAYED\n // renderPass = SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n\n src.push(`int silhouetteFlag = int(flags) >> 4 & 0xF;`);\n src.push(`if (silhouetteFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = clipPos;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n let i;\n let len;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points batching silhouette vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"uniform vec4 color;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = color;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass VBOBatchingPointsPickMeshRenderer extends VBOBatchingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + (this._scene.pointsMaterial.hash);\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points batching pick mesh vertex shader\");\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n src.push(\"in vec4 pickColor;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vPickColor;\");\n\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vPickColor = vec4(float(pickColor.r) / 255.0, float(pickColor.g) / 255.0, float(pickColor.b) / 255.0, float(pickColor.a) / 255.0);\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"gl_PointSize += 10.0;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points batching pick mesh vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vPickColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vPickColor; \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass VBOBatchingPointsPickDepthRenderer extends VBOBatchingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + (this._scene.pointsMaterial.hash);\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points batched pick depth vertex shader\");\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"gl_PointSize += 10.0;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points batched pick depth fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n src.push(\"uniform float pickZNear;\");\n src.push(\"uniform float pickZFar;\");\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"vec4 packDepth(const in float depth) {\");\n src.push(\" const vec4 bitShift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);\");\n src.push(\" const vec4 bitMask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);\");\n src.push(\" vec4 res = fract(depth * bitShift);\");\n src.push(\" res -= res.xxyz * bitMask;\");\n src.push(\" return res;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" float zNormalizedDepth = abs((pickZNear + vViewPosition.z) / (pickZFar - pickZNear));\");\n src.push(\" outColor = packDepth(zNormalizedDepth); \"); // Must be linear depth\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingPointsOcclusionRenderer extends VBOBatchingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + (this._scene.pointsMaterial.hash);\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points batching occlusion vertex shader\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n // Only opaque objects can be occluders\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\" gl_Position = clipPos;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points batching occlusion fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vec4(0.0, 0.0, 1.0, 1.0); \"); // Occluders are blue\n src.push(\"}\");\n return src;\n }\n}\n\nconst tempVec3a$r = math.vec3();\nconst tempVec3b$n = math.vec3();\nconst tempVec3c$i = math.vec3();\nconst tempVec3d$4 = math.vec3();\nconst tempMat4a$i = math.mat4();\n\n/**\n * @private\n */\nclass VBOBatchingPointsSnapInitRenderer extends VBORenderer {\n drawLayer(frameCtx, batchingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = batchingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = batchingLayer._state;\n const origin = batchingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = batchingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(batchingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(batchingLayer));\n } else {\n this._vaoCache.set(batchingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$r;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$n;\n if (origin) {\n const rotatedOrigin = tempVec3c$i;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$i);\n rtcCameraEye = tempVec3d$4;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(batchingLayer);\n //=============================================================\n // TODO: Use drawElements count and offset to draw only one entity\n //=============================================================\n\n gl.drawArrays(gl.POINTS, 0, state.positionsBuf.numItems);\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\");\n this._uLayerNumber = program.getLocation(\"layerNumber\");\n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBOBatchingPointsSnapInitRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec4 pickColor;\");\n src.push(\"in vec3 position;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n src.push(\"uniform vec2 snapVectorA;\");\n src.push(\"uniform vec2 snapInvVectorAB;\");\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n src.push(\"flat out vec4 vPickColor;\");\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vPickColor = pickColor;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBOBatchingPointsSnapInitRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\");\n src.push(\"uniform vec3 coordinateScaler;\");\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in vec4 vPickColor;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"layout(location = 0) out highp ivec4 outCoords;\");\n src.push(\"layout(location = 1) out highp ivec4 outNormal;\");\n src.push(\"layout(location = 2) out lowp uvec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" float dx = dFdx(vFragDepth);\");\n src.push(\" float dy = dFdy(vFragDepth);\");\n src.push(\" float diff = sqrt(dx*dx+dy*dy);\");\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth + diff ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, -layerNumber);\");\n\n // src.push(\"vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n // src.push(\"vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n // src.push(\"vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(`outNormal = ivec4(1.0, 1.0, 1.0, 1.0);`);\n src.push(\"outPickColor = uvec4(vPickColor);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$q = math.vec3();\nconst tempVec3b$m = math.vec3();\nconst tempVec3c$h = math.vec3();\nconst tempVec3d$3 = math.vec3();\nconst tempMat4a$h = math.mat4();\n\n/**\n * @private\n */\nclass VBOBatchingPointsSnapRenderer extends VBORenderer{\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + (this._scene.pointsMaterial.hash);\n }\n\n drawLayer(frameCtx, batchingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = batchingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = batchingLayer._state;\n const origin = batchingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = batchingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(batchingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(batchingLayer));\n } else {\n this._vaoCache.set(batchingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$q;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$m;\n if (origin) {\n const rotatedOrigin = tempVec3c$h;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$h);\n rtcCameraEye = tempVec3d$3;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(batchingLayer);\n\n //=============================================================\n // TODO: Use drawElements count and offset to draw only one entity\n //=============================================================\n\n gl.drawArrays(gl.POINTS, 0, state.positionsBuf.numItems);\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\"); \n this.uVectorA = program.getLocation(\"snapVectorA\"); \n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\"); \n this._uLayerNumber = program.getLocation(\"layerNumber\"); \n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\"); \n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n scene.pointsMaterial._state;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBOBatchingPointsSnapRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec3 uCameraEyeRtc;\"); \n src.push(\"uniform vec2 snapVectorA;\"); \n src.push(\"uniform vec2 snapInvVectorAB;\"); \n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (positionsDecodeMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"gl_PointSize = 1.0;\"); // Windows needs this?\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// VBOBatchingPointsSnapRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\"); \n src.push(\"uniform vec3 coordinateScaler;\"); \n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"out highp ivec4 outCoords;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, layerNumber);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingPointsRenderers {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n if (this._silhouetteRenderer && (!this._silhouetteRenderer.getValid())) {\n this._silhouetteRenderer.destroy();\n this._silhouetteRenderer = null;\n }\n if (this._pickMeshRenderer && (!this._pickMeshRenderer.getValid())) {\n this._pickMeshRenderer.destroy();\n this._pickMeshRenderer = null;\n }\n if (this._pickDepthRenderer && (!this._pickDepthRenderer.getValid())) {\n this._pickDepthRenderer.destroy();\n this._pickDepthRenderer = null;\n }\n if (this._occlusionRenderer && this._occlusionRenderer.getValid() === false) {\n this._occlusionRenderer.destroy();\n this._occlusionRenderer = null;\n }\n if (this._snapInitRenderer && (!this._snapInitRenderer.getValid())) {\n this._snapInitRenderer.destroy();\n this._snapInitRenderer = null;\n }\n if (this._snapRenderer && (!this._snapRenderer.getValid())) {\n this._snapRenderer.destroy();\n this._snapRenderer = null;\n }\n }\n\n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new VBOBatchingPointsColorRenderer(this._scene);\n }\n return this._colorRenderer;\n }\n\n get silhouetteRenderer() {\n if (!this._silhouetteRenderer) {\n this._silhouetteRenderer = new VBOBatchingPointsSilhouetteRenderer(this._scene);\n }\n return this._silhouetteRenderer;\n }\n\n get pickMeshRenderer() {\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new VBOBatchingPointsPickMeshRenderer(this._scene);\n }\n return this._pickMeshRenderer;\n }\n\n get pickDepthRenderer() {\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new VBOBatchingPointsPickDepthRenderer(this._scene);\n }\n return this._pickDepthRenderer;\n }\n\n get occlusionRenderer() {\n if (!this._occlusionRenderer) {\n this._occlusionRenderer = new VBOBatchingPointsOcclusionRenderer(this._scene);\n }\n return this._occlusionRenderer;\n }\n\n get snapInitRenderer() {\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new VBOBatchingPointsSnapInitRenderer(this._scene, false);\n }\n return this._snapInitRenderer;\n }\n\n get snapRenderer() {\n if (!this._snapRenderer) {\n this._snapRenderer = new VBOBatchingPointsSnapRenderer(this._scene);\n }\n return this._snapRenderer;\n }\n\n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n if (this._silhouetteRenderer) {\n this._silhouetteRenderer.destroy();\n }\n if (this._pickMeshRenderer) {\n this._pickMeshRenderer.destroy();\n }\n if (this._pickDepthRenderer) {\n this._pickDepthRenderer.destroy();\n }\n if (this._occlusionRenderer) {\n this._occlusionRenderer.destroy();\n }\n if (this._snapInitRenderer) {\n this._snapInitRenderer.destroy();\n }\n if (this._snapRenderer) {\n this._snapRenderer.destroy();\n }\n }\n}\n\nconst cachedRenderers$2 = {};\n\n/**\n * @private\n */\nfunction getRenderers$3(scene) {\n const sceneId = scene.id;\n let renderers = cachedRenderers$2[sceneId];\n if (!renderers) {\n renderers = new VBOBatchingPointsRenderers(scene);\n cachedRenderers$2[sceneId] = renderers;\n renderers._compile();\n scene.on(\"compile\", () => {\n renderers._compile();\n });\n scene.on(\"destroyed\", () => {\n delete cachedRenderers$2[sceneId];\n renderers._destroy();\n });\n }\n return renderers;\n}\n\n/**\n * @private\n */\nclass VBOBatchingPointsBuffer {\n\n constructor(maxGeometryBatchSize = 5000000) {\n\n if (maxGeometryBatchSize > 5000000) {\n maxGeometryBatchSize = 5000000;\n }\n\n this.maxVerts = maxGeometryBatchSize;\n this.maxIndices = maxGeometryBatchSize * 3; // Rough rule-of-thumb\n this.positions = [];\n this.colors = [];\n this.intensities = [];\n this.pickColors = [];\n this.offsets = [];\n }\n}\n\n/**\n * @private\n */\nclass VBOBatchingPointsLayer {\n\n /**\n * @param model\n * @param cfg\n * @param cfg.layerIndex\n * @param cfg.positionsDecodeMatrix\n * @param cfg.maxGeometryBatchSize\n * @param cfg.origin\n * @param cfg.scratchMemory\n */\n constructor(cfg) {\n\n console.info(\"Creating VBOBatchingPointsLayer\");\n\n /**\n * Owner model\n * @type {VBOSceneModel}\n */\n this.model = cfg.model;\n\n /**\n * State sorting key.\n * @type {string}\n */\n this.sortId = \"PointsBatchingLayer\";\n\n /**\n * Index of this PointsBatchingLayer in {@link VBOSceneModel#_layerList}.\n * @type {Number}\n */\n this.layerIndex = cfg.layerIndex;\n\n this._renderers = getRenderers$3(cfg.model.scene);\n\n this._buffer = new VBOBatchingPointsBuffer(cfg.maxGeometryBatchSize);\n this._scratchMemory = cfg.scratchMemory;\n\n this._state = new RenderState({\n positionsBuf: null,\n offsetsBuf: null,\n colorsBuf: null,\n flagsBuf: null,\n positionsDecodeMatrix: math.mat4(),\n origin: null\n });\n\n // These counts are used to avoid unnecessary render passes\n this._numPortions = 0;\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n this._modelAABB = math.collapseAABB3(); // Model-space AABB\n this._portions = [];\n this._meshes = [];\n\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n\n this._finalized = false;\n\n if (cfg.positionsDecodeMatrix) {\n this._state.positionsDecodeMatrix.set(cfg.positionsDecodeMatrix);\n this._preCompressedPositionsExpected = true;\n } else {\n this._preCompressedPositionsExpected = false;\n }\n\n if (cfg.origin) {\n this._state.origin = math.vec3(cfg.origin);\n }\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Tests if there is room for another portion in this PointsBatchingLayer.\n *\n * @param lenPositions Number of positions we'd like to create in the portion.\n * @returns {Boolean} True if OK to create another portion.\n */\n canCreatePortion(lenPositions) {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n return ((this._buffer.positions.length + lenPositions) < (this._buffer.maxVerts * 3));\n }\n\n /**\n * Creates a new portion within this PointsBatchingLayer, returns the new portion ID.\n *\n * Gives the portion the specified geometry, color and matrix.\n *\n * @param mesh The SceneModelMesh that owns the portion\n * @param cfg.positions Flat float Local-space positions array.\n * @param cfg.positionsCompressed Flat quantized positions array - decompressed with PointsBatchingLayer positionsDecodeMatrix\n * @param [cfg.colorsCompressed] Quantized RGB colors [0..255,0..255,0..255,0..255]\n * @param [cfg.colors] Flat float colors array.\n * @param cfg.color Float RGB color [0..1,0..1,0..1]\n * @param [cfg.meshMatrix] Flat float 4x4 matrix\n * @param cfg.aabb Flat float AABB World-space AABB\n * @param cfg.pickColor Quantized pick color\n * @returns {number} Portion ID\n */\n createPortion(mesh, cfg) {\n\n if (this._finalized) {\n throw \"Already finalized\";\n }\n\n const positions = cfg.positions;\n const positionsCompressed = cfg.positionsCompressed;\n const color = cfg.color;\n const colorsCompressed = cfg.colorsCompressed;\n const colors = cfg.colors;\n const pickColor = cfg.pickColor;\n\n const buffer = this._buffer;\n const positionsIndex = buffer.positions.length;\n const vertsIndex = positionsIndex / 3;\n\n let numVerts;\n\n math.expandAABB3(this._modelAABB, cfg.aabb);\n\n if (this._preCompressedPositionsExpected) {\n\n if (!positionsCompressed) {\n throw \"positionsCompressed expected\";\n }\n\n for (let i = 0, len = positionsCompressed.length; i < len; i++) {\n buffer.positions.push(positionsCompressed[i]);\n }\n\n numVerts = positionsCompressed.length / 3;\n\n } else {\n\n if (!positions) {\n throw \"positions expected\";\n }\n\n numVerts = positions.length / 3;\n\n positions.length;\n buffer.positions.length;\n\n for (let i = 0, len = positions.length; i < len; i++) {\n buffer.positions.push(positions[i]);\n }\n }\n\n if (colorsCompressed) {\n for (let i = 0, len = colorsCompressed.length; i < len; i++) {\n buffer.colors.push(colorsCompressed[i]);\n }\n\n } else if (colors) {\n for (let i = 0, len = colors.length; i < len; i++) {\n buffer.colors.push(colors[i] * 255);\n }\n\n } else if (color) {\n\n const r = color[0]; // Color is pre-quantized by VBOSceneModel\n const g = color[1];\n const b = color[2];\n const a = 1.0;\n\n for (let i = 0; i < numVerts; i++) {\n buffer.colors.push(r);\n buffer.colors.push(g);\n buffer.colors.push(b);\n buffer.colors.push(a);\n }\n }\n\n {\n const pickColorsBase = buffer.pickColors.length;\n const lenPickColors = numVerts * 4;\n for (let i = pickColorsBase, len = pickColorsBase + lenPickColors; i < len; i += 4) {\n buffer.pickColors.push(pickColor[0]);\n buffer.pickColors.push(pickColor[1]);\n buffer.pickColors.push(pickColor[2]);\n buffer.pickColors.push(pickColor[3]);\n }\n }\n\n if (this.model.scene.entityOffsetsEnabled) {\n for (let i = 0; i < numVerts; i++) {\n buffer.offsets.push(0);\n buffer.offsets.push(0);\n buffer.offsets.push(0);\n }\n }\n\n const portionId = this._portions.length / 2;\n\n this._portions.push(vertsIndex);\n this._portions.push(numVerts);\n\n this._numPortions++;\n this.model.numPortions++;\n this._meshes.push(mesh);\n return portionId;\n }\n\n /**\n * Builds batch VBOs from appended geometries.\n * No more portions can then be created.\n */\n finalize() {\n\n if (this._finalized) {\n return;\n }\n\n const state = this._state;\n const gl = this.model.scene.canvas.gl;\n const buffer = this._buffer;\n\n if (buffer.positions.length > 0) {\n if (this._preCompressedPositionsExpected) {\n const positions = new Uint16Array(buffer.positions);\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, buffer.positions.length, 3, gl.STATIC_DRAW);\n } else {\n const positions = new Float32Array(buffer.positions);\n const quantizedPositions = quantizePositions(positions, this._modelAABB, state.positionsDecodeMatrix);\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, quantizedPositions, buffer.positions.length, 3, gl.STATIC_DRAW);\n }\n }\n\n if (buffer.colors.length > 0) {\n const colors = new Uint8Array(buffer.colors);\n let normalized = false;\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colors, buffer.colors.length, 4, gl.STATIC_DRAW, normalized);\n }\n\n if (buffer.positions.length > 0) { // Because we build flags arrays here, get their length from the positions array\n const flagsLength = buffer.positions.length / 3;\n const flags = new Float32Array(flagsLength);\n let notNormalized = false;\n state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, flags, flags.length, 1, gl.DYNAMIC_DRAW, notNormalized);\n }\n\n if (buffer.pickColors.length > 0) {\n const pickColors = new Uint8Array(buffer.pickColors);\n let normalized = false;\n state.pickColorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, pickColors, buffer.pickColors.length, 4, gl.STATIC_DRAW, normalized);\n }\n\n if (this.model.scene.entityOffsetsEnabled) {\n if (buffer.offsets.length > 0) {\n const offsets = new Float32Array(buffer.offsets);\n state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, offsets, buffer.offsets.length, 3, gl.DYNAMIC_DRAW);\n }\n }\n\n this._buffer = null;\n this._finalized = true;\n }\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setVisible(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setHighlighted(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setXRayed(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setSelected(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setEdges(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n // Not applicable to point clouds\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags(portionId, flags);\n }\n\n setCulled(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions--;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setColor(portionId, color) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n const portionsIdx = portionId * 2;\n const vertexBase = this._portions[portionsIdx];\n const numVerts = this._portions[portionsIdx + 1];\n const firstColor = vertexBase * 4;\n const lenColor = numVerts * 4;\n const tempArray = this._scratchMemory.getUInt8Array(lenColor);\n const r = color[0];\n const g = color[1];\n const b = color[2];\n for (let i = 0; i < lenColor; i += 4) {\n tempArray[i + 0] = r;\n tempArray[i + 1] = g;\n tempArray[i + 2] = b;\n }\n this._state.colorsBuf.setData(tempArray, firstColor, lenColor);\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n /**\n * flags are 4bits values encoded on a 32bit base. color flag on the first 4 bits, silhouette flag on the next 4 bits and so on for edge, pick and clippable.\n */\n _setFlags(portionId, flags, transparent) {\n\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const portionsIdx = portionId * 2;\n const vertexBase = this._portions[portionsIdx];\n const numVerts = this._portions[portionsIdx + 1];\n const firstFlag = vertexBase;\n const lenFlags = numVerts;\n const tempArray = this._scratchMemory.getFloat32Array(lenFlags);\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n let colorFlag;\n if (!visible || culled || xrayed\n || (highlighted && !this.model.scene.highlightMaterial.glowThrough)\n || (selected && !this.model.scene.selectedMaterial.glowThrough)) {\n colorFlag = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (transparent) {\n colorFlag = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n colorFlag = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n let silhouetteFlag;\n if (!visible || culled) {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n\n const clippableFlag = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 1 : 0;\n\n for (let i = 0; i < lenFlags; i++) {\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n // no edges\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n tempArray[i] = vertFlag;\n }\n\n this._state.flagsBuf.setData(tempArray, firstFlag);\n }\n\n setOffset(portionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (!this.model.scene.entityOffsetsEnabled) {\n this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n return;\n }\n const portionsIdx = portionId * 2;\n const vertexBase = this._portions[portionsIdx];\n const numVerts = this._portions[portionsIdx + 1];\n const firstOffset = vertexBase * 3;\n const lenOffsets = numVerts * 3;\n const tempArray = this._scratchMemory.getFloat32Array(lenOffsets);\n const x = offset[0];\n const y = offset[1];\n const z = offset[2];\n for (let i = 0; i < lenOffsets; i += 3) {\n tempArray[i + 0] = x;\n tempArray[i + 1] = y;\n tempArray[i + 2] = z;\n }\n this._state.offsetsBuf.setData(tempArray, firstOffset, lenOffsets);\n }\n\n //-- NORMAL RENDERING ----------------------------------------------------------------------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n\n // -- RENDERING SAO POST EFFECT TARGETS ----------------------------------------------------------------------------\n\n drawDepth(renderFlags, frameCtx) {\n }\n\n drawNormals(renderFlags, frameCtx) {\n }\n\n // -- EMPHASIS RENDERING -------------------------------------------------------------------------------------------\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n }\n }\n\n //-- EDGES RENDERING -----------------------------------------------------------------------------------------------\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n }\n\n //---- PICKING ----------------------------------------------------------------------------------------------------\n\n drawPickMesh(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.pickMeshRenderer) {\n this._renderers.pickMeshRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickDepths(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.pickDepthRenderer) {\n this._renderers.pickDepthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickNormals(renderFlags, frameCtx) {\n }\n\n drawSnapInit(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapInitRenderer) {\n this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnap(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapRenderer) {\n this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n //---- OCCLUSION TESTING -------------------------------------------------------------------------------------------\n\n drawOcclusion(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.occlusionRenderer) {\n this._renderers.occlusionRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n //---- SHADOWS -----------------------------------------------------------------------------------------------------\n\n drawShadow(renderFlags, frameCtx) {\n }\n\n destroy() {\n const state = this._state;\n if (state.positionsBuf) {\n state.positionsBuf.destroy();\n state.positionsBuf = null;\n }\n if (state.offsetsBuf) {\n state.offsetsBuf.destroy();\n state.offsetsBuf = null;\n }\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n state.colorsBuf = null;\n }\n if (state.flagsBuf) {\n state.flagsBuf.destroy();\n state.flagsBuf = null;\n }\n if (state.pickColorsBuf) {\n state.pickColorsBuf.destroy();\n state.pickColorsBuf = null;\n }\n state.destroy();\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingPointsRenderer extends VBORenderer {\n constructor(scene, withSAO) {\n super(scene, withSAO, {instancing: true});\n }\n\n _draw(drawCfg) {\n const {gl} = this._scene.canvas;\n\n const {\n state,\n frameCtx,\n incrementDrawState,\n } = drawCfg;\n\n gl.drawArraysInstanced(gl.POINTS, 0, state.positionsBuf.numItems, state.numInstances);\n\n if (incrementDrawState) {\n frameCtx.drawArrays++;\n }\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingPointsColorRenderer extends VBOInstancingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n drawLayer(frameCtx, layer, renderPass) {\n super.drawLayer(frameCtx, layer, renderPass, { incrementDrawState: true });\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points instancing color vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (pointsMaterial.filterIntensity) {\n src.push(\"uniform vec2 intensityRange;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n if (pointsMaterial.filterIntensity) {\n src.push(\"float intensity = float(color.a) / 255.0;\");\n src.push(\"if (intensity < intensityRange[0] || intensity > intensityRange[1]) {\");\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n }\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vColor = vec4(float(color.r) / 255.0, float(color.g) / 255.0, float(color.b) / 255.0, 1.0);\");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n if (pointsMaterial.filterIntensity) {\n src.push(\"}\");\n }\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points instancing color fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n src.push(\" outColor = vColor;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingPointsSilhouetteRenderer extends VBOInstancingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n super.drawLayer(frameCtx, instancingLayer, renderPass, {colorUniform: true});\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points instancing silhouette vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 color;\");\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n src.push(\"uniform vec4 silhouetteColor;\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // silhouetteFlag = NOT_RENDERED | SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n // renderPass = SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n\n src.push(`int silhouetteFlag = int(flags) >> 4 & 0xF;`);\n src.push(`if (silhouetteFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n src.push(\"vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\"worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"vColor = vec4(float(silhouetteColor.r) / 255.0, float(silhouetteColor.g) / 255.0, float(silhouetteColor.b) / 255.0, float(color.a) / 255.0);\");\n src.push(\"gl_Position = clipPos;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points instancing silhouette fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingPointsPickMeshRenderer extends VBOInstancingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points instancing pick mesh vertex shader\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 pickColor;\");\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out vec4 vPickColor;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n\n\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\" vPickColor = vec4(float(pickColor.r) / 255.0, float(pickColor.g) / 255.0, float(pickColor.b) / 255.0, float(pickColor.a) / 255.0);\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points instancing pick mesh fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vPickColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outColor = vPickColor; \");\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\n\n\nclass VBOInstancingPointsPickDepthRenderer extends VBOInstancingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n\n src.push('#version 300 es');\n src.push(\"// Points instancing pick depth vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"in vec3 position;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n this._addRemapClipPosLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"void main(void) {\");\n\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\" vViewPosition = viewPosition;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points instancing pick depth fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n src.push(\"uniform float pickZNear;\");\n src.push(\"uniform float pickZFar;\");\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"vec4 packDepth(const in float depth) {\");\n src.push(\" const vec4 bitShift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);\");\n src.push(\" const vec4 bitMask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);\");\n src.push(\" vec4 res = fract(depth * bitShift);\");\n src.push(\" res -= res.xxyz * bitMask;\");\n src.push(\" return res;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" float zNormalizedDepth = abs((pickZNear + vViewPosition.z) / (pickZFar - pickZNear));\");\n src.push(\" outColor = packDepth(zNormalizedDepth); \"); // Must be linear depth\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingPointsOcclusionRenderer extends VBOInstancingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n\n src.push ('#version 300 es');\n src.push(\"// Points instancing occlusion vertex shader\");\n\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\");\n\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n\n src.push(\"gl_Position = clipPos;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points instancing occlusion vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n src.push(\" outColor = vec4(0.0, 0.0, 1.0, 1.0); \"); // Occluders are blue\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * @private\n */\nclass VBOInstancingPointsDepthRenderer extends VBOInstancingPointsRenderer {\n _getHash() {\n return this._scene._sectionPlanesState.getHash() + this._scene.pointsMaterial.hash;\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const pointsMaterial = scene.pointsMaterial._state;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points instancing depth vertex shader\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\");\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"uniform float nearPlaneHeight;\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n\n // colorFlag = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`int colorFlag = int(flags) & 0xF;`);\n src.push(`if (colorFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n }\n src.push(\"gl_Position = clipPos;\");\n if (pointsMaterial.perspectivePoints) {\n src.push(\"gl_PointSize = (nearPlaneHeight * pointSize) / clipPos.w;\");\n src.push(\"gl_PointSize = max(gl_PointSize, \" + Math.floor(pointsMaterial.minPerspectivePointSize) + \".0);\");\n src.push(\"gl_PointSize = min(gl_PointSize, \" + Math.floor(pointsMaterial.maxPerspectivePointSize) + \".0);\");\n } else {\n src.push(\"gl_PointSize = pointSize;\");\n }\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n let i;\n let len;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Points instancing depth vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n\n src.push(\"const float packUpScale = 256. / 255.;\");\n src.push(\"const float unpackDownscale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unpackFactors = unpackDownscale / vec4( packFactors, 1. );\");\n src.push(\"const float shiftRight8 = 1.0 / 256.;\");\n\n src.push(\"vec4 packDepthToRGBA( const in float v ) {\");\n src.push(\" vec4 r = vec4( fract( v * packFactors ), v );\");\n src.push(\" r.yzw -= r.xyz * shiftRight8;\");\n src.push(\" return r * packUpScale;\");\n src.push(\"}\");\n\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n\n if (scene.pointsMaterial.roundPoints) {\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n }\n\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n src.push(\" outColor = packDepthToRGBA( gl_FragCoord.z); \"); // Must be linear depth\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"}\");\n return src;\n }\n}\n\n/**\n * Renders InstancingLayer fragment depths to a shadow map.\n *\n * @private\n */\nclass VBOInstancingPointsShadowRenderer extends VBOInstancingPointsRenderer {\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Instancing geometry shadow drawing vertex shader\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in vec4 color;\");\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\");\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform mat4 shadowViewMatrix;\");\n src.push(\"uniform mat4 shadowProjMatrix;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform float pointSize;\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"void main(void) {\");\n src.push(\"int colorFlag = int(flags) & 0xF;\");\n src.push(\"bool visible = (colorFlag > 0);\");\n src.push(\"bool transparent = ((float(color.a) / 255.0) < 1.0);\");\n src.push(`if (!visible || transparent) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = shadowViewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags = flags;\");\n }\n src.push(\" gl_Position = shadowProjMatrix * viewPosition;\");\n src.push(\"}\");\n src.push(\"gl_PointSize = pointSize;\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Instancing geometry depth drawing fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec3 vViewNormal;\");\n src.push(\"vec3 packNormalToRGB( const in vec3 normal ) {\");\n src.push(\" return normalize( normal ) * 0.5 + 0.5;\");\n src.push(\"}\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n src.push(\" vec2 cxy = 2.0 * gl_PointCoord - 1.0;\");\n src.push(\" float r = dot(cxy, cxy);\");\n src.push(\" if (r > 1.0) {\");\n src.push(\" discard;\");\n src.push(\" }\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vec4(packNormalToRGB(vViewNormal), 1.0); \");\n src.push(\"}\");\n return src;\n }\n}\n\nconst tempVec3a$p = math.vec3();\nconst tempVec3b$l = math.vec3();\nconst tempVec3c$g = math.vec3();\nmath.vec3();\nconst tempMat4a$g = math.mat4();\n\n/**\n * @private\n */\nclass VBOInstancingPointsSnapInitRenderer extends VBORenderer {\n\n constructor(scene) {\n super(scene, false, { instancing: true });\n }\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = instancingLayer.model;\n const scene = model.scene;\n const gl = scene.canvas.gl;\n const camera = scene.camera;\n const state = instancingLayer._state;\n const origin = instancingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = instancingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(instancingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(instancingLayer));\n } else {\n this._vaoCache.set(instancingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$p;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$l;\n if (origin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$g);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$g);\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(instancingLayer);\n\n this._aModelMatrixCol0.bindArrayBuffer(state.modelMatrixCol0Buf);\n this._aModelMatrixCol1.bindArrayBuffer(state.modelMatrixCol1Buf);\n this._aModelMatrixCol2.bindArrayBuffer(state.modelMatrixCol2Buf);\n\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 1);\n\n\n if (this._aFlags) {\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n gl.vertexAttribDivisor(this._aFlags.location, 1);\n }\n\n gl.drawArraysInstanced(gl.POINTS, 0, state.positionsBuf.numItems, state.numInstances);\n\n // Cleanup\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 0);\n if (this._aFlags) {\n gl.vertexAttribDivisor(this._aFlags.location, 0);\n }\n if (this._aOffset) {\n gl.vertexAttribDivisor(this._aOffset.location, 0);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\");\n this._uLayerNumber = program.getLocation(\"layerNumber\");\n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthBufInitRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec4 pickColor;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec2 snapVectorA;\");\n src.push(\"uniform vec2 snapInvVectorAB;\");\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n src.push(\"flat out vec4 vPickColor;\");\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vPickColor = pickColor;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// Points instancing pick depth fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\");\n src.push(\"uniform vec3 coordinateScaler;\");\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in vec4 vPickColor;\");\n if (clipping) {\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"layout(location = 0) out highp ivec4 outCoords;\");\n src.push(\"layout(location = 1) out highp ivec4 outNormal;\");\n src.push(\"layout(location = 2) out lowp uvec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n {\n src.push(\" float dx = dFdx(vFragDepth);\");\n src.push(\" float dy = dFdy(vFragDepth);\");\n src.push(\" float diff = sqrt(dx*dx+dy*dy);\");\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth + diff ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, -layerNumber);\");\n // src.push(\"vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n // src.push(\"vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n // src.push(\"vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(`outNormal = ivec4(1.0, 1.0, 1.0, 1.0);`);\n src.push(\"outPickColor = uvec4(vPickColor);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$o = math.vec3();\nconst tempVec3b$k = math.vec3();\nconst tempVec3c$f = math.vec3();\nmath.vec3();\nconst tempMat4a$f = math.mat4();\n\n/**\n * @private\n */\nclass VBOInstancingPointsSnapRenderer extends VBORenderer {\n\n constructor(scene) {\n super(scene, false, { instancing: true });\n }\n\n drawLayer(frameCtx, instancingLayer, renderPass) {\n\n if (!this._program) {\n this._allocate(instancingLayer);\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = instancingLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = instancingLayer._state;\n const origin = instancingLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = instancingLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (this._vaoCache.has(instancingLayer)) {\n gl.bindVertexArray(this._vaoCache.get(instancingLayer));\n } else {\n this._vaoCache.set(instancingLayer, this._makeVAO(state));\n }\n\n const coordinateScaler = tempVec3a$o;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n let rtcViewMatrix;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3b$k;\n if (origin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$f);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$f);\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n\n let offset = 0;\n const mat4Size = 4 * 4;\n\n this._matricesUniformBlockBufferData.set(rotationMatrixConjugate, 0);\n this._matricesUniformBlockBufferData.set(rtcViewMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(camera.projMatrix, offset += mat4Size);\n this._matricesUniformBlockBufferData.set(state.positionsDecodeMatrix, offset += mat4Size);\n\n gl.bindBuffer(gl.UNIFORM_BUFFER, this._matricesUniformBlockBuffer);\n gl.bufferData(gl.UNIFORM_BUFFER, this._matricesUniformBlockBufferData, gl.DYNAMIC_DRAW);\n\n gl.bindBufferBase(\n gl.UNIFORM_BUFFER,\n this._matricesUniformBlockBufferBindingPoint,\n this._matricesUniformBlockBuffer);\n\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n this.setSectionPlanesStateUniforms(instancingLayer);\n\n\n this._aModelMatrixCol0.bindArrayBuffer(state.modelMatrixCol0Buf);\n this._aModelMatrixCol1.bindArrayBuffer(state.modelMatrixCol1Buf);\n this._aModelMatrixCol2.bindArrayBuffer(state.modelMatrixCol2Buf);\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 1);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 1);\n\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n gl.vertexAttribDivisor(this._aFlags.location, 1);\n\n gl.drawArraysInstanced(gl.POINTS, 0, state.positionsBuf.numItems, state.numInstances);\n\n // Cleanup\n gl.vertexAttribDivisor(this._aModelMatrixCol0.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol1.location, 0);\n gl.vertexAttribDivisor(this._aModelMatrixCol2.location, 0);\n gl.vertexAttribDivisor(this._aFlags.location, 0);\n if (this._aOffset) {\n gl.vertexAttribDivisor(this._aOffset.location, 0);\n }\n }\n\n _allocate() {\n super._allocate();\n\n const program = this._program;\n\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uVectorA = program.getLocation(\"snapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"snapInvVectorAB\"); \n this._uLayerNumber = program.getLocation(\"layerNumber\"); \n this._uCoordinateScaler = program.getLocation(\"coordinateScaler\"); \n }\n\n _bindProgram() {\n this._program.bind();\n\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n src.push(\"uniform int renderPass;\");\n src.push(\"in vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n src.push(\"in float flags;\");\n src.push(\"in vec4 modelMatrixCol0;\"); // Modeling matrix\n src.push(\"in vec4 modelMatrixCol1;\");\n src.push(\"in vec4 modelMatrixCol2;\");\n src.push(\"uniform bool pickInvisible;\");\n\n this._addMatricesUniformBlockLines(src);\n\n src.push(\"uniform vec2 snapVectorA;\"); \n src.push(\"uniform vec2 snapInvVectorAB;\"); \n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - snapVectorA.x) * snapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - snapVectorA.y) * snapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out float vFlags;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n // pickFlag = NOT_RENDERED | PICK\n // renderPass = PICK\n src.push(`int pickFlag = int(flags) >> 12 & 0xF;`);\n src.push(`if (pickFlag != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n src.push(\"} else {\");\n src.push(\" vec4 worldPosition = positionsDecodeMatrix * vec4(position, 1.0); \");\n src.push(\" worldPosition = worldMatrix * vec4(dot(worldPosition, modelMatrixCol0), dot(worldPosition, modelMatrixCol1), dot(worldPosition, modelMatrixCol2), 1.0);\");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags = flags;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"gl_PointSize = 1.0;\"); // Windows needs this?\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push ('#version 300 es');\n src.push(\"// SnapInstancingDepthRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int layerNumber;\"); \n src.push(\"uniform vec3 coordinateScaler;\"); \n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in float vFlags;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"out highp ivec4 outCoords;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (int(vFlags) >> 16 & 0xF) == 1;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\"if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz*coordinateScaler.xyz, layerNumber);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\n class VBOInstancingPointsRenderers {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n if (this._depthRenderer && (!this._depthRenderer.getValid())) {\n this._depthRenderer.destroy();\n this._depthRenderer = null;\n }\n if (this._silhouetteRenderer && (!this._silhouetteRenderer.getValid())) {\n this._silhouetteRenderer.destroy();\n this._silhouetteRenderer = null;\n }\n if (this._pickMeshRenderer && (!this._pickMeshRenderer.getValid())) {\n this._pickMeshRenderer.destroy();\n this._pickMeshRenderer = null;\n }\n if (this._pickDepthRenderer && (!this._pickDepthRenderer.getValid())) {\n this._pickDepthRenderer.destroy();\n this._pickDepthRenderer = null;\n }\n if (this._occlusionRenderer && this._occlusionRenderer.getValid() === false) {\n this._occlusionRenderer.destroy();\n this._occlusionRenderer = null;\n }\n if (this._shadowRenderer && (!this._shadowRenderer.getValid())) {\n this._shadowRenderer.destroy();\n this._shadowRenderer = null;\n }\n if (this._snapInitRenderer && (!this._snapInitRenderer.getValid())) {\n this._snapInitRenderer.destroy();\n this._snapInitRenderer = null;\n }\n if (this._snapRenderer && (!this._snapRenderer.getValid())) {\n this._snapRenderer.destroy();\n this._snapRenderer = null;\n }\n }\n\n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new VBOInstancingPointsColorRenderer(this._scene, false);\n }\n return this._colorRenderer;\n }\n\n get silhouetteRenderer() {\n if (!this._silhouetteRenderer) {\n this._silhouetteRenderer = new VBOInstancingPointsSilhouetteRenderer(this._scene);\n }\n return this._silhouetteRenderer;\n }\n\n get depthRenderer() {\n if (!this._depthRenderer) {\n this._depthRenderer = new VBOInstancingPointsDepthRenderer(this._scene);\n }\n return this._depthRenderer;\n }\n\n get pickMeshRenderer() {\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new VBOInstancingPointsPickMeshRenderer(this._scene);\n }\n return this._pickMeshRenderer;\n }\n\n get pickDepthRenderer() {\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new VBOInstancingPointsPickDepthRenderer(this._scene);\n }\n return this._pickDepthRenderer;\n }\n\n get occlusionRenderer() {\n if (!this._occlusionRenderer) {\n this._occlusionRenderer = new VBOInstancingPointsOcclusionRenderer(this._scene);\n }\n return this._occlusionRenderer;\n }\n\n get shadowRenderer() {\n if (!this._shadowRenderer) {\n this._shadowRenderer = new VBOInstancingPointsShadowRenderer(this._scene);\n }\n return this._shadowRenderer;\n }\n\n get snapInitRenderer() {\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new VBOInstancingPointsSnapInitRenderer(this._scene, false);\n }\n return this._snapInitRenderer;\n }\n\n get snapRenderer() {\n if (!this._snapRenderer) {\n this._snapRenderer = new VBOInstancingPointsSnapRenderer(this._scene);\n }\n return this._snapRenderer;\n }\n\n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n if (this._depthRenderer) {\n this._depthRenderer.destroy();\n }\n if (this._silhouetteRenderer) {\n this._silhouetteRenderer.destroy();\n }\n if (this._pickMeshRenderer) {\n this._pickMeshRenderer.destroy();\n }\n if (this._pickDepthRenderer) {\n this._pickDepthRenderer.destroy();\n }\n if (this._occlusionRenderer) {\n this._occlusionRenderer.destroy();\n }\n if (this._shadowRenderer) {\n this._shadowRenderer.destroy();\n }\n if (this._snapInitRenderer) {\n this._snapInitRenderer.destroy();\n }\n if (this._snapRenderer) {\n this._snapRenderer.destroy();\n }\n }\n}\n\nconst cachedRenderers$1 = {};\n\n/**\n * @private\n */\nfunction getRenderers$2(scene) {\n const sceneId = scene.id;\n let instancingRenderers = cachedRenderers$1[sceneId];\n if (!instancingRenderers) {\n instancingRenderers = new VBOInstancingPointsRenderers(scene);\n cachedRenderers$1[sceneId] = instancingRenderers;\n instancingRenderers._compile();\n scene.on(\"compile\", () => {\n instancingRenderers._compile();\n });\n scene.on(\"destroyed\", () => {\n delete cachedRenderers$1[sceneId];\n instancingRenderers._destroy();\n });\n }\n return instancingRenderers;\n}\n\nconst tempUint8Vec4 = new Uint8Array(4);\nconst tempFloat32 = new Float32Array(1);\nconst tempVec3fa = new Float32Array(3);\n\nconst tempFloat32Vec4 = new Float32Array(4);\n\n/**\n * @private\n */\nclass VBOInstancingPointsLayer {\n\n /**\n * @param cfg\n * @param cfg.layerIndex\n * @param cfg.model\n * @param cfg.geometry\n * @param cfg.material\n * @param cfg.origin\n */\n constructor(cfg) {\n\n console.info(\"VBOInstancingPointsLayer\");\n\n /**\n * Owner model\n * @type {VBOSceneModel}\n */\n this.model = cfg.model;\n\n /**\n * Shared material\n * @type {VBOSceneModelGeometry}\n */\n this.material = cfg.material;\n\n /**\n * State sorting key.\n * @type {string}\n */\n this.sortId = \"PointsInstancingLayer\";\n\n /**\n * Index of this InstancingLayer in VBOSceneModel#_layerList\n * @type {Number}\n */\n this.layerIndex = cfg.layerIndex;\n\n this._renderers = getRenderers$2(cfg.model.scene);\n this._aabb = math.collapseAABB3();\n\n this._state = new RenderState({\n obb: math.OBB3(),\n numInstances: 0,\n origin: cfg.origin ? math.vec3(cfg.origin) : null,\n geometry: cfg.geometry,\n positionsDecodeMatrix: cfg.geometry.positionsDecodeMatrix, // So we can null the geometry for GC\n colorsBuf: null,\n flagsBuf: null,\n offsetsBuf: null,\n modelMatrixCol0Buf: null,\n modelMatrixCol1Buf: null,\n modelMatrixCol2Buf: null,\n pickColorsBuf: null\n });\n\n // These counts are used to avoid unnecessary render passes\n this._numPortions = 0;\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numEdgesLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n /** @private */\n this.numIndices = cfg.geometry.numIndices;\n\n // Per-instance arrays\n this._pickColors = [];\n this._offsets = [];\n\n // Modeling matrix per instance, array for each column\n this._modelMatrixCol0 = [];\n this._modelMatrixCol1 = [];\n this._modelMatrixCol2 = [];\n\n this._portions = [];\n this._meshes = [];\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n\n this._finalized = false;\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Creates a new portion within this InstancingLayer, returns the new portion ID.\n *\n * The portion will instance this InstancingLayer's geometry.\n *\n * Gives the portion the specified color and matrix.\n *\n * @param mesh The SceneModelMesh that owns the portion\n * @param cfg Portion params\n * @param cfg.meshMatrix Flat float 4x4 matrix.\n * @param [cfg.worldMatrix] Flat float 4x4 matrix.\n * @param cfg.pickColor Quantized pick color\n * @returns {number} Portion ID.\n */\n createPortion(mesh, cfg) {\n\n const meshMatrix = cfg.meshMatrix;\n const pickColor = cfg.pickColor;\n\n if (this._finalized) {\n throw \"Already finalized\";\n }\n\n if (this.model.scene.entityOffsetsEnabled) {\n this._offsets.push(0);\n this._offsets.push(0);\n this._offsets.push(0);\n }\n\n this._modelMatrixCol0.push(meshMatrix[0]);\n this._modelMatrixCol0.push(meshMatrix[4]);\n this._modelMatrixCol0.push(meshMatrix[8]);\n this._modelMatrixCol0.push(meshMatrix[12]);\n\n this._modelMatrixCol1.push(meshMatrix[1]);\n this._modelMatrixCol1.push(meshMatrix[5]);\n this._modelMatrixCol1.push(meshMatrix[9]);\n this._modelMatrixCol1.push(meshMatrix[13]);\n\n this._modelMatrixCol2.push(meshMatrix[2]);\n this._modelMatrixCol2.push(meshMatrix[6]);\n this._modelMatrixCol2.push(meshMatrix[10]);\n this._modelMatrixCol2.push(meshMatrix[14]);\n\n // Per-instance pick colors\n\n this._pickColors.push(pickColor[0]);\n this._pickColors.push(pickColor[1]);\n this._pickColors.push(pickColor[2]);\n this._pickColors.push(pickColor[3]);\n\n this._state.numInstances++;\n\n const portionId = this._portions.length;\n this._portions.push({});\n\n this._numPortions++;\n this.model.numPortions++;\n this._meshes.push(mesh);\n return portionId;\n }\n\n finalize() {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n const gl = this.model.scene.canvas.gl;\n const flagsLength = this._pickColors.length / 4;\n const state = this._state;\n const geometry = state.geometry;\n if (flagsLength > 0) {\n // Because we only build flags arrays here, \n // get their length from the colors array\n let notNormalized = false;\n state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(flagsLength), flagsLength, 1, gl.DYNAMIC_DRAW, notNormalized);\n }\n if (this.model.scene.entityOffsetsEnabled) {\n if (this._offsets.length > 0) {\n const notNormalized = false;\n state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._offsets), this._offsets.length, 3, gl.DYNAMIC_DRAW, notNormalized);\n this._offsets = []; // Release memory\n }\n }\n if (geometry.positionsCompressed && geometry.positionsCompressed.length > 0) {\n const normalized = false;\n state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, geometry.positionsCompressed, geometry.positionsCompressed.length, 3, gl.STATIC_DRAW, normalized);\n state.positionsDecodeMatrix = math.mat4(geometry.positionsDecodeMatrix);\n }\n if (geometry.colorsCompressed && geometry.colorsCompressed.length > 0) {\n const colorsCompressed = new Uint8Array(geometry.colorsCompressed);\n const notNormalized = false;\n state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colorsCompressed, colorsCompressed.length, 4, gl.STATIC_DRAW, notNormalized);\n }\n if (this._modelMatrixCol0.length > 0) {\n const normalized = false;\n state.modelMatrixCol0Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol0), this._modelMatrixCol0.length, 4, gl.STATIC_DRAW, normalized);\n state.modelMatrixCol1Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol1), this._modelMatrixCol1.length, 4, gl.STATIC_DRAW, normalized);\n state.modelMatrixCol2Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol2), this._modelMatrixCol2.length, 4, gl.STATIC_DRAW, normalized);\n this._modelMatrixCol0 = [];\n this._modelMatrixCol1 = [];\n this._modelMatrixCol2 = [];\n }\n if (this._pickColors.length > 0) {\n const normalized = false;\n state.pickColorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Uint8Array(this._pickColors), this._pickColors.length, 4, gl.STATIC_DRAW, normalized);\n this._pickColors = []; // Release memory\n }\n state.geometry = null;\n this._finalized = true;\n }\n\n // The following setters are called by VBOSceneModelMesh, in turn called by VBOSceneModelNode, only after the layer is finalized.\n // It's important that these are called after finalize() in order to maintain integrity of counts like _numVisibleLayerPortions etc.\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setVisible(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setHighlighted(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setXRayed(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setSelected(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setEdges(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n } else {\n this._numEdgesLayerPortions--;\n this.model.numEdgesLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags(portionId, flags);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setCulled(portionId, flags, meshTransparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions--;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, meshTransparent);\n }\n\n setColor(portionId, color) { // RGBA color is normalized as ints\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n tempUint8Vec4[0] = color[0];\n tempUint8Vec4[1] = color[1];\n tempUint8Vec4[2] = color[2];\n this._state.colorsBuf.setData(tempUint8Vec4, portionId * 3);\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n // setMatrix(portionId, matrix) {\n\n ////////////////////////////////////////\n // TODO: Update portion matrix\n ////////////////////////////////////////\n //\n // if (!this._finalized) {\n // throw \"Not finalized\";\n // }\n //\n // var offset = portionId * 4;\n //\n // tempFloat32Vec4[0] = matrix[0];\n // tempFloat32Vec4[1] = matrix[4];\n // tempFloat32Vec4[2] = matrix[8];\n // tempFloat32Vec4[3] = matrix[12];\n //\n // this._state.modelMatrixCol0Buf.setData(tempFloat32Vec4, offset);\n //\n // tempFloat32Vec4[0] = matrix[1];\n // tempFloat32Vec4[1] = matrix[5];\n // tempFloat32Vec4[2] = matrix[9];\n // tempFloat32Vec4[3] = matrix[13];\n //\n // this._state.modelMatrixCol1Buf.setData(tempFloat32Vec4, offset);\n //\n // tempFloat32Vec4[0] = matrix[2];\n // tempFloat32Vec4[1] = matrix[6];\n // tempFloat32Vec4[2] = matrix[10];\n // tempFloat32Vec4[3] = matrix[14];\n //\n // this._state.modelMatrixCol2Buf.setData(tempFloat32Vec4, offset);\n // }\n\n /**\n * flags are 4bits values encoded on a 32bit base. color flag on the first 4 bits, silhouette flag on the next 4 bits and so on for edge, pick and clippable.\n */\n _setFlags(portionId, flags, meshTransparent) {\n\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n const edges = !!(flags & ENTITY_FLAGS.EDGES);\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n let colorFlag;\n if (!visible || culled || xrayed\n || (highlighted && !this.model.scene.highlightMaterial.glowThrough)\n || (selected && !this.model.scene.selectedMaterial.glowThrough)) {\n colorFlag = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (meshTransparent) {\n colorFlag = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n colorFlag = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n let silhouetteFlag;\n if (!visible || culled) {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n silhouetteFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let edgeFlag = 0;\n if (!visible || culled) {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n edgeFlag = RENDER_PASSES.EDGES_SELECTED;\n } else if (highlighted) {\n edgeFlag = RENDER_PASSES.EDGES_HIGHLIGHTED;\n } else if (xrayed) {\n edgeFlag = RENDER_PASSES.EDGES_XRAYED;\n } else if (edges) {\n if (meshTransparent) {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;\n } else {\n edgeFlag = RENDER_PASSES.EDGES_COLOR_OPAQUE;\n }\n } else {\n edgeFlag = RENDER_PASSES.NOT_RENDERED;\n }\n\n let pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n\n const clippableFlag = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 255 : 0;\n\n let vertFlag = 0;\n vertFlag |= colorFlag;\n vertFlag |= silhouetteFlag << 4;\n vertFlag |= edgeFlag << 8;\n vertFlag |= pickFlag << 12;\n vertFlag |= clippableFlag << 16;\n\n tempFloat32[0] = vertFlag;\n\n this._state.flagsBuf.setData(tempFloat32, portionId);\n }\n\n setOffset(portionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (!this.model.scene.entityOffsetsEnabled) {\n this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n return;\n }\n tempVec3fa[0] = offset[0];\n tempVec3fa[1] = offset[1];\n tempVec3fa[2] = offset[2];\n this._state.offsetsBuf.setData(tempVec3fa, portionId * 3);\n }\n\n setMatrix(portionId, matrix) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n const offset = portionId * 4;\n\n tempFloat32Vec4[0] = matrix[0];\n tempFloat32Vec4[1] = matrix[4];\n tempFloat32Vec4[2] = matrix[8];\n tempFloat32Vec4[3] = matrix[12];\n\n this._state.modelMatrixCol0Buf.setData(tempFloat32Vec4, offset);\n\n tempFloat32Vec4[0] = matrix[1];\n tempFloat32Vec4[1] = matrix[5];\n tempFloat32Vec4[2] = matrix[9];\n tempFloat32Vec4[3] = matrix[13];\n\n this._state.modelMatrixCol1Buf.setData(tempFloat32Vec4, offset);\n\n tempFloat32Vec4[0] = matrix[2];\n tempFloat32Vec4[1] = matrix[6];\n tempFloat32Vec4[2] = matrix[10];\n tempFloat32Vec4[3] = matrix[14];\n\n this._state.modelMatrixCol2Buf.setData(tempFloat32Vec4, offset);\n }\n\n // ---------------------- NORMAL RENDERING -----------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n\n // -- RENDERING SAO POST EFFECT TARGETS ----------------------------------------------------------------------------\n\n drawDepth(renderFlags, frameCtx) {\n }\n\n drawNormals(renderFlags, frameCtx) {\n }\n\n // ---------------------- EMPHASIS RENDERING -----------------------------------\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n }\n }\n\n //-- EDGES RENDERING -----------------------------------------------------------------------------------------------\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n }\n\n // ---------------------- OCCLUSION CULL RENDERING -----------------------------------\n\n drawOcclusion(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.occlusionRenderer) {\n // Only opaque, filled objects can be occluders\n this._renderers.occlusionRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n // ---------------------- SHADOW BUFFER RENDERING -----------------------------------\n\n drawShadow(renderFlags, frameCtx) {\n }\n\n //---- PICKING ----------------------------------------------------------------------------------------------------\n\n drawPickMesh(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.pickMeshRenderer) {\n this._renderers.pickMeshRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickDepths(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.pickDepthRenderer) {\n this._renderers.pickDepthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickNormals(renderFlags, frameCtx) {\n }\n\n drawSnapInit(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapInitRenderer) {\n this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnap(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n if (this._renderers.snapRenderer) {\n this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n destroy() {\n const state = this._state;\n if (state.colorsBuf) {\n state.colorsBuf.destroy();\n state.colorsBuf = null;\n }\n if (state.flagsBuf) {\n state.flagsBuf.destroy();\n state.flagsBuf = null;\n }\n if (state.offsetsBuf) {\n state.offsetsBuf.destroy();\n state.offsetsBuf = null;\n }\n if (state.modelMatrixCol0Buf) {\n state.modelMatrixCol0Buf.destroy();\n state.modelMatrixCol0Buf = null;\n }\n if (state.modelMatrixCol1Buf) {\n state.modelMatrixCol1Buf.destroy();\n state.modelMatrixCol1Buf = null;\n }\n if (state.modelMatrixCol2Buf) {\n state.modelMatrixCol2Buf.destroy();\n state.modelMatrixCol2Buf = null;\n }\n if (state.pickColorsBuf) {\n state.pickColorsBuf.destroy();\n state.pickColorsBuf = null;\n }\n state.destroy();\n }\n}\n\nconst tempVec3a$n = math.vec3();\nconst tempVec3b$j = math.vec3();\nconst tempMat4a$e = math.mat4();\n\n/**\n * @private\n */\nclass DTXLinesColorRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const scene = this._scene;\n const camera = scene.camera;\n const model = dataTextureLayer.model;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const viewMatrix = camera.viewMatrix;\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx, state);\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uPerObjectDecodeMatrix,\n this._uPerVertexPosition,\n this.uPerObjectColorAndFlags,\n this._uPerObjectMatrix\n );\n\n let rtcViewMatrix;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$n;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3b$j);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$e);\n } else {\n rtcViewMatrix = viewMatrix;\n }\n\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n gl.uniform1i(this._uRenderPass, renderPass);\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$n);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numIndices8Bits > 0) {\n textureState.bindLineIndicesTextures(\n this._program,\n this._uPerLineObject,\n this._uPerLineIndices,\n 8 // 8 bits indices\n );\n gl.drawArrays(gl.LINES, 0, state.numIndices8Bits);\n }\n\n if (state.numIndices16Bits > 0) {\n textureState.bindLineIndicesTextures(\n this._program,\n this._uPerLineObject,\n this._uPerLineIndices,\n 16 // 16 bits indices\n );\n gl.drawArrays(gl.LINES, 0, state.numIndices16Bits);\n }\n\n if (state.numIndices32Bits > 0) {\n textureState.bindLineIndicesTextures(\n this._program,\n this._uPerLineObject,\n this._uPerLineIndices,\n 32 // 32 bits indices\n );\n gl.drawArrays(gl.LINES, 0, state.numIndices32Bits);\n }\n\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n console.error(this.errors);\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uPerObjectDecodeMatrix = \"uPerObjectDecodeMatrix\";\n this.uPerObjectColorAndFlags = \"uPerObjectColorAndFlags\";\n this._uPerVertexPosition = \"uPerVertexPosition\";\n this._uPerLineIndices = \"uPerLineIndices\";\n this._uPerLineObject = \"uPerLineObject\";\n this._uPerObjectMatrix= \"uPerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram(frameCtx) {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const project = scene.camera.project;\n program.bind();\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// LinesDataTextureColorRenderer\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) ;\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uPerObjectDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uPerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uPerObjectColorAndFlags;\");\n src.push(\"uniform mediump usampler2D uPerVertexPosition;\");\n src.push(\"uniform highp usampler2D uPerLineIndices;\");\n src.push(\"uniform mediump usampler2D uPerLineObject;\");\n\n // src.push(\"uniform vec4 color;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n src.push(\"out vec4 vColor;\");\n src.push(\"void main(void) {\");\n\n src.push(\" int lineIndex = gl_VertexID / 2;\");\n\n src.push(\" int h_packed_object_id_index = (lineIndex >> 3) & 4095;\");\n src.push(\" int v_packed_object_id_index = (lineIndex >> 3) >> 12;\");\n\n src.push(\" int objectIndex = int(texelFetch(uPerLineObject, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n\n src.push(\" ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n src.push(\" uvec4 flags = texelFetch (uPerObjectColorAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\" uvec4 flags2 = texelFetch (uPerObjectColorAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.x = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(` if (int(flags.x) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\" } else {\");\n\n src.push(\" ivec4 packedVertexBase = ivec4(texelFetch (uPerObjectColorAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n src.push(\" ivec4 packedLineIndexBaseOffset = ivec4(texelFetch (uPerObjectColorAndFlags, ivec2(objectIndexCoords.x*8+6, objectIndexCoords.y), 0));\");\n src.push(\" int lineIndexBaseOffset = (packedLineIndexBaseOffset.r << 24) + (packedLineIndexBaseOffset.g << 16) + (packedLineIndexBaseOffset.b << 8) + packedLineIndexBaseOffset.a;\");\n src.push(\" int h_index = (lineIndex - lineIndexBaseOffset) & 4095;\");\n src.push(\" int v_index = (lineIndex - lineIndexBaseOffset) >> 12;\");\n src.push(\" ivec3 vertexIndices = ivec3(texelFetch(uPerLineIndices, ivec2(h_index, v_index), 0));\");\n src.push(\" ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\" int indexPositionH = uniqueVertexIndexes[gl_VertexID % 2] & 4095;\");\n src.push(\" int indexPositionV = uniqueVertexIndexes[gl_VertexID % 2] >> 12;\");\n\n src.push(\" mat4 objectInstanceMatrix = mat4 (texelFetch (uPerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uPerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uPerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uPerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n src.push(\" mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uPerObjectDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uPerObjectDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uPerObjectDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uPerObjectDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\" uvec4 flags = texelFetch (uPerObjectColorAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\" uvec4 flags2 = texelFetch (uPerObjectColorAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n src.push(\" vec3 position = vec3(texelFetch(uPerVertexPosition, ivec2(indexPositionH, indexPositionV), 0));\");\n\n src.push(\" uvec4 color = texelFetch (uPerObjectColorAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n src.push(` if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\" };\");\n src.push(\" vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags2 = flags2.r;\");\n }\n src.push(\" vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" vFragDepth = 1.0 + clipPos.w;\");\n src.push(\" isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\" gl_Position = clipPos;\");\n src.push(\" vec4 rgb = vec4(color.rgba);\");\n src.push(\" vColor = vec4(float(rgb.r*0.5) / 255.0, float(rgb.g*0.5) / 255.0, float(rgb.b*0.5) / 255.0, float(rgb.a) / 255.0);\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// LinesDataTextureColorRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\nclass DTXLinesRenderers {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n \n }\n\n eagerCreateRenders() {\n\n // Pre-initialize certain renderers that would otherwise be lazy-initialised\n // on user interaction, such as picking or emphasis, so that there is no delay\n // when user first begins interacting with the viewer.\n \n }\n \n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new DTXLinesColorRenderer(this._scene, false);\n }\n return this._colorRenderer;\n }\n \n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n }\n}\n\nconst cachedRenderers = {};\n\n/**\n * @private\n */\nfunction getRenderers$1(scene) {\n const sceneId = scene.id;\n let dataTextureRenderers = cachedRenderers[sceneId];\n if (!dataTextureRenderers) {\n dataTextureRenderers = new DTXLinesRenderers(scene);\n cachedRenderers[sceneId] = dataTextureRenderers;\n dataTextureRenderers._compile();\n dataTextureRenderers.eagerCreateRenders();\n scene.on(\"compile\", () => {\n dataTextureRenderers._compile();\n dataTextureRenderers.eagerCreateRenders();\n });\n scene.on(\"destroyed\", () => {\n delete cachedRenderers[sceneId];\n dataTextureRenderers._destroy();\n });\n }\n return dataTextureRenderers;\n}\n\n/**\n * @private\n */\nclass DTXLinesBuffer {\n\n constructor() {\n this.positionsCompressed = [];\n this.lenPositionsCompressed = 0;\n this.indices8Bits = [];\n this.lenIndices8Bits = 0;\n this.indices16Bits = [];\n this.lenIndices16Bits = 0;\n this.indices32Bits = [];\n this.lenIndices32Bits = 0;\n this.perObjectColors = [];\n this.perObjectPickColors = [];\n this.perObjectSolid = [];\n this.perObjectOffsets = [];\n this.perObjectPositionsDecodeMatrices = [];\n this.perObjectInstancePositioningMatrices = [];\n this.perObjectVertexBases = [];\n this.perObjectIndexBaseOffsets = [];\n this.perLineNumberPortionId8Bits = [];\n this.perLineNumberPortionId16Bits = [];\n this.perLineNumberPortionId32Bits = [];\n }\n}\n\n/**\n * @private\n */\nclass DTXLinesState {\n\n constructor() {\n this.texturePerObjectColorsAndFlags = null;\n this.texturePerObjectOffsets = null;\n this.texturePerObjectInstanceMatrices = null;\n this.texturePerObjectPositionsDecodeMatrix = null;\n this.texturePerVertexIdCoordinates = null;\n this.texturePerLineIdPortionIds8Bits = null;\n this.texturePerLineIdPortionIds16Bits = null;\n this.texturePerLineIdPortionIds32Bits = null;\n this.texturePerLineIdIndices8Bits = null;\n this.texturePerLineIdIndices16Bits = null;\n this.texturePerLineIdIndices32Bits = null;\n this.textureModelMatrices = null;\n }\n\n finalize() {\n this.indicesPerBitnessTextures = {\n 8: this.texturePerLineIdIndices8Bits,\n 16: this.texturePerLineIdIndices16Bits,\n 32: this.texturePerLineIdIndices32Bits,\n };\n this.indicesPortionIdsPerBitnessTextures = {\n 8: this.texturePerLineIdPortionIds8Bits,\n 16: this.texturePerLineIdPortionIds16Bits,\n 32: this.texturePerLineIdPortionIds32Bits,\n };\n }\n\n bindCommonTextures(glProgram, objectDecodeMatricesShaderName, vertexTextureShaderName, objectAttributesTextureShaderName, objectMatricesShaderName) {\n this.texturePerObjectPositionsDecodeMatrix.bindTexture(glProgram, objectDecodeMatricesShaderName, 1);\n this.texturePerVertexIdCoordinates.bindTexture(glProgram, vertexTextureShaderName, 2);\n this.texturePerObjectColorsAndFlags.bindTexture(glProgram, objectAttributesTextureShaderName, 3);\n this.texturePerObjectInstanceMatrices.bindTexture(glProgram, objectMatricesShaderName, 4);\n }\n\n bindLineIndicesTextures(glProgram, portionIdsShaderName, lineIndicesShaderName, textureBitness) {\n this.indicesPortionIdsPerBitnessTextures[textureBitness].bindTexture(glProgram, portionIdsShaderName, 5);\n this.indicesPerBitnessTextures[textureBitness].bindTexture(glProgram, lineIndicesShaderName, 6);\n }\n}\n\n/**\n * @private\n */\nclass BindableDataTexture {\n\n constructor(gl, texture, textureWidth, textureHeight, textureData = null) {\n this._gl = gl;\n this._texture = texture;\n this._textureWidth = textureWidth;\n this._textureHeight = textureHeight;\n this._textureData = textureData;\n }\n\n bindTexture(glProgram, shaderName, glTextureUnit) {\n return glProgram.bindTexture(shaderName, this, glTextureUnit);\n }\n\n bind(unit) {\n this._gl.activeTexture(this._gl[\"TEXTURE\" + unit]);\n this._gl.bindTexture(this._gl.TEXTURE_2D, this._texture);\n return true;\n }\n\n unbind(unit) {\n // This `unbind` method is ignored at the moment to allow avoiding\n // to rebind same texture already bound to a texture unit.\n\n // this._gl.activeTexture(this.state.gl[\"TEXTURE\" + unit]);\n // this._gl.bindTexture(this.state.gl.TEXTURE_2D, null);\n }\n}\n\nconst dataTextureRamStats$1 = {\n sizeDataColorsAndFlags: 0,\n sizeDataPositionDecodeMatrices: 0,\n sizeDataTextureOffsets: 0,\n sizeDataTexturePositions: 0,\n sizeDataTextureIndices: 0,\n sizeDataTexturePortionIds: 0,\n numberOfGeometries: 0,\n numberOfPortions: 0,\n numberOfLayers: 0,\n numberOfTextures: 0,\n totalLines: 0,\n totalLines8Bits: 0,\n totalLines16Bits: 0,\n totalLines32Bits: 0,\n cannotCreatePortion: {\n because10BitsObjectId: 0,\n becauseTextureSize: 0,\n },\n overheadSizeAlignementIndices: 0,\n overheadSizeAlignementEdgeIndices: 0,\n};\n\nwindow.printDataTextureRamStats = function () {\n\n console.log(JSON.stringify(dataTextureRamStats$1, null, 4));\n\n let totalRamSize = 0;\n\n Object.keys(dataTextureRamStats$1).forEach(key => {\n if (key.startsWith(\"size\")) {\n totalRamSize += dataTextureRamStats$1[key];\n }\n });\n\n console.log(`Total size ${totalRamSize} bytes (${(totalRamSize / 1000 / 1000).toFixed(2)} MB)`);\n console.log(`Avg bytes / triangle: ${(totalRamSize / dataTextureRamStats$1.totalLines).toFixed(2)}`);\n\n let percentualRamStats = {};\n\n Object.keys(dataTextureRamStats$1).forEach(key => {\n if (key.startsWith(\"size\")) {\n percentualRamStats[key] =\n `${(dataTextureRamStats$1[key] / totalRamSize * 100).toFixed(2)} % of total`;\n }\n });\n\n console.log(JSON.stringify({percentualRamUsage: percentualRamStats}, null, 4));\n};\n\n/**\n * @private\n */\nclass DTXLinesTextureFactory {\n /**\n * Enables the currently binded ````WebGLTexture```` to be used as a data texture.\n *\n * @param {WebGL2RenderingContext} gl\n *\n * @private\n */\n disableBindedTextureFiltering(gl) {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n }\n\n /**\n * This will generate an RGBA texture for:\n * - colors\n * - pickColors\n * - flags\n * - flags2\n * - vertex bases\n * - vertex base offsets\n *\n * The texture will have:\n * - 4 RGBA columns per row: for each object (pick) color and flags(2)\n * - N rows where N is the number of objects\n *\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike>} colors Array of colors for all objects in the layer\n * @param {ArrayLike>} pickColors Array of pickColors for all objects in the layer\n * @param {ArrayLike} vertexBases Array of position-index-bases foteh all objects in the layer\n * @param {ArrayLike} indexBaseOffsets For lines: array of offests between the (gl_VertexID / 2) and the position where the indices start in the texture layer\n *\n * @returns {BindableDataTexture}\n */\n generateTextureForColorsAndFlags(gl, colors, pickColors, vertexBases, indexBaseOffsets) {\n const numPortions = colors.length;\n\n // The number of rows in the texture is the number of\n // objects in the layer.\n\n this.numPortions = numPortions;\n\n const textureWidth = 512 * 8;\n const textureHeight = Math.ceil(numPortions / (textureWidth / 8));\n\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n\n // 8 columns per texture row:\n // - col0: (RGBA) object color RGBA\n // - col1: (packed Uint32 as RGBA) object pick color\n // - col2: (packed 4 bytes as RGBA) object flags\n // - col3: (packed 4 bytes as RGBA) object flags2\n // - col4: (packed Uint32 bytes as RGBA) vertex base\n // - col5: (packed Uint32 bytes as RGBA) index base offset\n\n\n // // - col6: (packed Uint32 bytes as RGBA) edge index base offset\n // // - col7: (packed 4 bytes as RGBA) is-solid flag for objects\n\n const texArray = new Uint8Array(4 * textureWidth * textureHeight);\n\n dataTextureRamStats$1.sizeDataColorsAndFlags += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n\n for (let i = 0; i < numPortions; i++) {\n // object color\n texArray.set(colors [i], i * 32 + 0);\n texArray.set(pickColors [i], i * 32 + 4); // object pick color\n texArray.set([0, 0, 0, 0], i * 32 + 8); // object flags\n texArray.set([0, 0, 0, 0], i * 32 + 12); // object flags2\n\n // vertex base\n texArray.set([\n (vertexBases[i] >> 24) & 255,\n (vertexBases[i] >> 16) & 255,\n (vertexBases[i] >> 8) & 255,\n (vertexBases[i]) & 255,\n ],\n i * 32 + 16\n );\n\n // lines index base offset\n texArray.set(\n [\n (indexBaseOffsets[i] >> 24) & 255,\n (indexBaseOffsets[i] >> 16) & 255,\n (indexBaseOffsets[i] >> 8) & 255,\n (indexBaseOffsets[i]) & 255,\n ],\n i * 32 + 20\n );\n\n // // edge index base offset\n // texArray.set(\n // [\n // (edgeIndexBaseOffsets[i] >> 24) & 255,\n // (edgeIndexBaseOffsets[i] >> 16) & 255,\n // (edgeIndexBaseOffsets[i] >> 8) & 255,\n // (edgeIndexBaseOffsets[i]) & 255,\n // ],\n // i * 32 + 24\n // );\n //\n // // is-solid flag\n // texArray.set([solid[i] ? 1 : 0, 0, 0, 0], i * 32 + 28);\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray);\n }\n\n /**\n * This will generate a texture for all object offsets.\n *\n * @param {WebGL2RenderingContext} gl\n * @param {int[]} offsets Array of int[3], one XYZ offset array for each object\n *\n * @returns {BindableDataTexture}\n */\n generateTextureForObjectOffsets(gl, numOffsets) {\n const textureWidth = 512;\n const textureHeight = Math.ceil(numOffsets / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArray = new Float32Array(3 * textureWidth * textureHeight).fill(0);\n dataTextureRamStats$1.sizeDataTextureOffsets += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32F, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB, gl.FLOAT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray);\n }\n\n /**\n * This will generate a texture for all positions decode matrices in the layer.\n *\n * The texture will have:\n * - 4 RGBA columns per row (each column will contain 4 packed half-float (16 bits) components).\n * Thus, each row will contain 16 packed half-floats corresponding to a complete positions decode matrix)\n * - N rows where N is the number of objects\n *\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} instanceMatrices Array of geometry instancing matrices for all objects in the layer. Null if the objects are not instanced.\n *\n * @returns {BindableDataTexture}\n */\n generateTextureForInstancingMatrices(gl, instanceMatrices) {\n const numMatrices = instanceMatrices.length;\n if (numMatrices === 0) {\n throw \"num instance matrices===0\";\n }\n // in one row we can fit 512 matrices\n const textureWidth = 512 * 4;\n const textureHeight = Math.ceil(numMatrices / (textureWidth / 4));\n const texArray = new Float32Array(4 * textureWidth * textureHeight);\n // dataTextureRamStats.sizeDataPositionDecodeMatrices += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n for (let i = 0; i < instanceMatrices.length; i++) { // 4x4 values\n texArray.set(instanceMatrices[i], i * 16);\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA, gl.FLOAT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray);\n }\n\n /**\n * This will generate a texture for all positions decode matrices in the layer.\n *\n * The texture will have:\n * - 4 RGBA columns per row (each column will contain 4 packed half-float (16 bits) components).\n * Thus, each row will contain 16 packed half-floats corresponding to a complete positions decode matrix)\n * - N rows where N is the number of objects\n *\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} positionDecodeMatrices Array of positions decode matrices for all objects in the layer\n *\n * @returns {BindableDataTexture}\n */\n generateTextureForPositionsDecodeMatrices(gl, positionDecodeMatrices) {\n const numMatrices = positionDecodeMatrices.length;\n if (numMatrices === 0) {\n throw \"num decode+entity matrices===0\";\n }\n // in one row we can fit 512 matrices\n const textureWidth = 512 * 4;\n const textureHeight = Math.ceil(numMatrices / (textureWidth / 4));\n const texArray = new Float32Array(4 * textureWidth * textureHeight);\n dataTextureRamStats$1.sizeDataPositionDecodeMatrices += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n for (let i = 0; i < positionDecodeMatrices.length; i++) { // 4x4 values\n texArray.set(positionDecodeMatrices[i], i * 16);\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA, gl.FLOAT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n generateTextureFor8BitIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 3 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint8Array(texArraySize);\n dataTextureRamStats$1.sizeDataTextureIndices += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB8UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_BYTE, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n generateTextureFor16BitIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 3 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint16Array(texArraySize);\n dataTextureRamStats$1.sizeDataTextureIndices += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_SHORT, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n generateTextureFor32BitIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 3 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint32Array(texArraySize);\n dataTextureRamStats$1.sizeDataTextureIndices += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_INT, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} positionsArrays Arrays of quantized positions in the layer\n * @param lenPositions\n *\n * This will generate a texture for positions in the layer.\n *\n * The texture will have:\n * - 1024 columns, where each pixel will be a 16-bit-per-component RGB texture, corresponding to the XYZ of the position\n * - a number of rows R where R*1024 is just >= than the number of vertices (positions / 3)\n *\n * @returns {BindableDataTexture}\n */\n generateTextureForPositions(gl, positionsArrays, lenPositions) {\n const numVertices = lenPositions / 3;\n const textureWidth = 4096;\n const textureHeight = Math.ceil(numVertices / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint16Array(texArraySize);\n dataTextureRamStats$1.sizeDataTexturePositions += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n for (let i = 0, j = 0, len = positionsArrays.length; i < len; i++) {\n const pc = positionsArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_SHORT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} portionIdsArray\n *\n * @returns {BindableDataTexture}\n */\n generateTextureForPackedPortionIds(gl, portionIdsArray) {\n if (portionIdsArray.length === 0) {\n return {texture: null, textureHeight: 0};\n }\n const lenArray = portionIdsArray.length;\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenArray / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight;\n const texArray = new Uint16Array(texArraySize);\n texArray.set(portionIdsArray, 0);\n dataTextureRamStats$1.sizeDataTexturePortionIds += texArray.byteLength;\n dataTextureRamStats$1.numberOfTextures++;\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R16UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RED_INTEGER, gl.UNSIGNED_SHORT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n}\n\nconst configs$1 = new Configs();\n\nconst MAX_NUMBER_OF_OBJECTS_IN_LAYER$1 = (1 << 16);\nconst MAX_DATA_TEXTURE_HEIGHT$1 = configs$1.maxDataTextureHeight;\nconst INDICES_EDGE_INDICES_ALIGNEMENT_SIZE$1 = 8;\nconst MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE$1 = 10;\n\nconst tempMat4a$d = new Float32Array(16);\nconst tempUint8Array4$1 = new Uint8Array(4);\nconst tempFloat32Array3$1 = new Float32Array(3);\n\nlet numLayers$1 = 0;\n\nconst DEFAULT_MATRIX$2 = math.identityMat4();\n\n/**\n * @private\n */\nclass DTXLinesLayer {\n\n constructor(model, cfg) {\n\n console.info(\"Creating DTXLinesLayer\");\n\n dataTextureRamStats$1.numberOfLayers++;\n\n this._layerNumber = numLayers$1++;\n this.sortId = `TriDTX-${this._layerNumber}`; // State sorting key.\n this.layerIndex = cfg.layerIndex; // Index of this DTXLinesLayer in {@link SceneModel#_layerList}.\n this._renderers = getRenderers$1(model.scene);\n this.model = model;\n this._buffer = new DTXLinesBuffer();\n this._dataTextureState = new DTXLinesState();\n this._dataTextureGenerator = new DTXLinesTextureFactory();\n\n this._state = new RenderState({\n origin: math.vec3(cfg.origin),\n textureState: this._dataTextureState,\n numIndices8Bits: 0,\n numIndices16Bits: 0,\n numIndices32Bits: 0,\n numVertices: 0,\n });\n\n this._numPortions = 0; // These counts are used to avoid unnecessary render passes\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n this._subPortions = [];\n this._portionToSubPortionsMap = [];\n this._bucketGeometries = {};\n this._meshes = [];\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n this._numUpdatesInFrame = 0;\n this._finalized = false;\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n canCreatePortion(portionCfg) {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n const numNewPortions = portionCfg.buckets.length;\n if ((this._numPortions + numNewPortions) > MAX_NUMBER_OF_OBJECTS_IN_LAYER$1) {\n dataTextureRamStats$1.cannotCreatePortion.because10BitsObjectId++;\n }\n let retVal = (this._numPortions + numNewPortions) <= MAX_NUMBER_OF_OBJECTS_IN_LAYER$1;\n const bucketIndex = 0; // TODO: Is this a bug?\n const bucketGeometryId = portionCfg.geometryId !== undefined && portionCfg.geometryId !== null\n ? `${portionCfg.geometryId}#${bucketIndex}`\n : `${portionCfg.id}#${bucketIndex}`;\n const alreadyHasPortionGeometry = this._bucketGeometries[bucketGeometryId];\n if (!alreadyHasPortionGeometry) {\n const maxIndicesOfAnyBits = Math.max(this._state.numIndices8Bits, this._state.numIndices16Bits, this._state.numIndices32Bits,);\n let numVertices = 0;\n let numIndices = 0;\n portionCfg.buckets.forEach(bucket => {\n numVertices += bucket.positionsCompressed.length / 3;\n numIndices += bucket.indices.length / 2;\n });\n if ((this._state.numVertices + numVertices) > MAX_DATA_TEXTURE_HEIGHT$1 * 4096 ||\n (maxIndicesOfAnyBits + numIndices) > MAX_DATA_TEXTURE_HEIGHT$1 * 4096) {\n dataTextureRamStats$1.cannotCreatePortion.becauseTextureSize++;\n }\n retVal &&=\n (this._state.numVertices + numVertices) <= MAX_DATA_TEXTURE_HEIGHT$1 * 4096 &&\n (maxIndicesOfAnyBits + numIndices) <= MAX_DATA_TEXTURE_HEIGHT$1 * 4096;\n }\n return retVal;\n }\n\n createPortion(mesh, portionCfg) {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n const subPortionIds = [];\n // const portionAABB = portionCfg.worldAABB;\n portionCfg.buckets.forEach((bucket, bucketIndex) => {\n const bucketGeometryId = portionCfg.geometryId !== undefined && portionCfg.geometryId !== null\n ? `${portionCfg.geometryId}#${bucketIndex}`\n : `${portionCfg.id}#${bucketIndex}`;\n let bucketGeometry = this._bucketGeometries[bucketGeometryId];\n if (!bucketGeometry) {\n bucketGeometry = this._createBucketGeometry(portionCfg, bucket);\n this._bucketGeometries[bucketGeometryId] = bucketGeometry;\n }\n // const subPortionAABB = math.collapseAABB3(tempAABB3b);\n const subPortionId = this._createSubPortion(portionCfg, bucketGeometry, bucket);\n //math.expandAABB3(portionAABB, subPortionAABB);\n subPortionIds.push(subPortionId);\n });\n const portionId = this._portionToSubPortionsMap.length;\n this._portionToSubPortionsMap.push(subPortionIds);\n this.model.numPortions++;\n this._meshes.push(mesh);\n return portionId;\n }\n\n _createBucketGeometry(portionCfg, bucket) {\n if (bucket.indices) {\n const alignedIndicesLen = Math.ceil((bucket.indices.length / 2) / INDICES_EDGE_INDICES_ALIGNEMENT_SIZE$1) * INDICES_EDGE_INDICES_ALIGNEMENT_SIZE$1 * 2;\n dataTextureRamStats$1.overheadSizeAlignementIndices += 2 * (alignedIndicesLen - bucket.indices.length);\n const alignedIndices = new Uint32Array(alignedIndicesLen);\n alignedIndices.fill(0);\n alignedIndices.set(bucket.indices);\n bucket.indices = alignedIndices;\n }\n const positionsCompressed = bucket.positionsCompressed;\n const indices = bucket.indices;\n const buffer = this._buffer;\n buffer.positionsCompressed.push(positionsCompressed);\n const vertexBase = buffer.lenPositionsCompressed / 3;\n const numVertices = positionsCompressed.length / 3;\n buffer.lenPositionsCompressed += positionsCompressed.length;\n let indicesBase;\n let numLines = 0;\n if (indices) {\n numLines = indices.length / 2;\n let indicesBuffer;\n if (numVertices <= (1 << 8)) {\n indicesBuffer = buffer.indices8Bits;\n indicesBase = buffer.lenIndices8Bits / 2;\n buffer.lenIndices8Bits += indices.length;\n } else if (numVertices <= (1 << 16)) {\n indicesBuffer = buffer.indices16Bits;\n indicesBase = buffer.lenIndices16Bits / 2;\n buffer.lenIndices16Bits += indices.length;\n } else {\n indicesBuffer = buffer.indices32Bits;\n indicesBase = buffer.lenIndices32Bits / 2;\n buffer.lenIndices32Bits += indices.length;\n }\n indicesBuffer.push(indices);\n }\n this._state.numVertices += numVertices;\n dataTextureRamStats$1.numberOfGeometries++;\n const bucketGeometry = {\n vertexBase,\n numVertices,\n numLines,\n indicesBase\n };\n return bucketGeometry;\n }\n\n _createSubPortion(portionCfg, bucketGeometry) {\n const color = portionCfg.color;\n const colors = portionCfg.colors;\n const opacity = portionCfg.opacity;\n const meshMatrix = portionCfg.meshMatrix;\n const pickColor = portionCfg.pickColor;\n const buffer = this._buffer;\n const state = this._state;\n buffer.perObjectPositionsDecodeMatrices.push(portionCfg.positionsDecodeMatrix);\n buffer.perObjectInstancePositioningMatrices.push(meshMatrix || DEFAULT_MATRIX$2);\n buffer.perObjectSolid.push(!!portionCfg.solid);\n if (colors) {\n buffer.perObjectColors.push([colors[0] * 255, colors[1] * 255, colors[2] * 255, 255]);\n } else if (color) { // Color is pre-quantized by SceneModel\n buffer.perObjectColors.push([color[0], color[1], color[2], opacity]);\n }\n buffer.perObjectPickColors.push(pickColor);\n buffer.perObjectVertexBases.push(bucketGeometry.vertexBase);\n {\n let currentNumIndices;\n if (bucketGeometry.numVertices <= (1 << 8)) {\n currentNumIndices = state.numIndices8Bits;\n } else if (bucketGeometry.numVertices <= (1 << 16)) {\n currentNumIndices = state.numIndices16Bits;\n } else {\n currentNumIndices = state.numIndices32Bits;\n }\n buffer.perObjectIndexBaseOffsets.push(currentNumIndices / 2 - bucketGeometry.indicesBase);\n }\n const subPortionId = this._subPortions.length;\n if (bucketGeometry.numLines > 0) {\n let numIndices = bucketGeometry.numLines * 2;\n let indicesPortionIdBuffer;\n if (bucketGeometry.numVertices <= (1 << 8)) {\n indicesPortionIdBuffer = buffer.perLineNumberPortionId8Bits;\n state.numIndices8Bits += numIndices;\n dataTextureRamStats$1.totalLines8Bits += bucketGeometry.numLines;\n } else if (bucketGeometry.numVertices <= (1 << 16)) {\n indicesPortionIdBuffer = buffer.perLineNumberPortionId16Bits;\n state.numIndices16Bits += numIndices;\n dataTextureRamStats$1.totalLines16Bits += bucketGeometry.numLines;\n } else {\n indicesPortionIdBuffer = buffer.perLineNumberPortionId32Bits;\n state.numIndices32Bits += numIndices;\n dataTextureRamStats$1.totalLines32Bits += bucketGeometry.numLines;\n }\n dataTextureRamStats$1.totalLines += bucketGeometry.numLines;\n for (let i = 0; i < bucketGeometry.numLines; i += INDICES_EDGE_INDICES_ALIGNEMENT_SIZE$1) {\n indicesPortionIdBuffer.push(subPortionId);\n }\n }\n this._subPortions.push({\n // vertsBase: vertsIndex,\n numVertices: bucketGeometry.numLines\n });\n this._numPortions++;\n dataTextureRamStats$1.numberOfPortions++;\n return subPortionId;\n }\n\n /**\n * Builds data textures from the appended geometries and loads them into the GPU.\n *\n * No more portions can then be created.\n */\n finalize() {\n if (this._finalized) {\n return;\n }\n const state = this._state;\n const textureState = this._dataTextureState;\n const gl = this.model.scene.canvas.gl;\n const buffer = this._buffer;\n state.gl = gl;\n textureState.texturePerObjectColorsAndFlags = this._dataTextureGenerator.generateTextureForColorsAndFlags(\n gl,\n buffer.perObjectColors,\n buffer.perObjectPickColors,\n buffer.perObjectVertexBases,\n buffer.perObjectIndexBaseOffsets,\n buffer.perObjectSolid);\n textureState.texturePerObjectInstanceMatrices = this._dataTextureGenerator.generateTextureForInstancingMatrices(gl, buffer.perObjectInstancePositioningMatrices);\n textureState.texturePerObjectPositionsDecodeMatrix = this._dataTextureGenerator.generateTextureForPositionsDecodeMatrices(gl, buffer.perObjectPositionsDecodeMatrices);\n textureState.texturePerVertexIdCoordinates = this._dataTextureGenerator.generateTextureForPositions(gl, buffer.positionsCompressed, buffer.lenPositionsCompressed);\n textureState.texturePerLineIdPortionIds8Bits = this._dataTextureGenerator.generateTextureForPackedPortionIds(gl, buffer.perLineNumberPortionId8Bits);\n textureState.texturePerLineIdPortionIds16Bits = this._dataTextureGenerator.generateTextureForPackedPortionIds(gl, buffer.perLineNumberPortionId16Bits);\n textureState.texturePerLineIdPortionIds32Bits = this._dataTextureGenerator.generateTextureForPackedPortionIds(gl, buffer.perLineNumberPortionId32Bits);\n if (buffer.lenIndices8Bits > 0) {\n textureState.texturePerLineIdIndices8Bits = this._dataTextureGenerator.generateTextureFor8BitIndices(gl, buffer.indices8Bits, buffer.lenIndices8Bits);\n }\n if (buffer.lenIndices16Bits > 0) {\n textureState.texturePerLineIdIndices16Bits = this._dataTextureGenerator.generateTextureFor16BitIndices(gl, buffer.indices16Bits, buffer.lenIndices16Bits);\n }\n if (buffer.lenIndices32Bits > 0) {\n textureState.texturePerLineIdIndices32Bits = this._dataTextureGenerator.generateTextureFor32BitIndices(gl, buffer.indices32Bits, buffer.lenIndices32Bits);\n }\n textureState.finalize();\n // Free up memory\n this._buffer = null;\n this._bucketGeometries = {};\n this._finalized = true;\n this._deferredSetFlagsDirty = false; //\n this._onSceneRendering = this.model.scene.on(\"rendering\", () => {\n if (this._deferredSetFlagsDirty) {\n this._uploadDeferredFlags();\n }\n this._numUpdatesInFrame = 0;\n });\n }\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n const deferred = true;\n this._setFlags(portionId, flags, meshTransparent, deferred);\n this._setFlags2(portionId, flags, deferred);\n }\n\n flushInitFlags() {\n this._setDeferredFlags();\n this._setDeferredFlags2();\n }\n\n setVisible(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setHighlighted(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setXRayed(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setSelected(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setEdges(portionId, flags, transparent) {\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags2(portionId, flags);\n }\n\n /**\n * This will _start_ a \"set-flags transaction\".\n *\n * After invoking this method, calling setFlags/setFlags2 will not update\n * the colors+flags texture but only store the new flags/flag2 in the\n * colors+flags texture data array.\n *\n * After invoking this method, and when all desired setFlags/setFlags2 have\n * been called on needed portions of the layer, invoke `_uploadDeferredFlags`\n * to actually upload the data array into the texture.\n *\n * In massive \"set-flags\" scenarios like VFC or LOD mechanisms, the combination of\n * `_beginDeferredFlags` + `_uploadDeferredFlags`brings a speed-up of\n * up to 80x when e.g. objects are massively (un)culled 🚀.\n */\n _beginDeferredFlags() {\n this._deferredSetFlagsActive = true;\n }\n\n /**\n * This will _commit_ a \"set-flags transaction\".\n *\n * Invoking this method will update the colors+flags texture data with new\n * flags/flags2 set since the previous invocation of `_beginDeferredFlags`.\n */\n _uploadDeferredFlags() {\n this._deferredSetFlagsActive = false;\n if (!this._deferredSetFlagsDirty) {\n return;\n }\n this._deferredSetFlagsDirty = false;\n const gl = this.model.scene.canvas.gl;\n const textureState = this._dataTextureState;\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n 0, // xoffset\n 0, // yoffset\n textureState.texturePerObjectColorsAndFlags._textureWidth, // width\n textureState.texturePerObjectColorsAndFlags._textureHeight, // width\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n textureState.texturePerObjectColorsAndFlags._textureData\n );\n }\n\n setCulled(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions += this._portionToSubPortionsMap[portionId].length;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions -= this._portionToSubPortionsMap[portionId].length;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setColor(portionId, color) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetColor(subPortionIds[i], color);\n }\n }\n\n _subPortionSetColor(subPortionId, color) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n // Color\n const textureState = this._dataTextureState;\n const gl = this.model.scene.canvas.gl;\n tempUint8Array4$1 [0] = color[0];\n tempUint8Array4$1 [1] = color[1];\n tempUint8Array4$1 [2] = color[2];\n tempUint8Array4$1 [3] = color[3];\n // object colors\n textureState.texturePerObjectColorsAndFlags._textureData.set(tempUint8Array4$1, subPortionId * 32);\n if (this._deferredSetFlagsActive) {\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE$1) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 8, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n 1, // width\n 1, //height\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n tempUint8Array4$1\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n _setFlags(portionId, flags, transparent, deferred = false) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetFlags(subPortionIds[i], flags, transparent, deferred);\n }\n }\n\n _subPortionSetFlags(subPortionId, flags, transparent, deferred = false) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n // Color\n\n let f0;\n if (!visible || culled || xrayed) { // Highlight & select are layered on top of color - not mutually exclusive\n f0 = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (transparent) {\n f0 = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n f0 = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n // Silhouette\n\n let f1;\n if (!visible || culled) {\n f1 = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n f1 = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n f1 = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n f1 = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n f1 = RENDER_PASSES.NOT_RENDERED;\n }\n\n // // Edges\n //\n // let f2 = 0;\n // if (!visible || culled) {\n // f2 = RENDER_PASSES.NOT_RENDERED;\n // } else if (selected) {\n // f2 = RENDER_PASSES.EDGES_SELECTED;\n // } else if (highlighted) {\n // f2 = RENDER_PASSES.EDGES_HIGHLIGHTED;\n // } else if (xrayed) {\n // f2 = RENDER_PASSES.EDGES_XRAYED;\n // } else if (edges) {\n // if (transparent) {\n // f2 = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;\n // } else {\n // f2 = RENDER_PASSES.EDGES_COLOR_OPAQUE;\n // }\n // } else {\n // f2 = RENDER_PASSES.NOT_RENDERED;\n // }\n\n // Pick\n\n let f3 = (visible && (!culled) && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n const textureState = this._dataTextureState;\n const gl = this.model.scene.canvas.gl;\n tempUint8Array4$1 [0] = f0;\n tempUint8Array4$1 [1] = f1;\n // tempUint8Array4 [2] = f2;\n tempUint8Array4$1 [3] = f3;\n // object flags\n textureState.texturePerObjectColorsAndFlags._textureData.set(tempUint8Array4$1, subPortionId * 32 + 8);\n if (this._deferredSetFlagsActive || deferred) {\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE$1) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 8 + 2, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n 1, // width\n 1, //height\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n tempUint8Array4$1\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n _setDeferredFlags() {\n }\n\n _setFlags2(portionId, flags, deferred = false) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetFlags2(subPortionIds[i], flags, deferred);\n }\n }\n\n _subPortionSetFlags2(subPortionId, flags, deferred = false) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n const clippable = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 255 : 0;\n const textureState = this._dataTextureState;\n const gl = this.model.scene.canvas.gl;\n tempUint8Array4$1 [0] = clippable;\n tempUint8Array4$1 [1] = 0;\n tempUint8Array4$1 [2] = 1;\n tempUint8Array4$1 [3] = 2;\n // object flags2\n textureState.texturePerObjectColorsAndFlags._textureData.set(tempUint8Array4$1, subPortionId * 32 + 12);\n if (this._deferredSetFlagsActive || deferred) {\n // console.log(\"_subPortionSetFlags2 set flags defer\");\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE$1) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 8 + 3, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n 1, // width\n 1, //height\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n tempUint8Array4$1\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n _setDeferredFlags2() {\n }\n\n setOffset(portionId, offset) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetOffset(subPortionIds[i], offset);\n }\n }\n\n _subPortionSetOffset(subPortionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n // if (!this.model.scene.entityOffsetsEnabled) {\n // this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n // return;\n // }\n const textureState = this._dataTextureState;\n const gl = this.model.scene.canvas.gl;\n tempFloat32Array3$1 [0] = offset[0];\n tempFloat32Array3$1 [1] = offset[1];\n tempFloat32Array3$1 [2] = offset[2];\n // object offset\n textureState.texturePerObjectOffsets._textureData.set(tempFloat32Array3$1, subPortionId * 3);\n if (this._deferredSetFlagsActive) {\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE$1) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectOffsets._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n 0, // x offset\n subPortionId, // yoffset\n 1, // width\n 1, // height\n gl.RGB,\n gl.FLOAT,\n tempFloat32Array3$1\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n setMatrix(portionId, matrix) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetMatrix(subPortionIds[i], matrix);\n }\n }\n\n _subPortionSetMatrix(subPortionId, matrix) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n // if (!this.model.scene.entityMatrixsEnabled) {\n // this.model.error(\"Entity#matrix not enabled for this Viewer\"); // See Viewer entityMatrixsEnabled\n // return;\n // }\n const textureState = this._dataTextureState;\n const gl = this.model.scene.canvas.gl;\n tempMat4a$d.set(matrix);\n textureState.texturePerObjectInstanceMatrices._textureData.set(tempMat4a$d, subPortionId * 16);\n if (this._deferredSetFlagsActive) {\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE$1) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectInstanceMatrices._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 4, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n // 1,\n 4, // width\n 1, // height\n gl.RGBA,\n gl.FLOAT,\n tempMat4a$d\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n // ---------------------- COLOR RENDERING -----------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n\n drawDepth(renderFlags, frameCtx) {\n }\n\n drawNormals(renderFlags, frameCtx) {\n }\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n // if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n // return;\n // }\n // if (this._renderers.silhouetteRenderer) {\n // this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n // }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n // if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n // return;\n // }\n // if (this._renderers.silhouetteRenderer) {\n // this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n // }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n // if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n // return;\n // }\n // if (this._renderers.silhouetteRenderer) {\n // this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n // }\n }\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n }\n\n drawOcclusion(renderFlags, frameCtx) {\n }\n\n drawShadow(renderFlags, frameCtx) {\n }\n\n setPickMatrices(pickViewMatrix, pickProjMatrix) {\n }\n\n drawPickMesh(renderFlags, frameCtx) {\n }\n\n drawPickDepths(renderFlags, frameCtx) {\n }\n\n drawSnapInit(renderFlags, frameCtx) {\n }\n\n drawSnap(renderFlags, frameCtx) {\n }\n\n drawPickNormals(renderFlags, frameCtx) {\n }\n\n destroy() {\n if (this._destroyed) {\n return;\n }\n const state = this._state;\n this.model.scene.off(this._onSceneRendering);\n state.destroy();\n this._destroyed = true;\n }\n}\n\nconst tempVec3a$m = math.vec3();\nconst tempVec3b$i = math.vec3();\nconst tempVec3c$e = math.vec3();\nmath.vec3();\n\nconst tempVec4a$5 = math.vec4();\n\nconst tempMat4a$c = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesColorRenderer {\n\n constructor(scene, withSAO) {\n this._scene = scene;\n this._withSAO = withSAO;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n const scene = this._scene;\n return [scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const scene = this._scene;\n const camera = scene.camera;\n const model = dataTextureLayer.model;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx, state);\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$m;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3b$i);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(camera.viewMatrix, rtcOrigin, tempMat4a$c);\n rtcCameraEye = tempVec3c$e;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n } else {\n rtcViewMatrix = camera.viewMatrix;\n rtcCameraEye = camera.eye;\n }\n\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform1i(this._uRenderPass, renderPass);\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$m);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n\n frameCtx.drawElements++;\n }\n\n _allocate() {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const lightsState = scene._lightsState;\n\n this._program = new Program(gl, this._buildShader());\n\n if (this._program.errors) {\n this.errors = this._program.errors;\n console.error(this.errors);\n return;\n }\n\n const program = this._program;\n\n this._uRenderPass = program.getLocation(\"renderPass\");\n\n this._uLightAmbient = program.getLocation(\"lightAmbient\");\n this._uLightColor = [];\n this._uLightDir = [];\n this._uLightPos = [];\n this._uLightAttenuation = [];\n\n const lights = lightsState.lights;\n let light;\n\n for (let i = 0, len = lights.length; i < len; i++) {\n light = lights[i];\n switch (light.type) {\n case \"dir\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = null;\n this._uLightDir[i] = program.getLocation(\"lightDir\" + i);\n break;\n case \"point\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = program.getLocation(\"lightPos\" + i);\n this._uLightDir[i] = null;\n this._uLightAttenuation[i] = program.getLocation(\"lightAttenuation\" + i);\n break;\n case \"spot\":\n this._uLightColor[i] = program.getLocation(\"lightColor\" + i);\n this._uLightPos[i] = program.getLocation(\"lightPos\" + i);\n this._uLightDir[i] = program.getLocation(\"lightDir\" + i);\n this._uLightAttenuation[i] = program.getLocation(\"lightAttenuation\" + i);\n break;\n }\n }\n\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n\n this._uSectionPlanes = [];\n\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n\n if (this._withSAO) {\n this._uOcclusionTexture = \"uOcclusionTexture\";\n this._uSAOParams = program.getLocation(\"uSAOParams\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdNormals = \"uTexturePerPolygonIdNormals\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram(frameCtx) {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const lights = scene._lightsState.lights;\n const project = scene.camera.project;\n\n program.bind();\n\n if (this._uLightAmbient) {\n gl.uniform4fv(this._uLightAmbient, scene._lightsState.getAmbientColorAndIntensity());\n }\n\n for (let i = 0, len = lights.length; i < len; i++) {\n const light = lights[i];\n\n if (this._uLightColor[i]) {\n gl.uniform4f(this._uLightColor[i], light.color[0], light.color[1], light.color[2], light.intensity);\n }\n if (this._uLightPos[i]) {\n gl.uniform3fv(this._uLightPos[i], light.pos);\n if (this._uLightAttenuation[i]) {\n gl.uniform1f(this._uLightAttenuation[i], light.attenuation);\n }\n }\n if (this._uLightDir[i]) {\n gl.uniform3fv(this._uLightDir[i], light.dir);\n }\n }\n\n if (this._withSAO) {\n const sao = scene.sao;\n const saoEnabled = sao.possible;\n if (saoEnabled) {\n const viewportWidth = gl.drawingBufferWidth;\n const viewportHeight = gl.drawingBufferHeight;\n tempVec4a$5[0] = viewportWidth;\n tempVec4a$5[1] = viewportHeight;\n tempVec4a$5[2] = sao.blendCutoff;\n tempVec4a$5[3] = sao.blendFactor;\n gl.uniform4fv(this._uSAOParams, tempVec4a$5);\n this._program.bindTexture(this._uOcclusionTexture, frameCtx.occlusionTexture, 10);\n }\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const lightsState = scene._lightsState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n let light;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// TrianglesDataTextureColorRenderer vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n\n src.push(\"vec3 positions[3];\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n\n src.push(\"uniform vec4 lightAmbient;\");\n\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n src.push(\"uniform vec4 lightColor\" + i + \";\");\n if (light.type === \"dir\") {\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n if (light.type === \"point\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n }\n if (light.type === \"spot\") {\n src.push(\"uniform vec3 lightPos\" + i + \";\");\n src.push(\"uniform vec3 lightDir\" + i + \";\");\n }\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.x = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`if (int(flags.x) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n src.push(`if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\"};\");\n\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n // when the geometry is not solid, if needed, flip the triangle winding\n\n src.push(\"if (solid != 1u) {\");\n src.push(\"if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\"vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n // src.push(\"vColor = vec4(vec3(1, -1, 0)*dot(normalize(position.xyz - uCameraEyeRtcInQuantizedSpace), normal), 1);\")\n src.push(\"if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"viewNormal = -viewNormal;\");\n src.push(\"}\");\n src.push(\"} else {\");\n // src.push(\"vColor = vec4(vec3(1, -1, 0)*viewNormal.z, 1);\")\n\n src.push(\"if (viewNormal.z < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"viewNormal = -viewNormal;\");\n src.push(\"}\");\n src.push(\"}\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * ((objectDecodeAndInstanceMatrix * vec4(position, 1.0))); \");\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vec3 reflectedColor = vec3(0.0, 0.0, 0.0);\");\n src.push(\"vec3 viewLightDir = vec3(0.0, 0.0, -1.0);\");\n\n src.push(\"float lambertian = 1.0;\");\n for (let i = 0, len = lightsState.lights.length; i < len; i++) {\n light = lightsState.lights[i];\n if (light.type === \"ambient\") {\n continue;\n }\n if (light.type === \"dir\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"point\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = -normalize(lightPos\" + i + \" - viewPosition.xyz);\");\n } else {\n src.push(\"viewLightDir = -normalize((viewMatrix * vec4(lightPos\" + i + \", 0.0)).xyz);\");\n }\n } else if (light.type === \"spot\") {\n if (light.space === \"view\") {\n src.push(\"viewLightDir = normalize(lightDir\" + i + \");\");\n } else {\n src.push(\"viewLightDir = normalize((viewMatrix * vec4(lightDir\" + i + \", 0.0)).xyz);\");\n }\n } else {\n continue;\n }\n src.push(\"lambertian = max(dot(-viewNormal, viewLightDir), 0.0);\");\n src.push(\"reflectedColor += lambertian * (lightColor\" + i + \".rgb * lightColor\" + i + \".a);\");\n }\n\n src.push(\"vec3 rgb = vec3(color.rgb) / 255.0;\");\n src.push(\"vColor = vec4((lightAmbient.rgb * lightAmbient.a * rgb) + (reflectedColor * rgb), float(color.a) / 255.0);\");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags2 = flags2.r;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n\n src.push(\"}\");\n\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// TrianglesDataTextureColorRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (this._withSAO) {\n src.push(\"uniform sampler2D uOcclusionTexture;\");\n src.push(\"uniform vec4 uSAOParams;\");\n\n src.push(\"const float packUpscale = 256. / 255.;\");\n src.push(\"const float unpackDownScale = 255. / 256.;\");\n src.push(\"const vec3 packFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\");\n src.push(\"const vec4 unPackFactors = unpackDownScale / vec4( packFactors, 1. );\");\n\n src.push(\"float unpackRGBToFloat( const in vec4 v ) {\");\n src.push(\" return dot( v, unPackFactors );\");\n src.push(\"}\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n //src.push(\" gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n\n if (this._withSAO) {\n // Doing SAO blend in the main solid fill draw shader just so that edge lines can be drawn over the top\n // Would be more efficient to defer this, then render lines later, using same depth buffer for Z-reject\n src.push(\" float viewportWidth = uSAOParams[0];\");\n src.push(\" float viewportHeight = uSAOParams[1];\");\n src.push(\" float blendCutoff = uSAOParams[2];\");\n src.push(\" float blendFactor = uSAOParams[3];\");\n src.push(\" vec2 uv = vec2(gl_FragCoord.x / viewportWidth, gl_FragCoord.y / viewportHeight);\");\n src.push(\" float ambient = smoothstep(blendCutoff, 1.0, unpackRGBToFloat(texture(uOcclusionTexture, uv))) * blendFactor;\");\n src.push(\" outColor = vec4(vColor.rgb * ambient, 1.0);\");\n } else {\n src.push(\" outColor = vColor;\");\n }\n\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst defaultColor$1 = new Float32Array([1, 1, 1]);\n\nconst tempVec3a$l = math.vec3();\nconst tempVec3b$h = math.vec3();\nconst tempVec3c$d = math.vec3();\nmath.vec3();\nconst tempMat4a$b = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesSilhouetteRenderer {\n\n constructor(scene, primitiveType) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const scene = this._scene;\n const camera = scene.camera;\n const model = dataTextureLayer.model;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const viewMatrix = camera.viewMatrix;\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx, state);\n }\n\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3a$l;\n if (origin) {\n const rotatedOrigin = tempVec3b$h;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$b);\n rtcCameraEye = tempVec3c$d;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniformMatrix4fv(this._uWorldMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n\n if (renderPass === RENDER_PASSES.SILHOUETTE_XRAYED) {\n const material = scene.xrayMaterial._state;\n const fillColor = material.fillColor;\n const fillAlpha = material.fillAlpha;\n gl.uniform4f(this._uColor, fillColor[0], fillColor[1], fillColor[2], fillAlpha);\n\n } else if (renderPass === RENDER_PASSES.SILHOUETTE_HIGHLIGHTED) {\n const material = scene.highlightMaterial._state;\n const fillColor = material.fillColor;\n const fillAlpha = material.fillAlpha;\n gl.uniform4f(this._uColor, fillColor[0], fillColor[1], fillColor[2], fillAlpha);\n\n } else if (renderPass === RENDER_PASSES.SILHOUETTE_SELECTED) {\n const material = scene.selectedMaterial._state;\n const fillColor = material.fillColor;\n const fillAlpha = material.fillAlpha;\n gl.uniform4f(this._uColor, fillColor[0], fillColor[1], fillColor[2], fillAlpha);\n\n } else {\n gl.uniform4fv(this._uColor, defaultColor$1);\n }\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$l);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uColor = program.getLocation(\"color\");\n this._uWorldMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdNormals = \"uTexturePerPolygonIdNormals\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram(frameCtx) {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const project = scene.camera.project;\n\n this._program.bind();\n\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles dataTexture silhouette vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n // src.push(\"uniform sampler2D uOcclusionTexture;\"); \n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n\n src.push(\"vec3 positions[3];\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.y = NOT_RENDERED | SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | SILHOUETTE_XRAYED\n // renderPass = SILHOUETTE_HIGHLIGHTED | SILHOUETTE_SELECTED | | SILHOUETTE_XRAYED\n\n src.push(`if (int(flags.y) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n\n // get position\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n // get normal\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n\n // when the geometry is not solid, if needed, flip the triangle winding\n src.push(\"if (solid != 1u) {\");\n src.push(\"if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\"vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n src.push(\"if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"viewNormal = -viewNormal;\");\n src.push(\"}\");\n src.push(\"} else {\");\n src.push(\"if (viewNormal.z < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"viewNormal = -viewNormal;\");\n src.push(\"}\");\n src.push(\"}\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags2 = flags2.r;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"}\");\n\n src.push(\"}\");\n\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Triangles dataTexture draw fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"uniform vec4 color;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = color;\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst defaultColor = new Float32Array([0, 0, 0, 1]);\n\nconst tempVec3a$k = math.vec3();\nconst tempVec3b$g = math.vec3();\nmath.vec3();\nconst tempMat4a$a = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesEdgesRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const viewMatrix = camera.viewMatrix;\n\n if (!this._program) {\n this._allocate(dataTextureLayer);\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$k;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3b$g);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$a);\n } else {\n rtcViewMatrix = viewMatrix;\n }\n\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n\n if (renderPass === RENDER_PASSES.EDGES_XRAYED) {\n const material = scene.xrayMaterial._state;\n const edgeColor = material.edgeColor;\n const edgeAlpha = material.edgeAlpha;\n gl.uniform4f(this._uColor, edgeColor[0], edgeColor[1], edgeColor[2], edgeAlpha);\n\n } else if (renderPass === RENDER_PASSES.EDGES_HIGHLIGHTED) {\n const material = scene.highlightMaterial._state;\n const edgeColor = material.edgeColor;\n const edgeAlpha = material.edgeAlpha;\n gl.uniform4f(this._uColor, edgeColor[0], edgeColor[1], edgeColor[2], edgeAlpha);\n\n } else if (renderPass === RENDER_PASSES.EDGES_SELECTED) {\n const material = scene.selectedMaterial._state;\n const edgeColor = material.edgeColor;\n const edgeAlpha = material.edgeAlpha;\n gl.uniform4f(this._uColor, edgeColor[0], edgeColor[1], edgeColor[2], edgeAlpha);\n\n } else {\n gl.uniform4fv(this._uColor, defaultColor);\n }\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$k);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numEdgeIndices8Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 8 // 8 bits edge indices\n );\n\n gl.drawArrays(gl.LINES, 0, state.numEdgeIndices8Bits);\n }\n\n if (state.numEdgeIndices16Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 16 // 16 bits edge indices\n );\n\n gl.drawArrays(gl.LINES, 0, state.numEdgeIndices16Bits);\n }\n\n if (state.numEdgeIndices32Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 32 // 32 bits edge indices\n );\n\n gl.drawArrays(gl.LINES, 0, state.numEdgeIndices32Bits);\n }\n\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uColor = program.getLocation(\"color\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uWorldMatrix = program.getLocation(\"worldMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdEdgeIndices = \"uTexturePerPolygonIdEdgeIndices\";\n this._uTexturePerEdgeIdPortionIds = \"uTexturePerEdgeIdPortionIds\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n }\n\n _bindProgram() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const project = scene.camera.project;\n program.bind();\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// DTXTrianglesEdgesRenderer vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n // if (scene.entityOffsetsEnabled) {\n // src.push(\"in vec3 offset;\");\n // }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdEdgeIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerEdgeIdPortionIds;\");\n\n src.push(\"uniform vec4 color;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int edgeIndex = gl_VertexID / 2;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (edgeIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (edgeIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerEdgeIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.z = NOT_RENDERED | EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n // renderPass = EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n\n src.push(`if (int(flags.z) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedEdgeIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+6, objectIndexCoords.y), 0));\");\n\n src.push(\"int edgeIndexBaseOffset = (packedEdgeIndexBaseOffset.r << 24) + (packedEdgeIndexBaseOffset.g << 16) + (packedEdgeIndexBaseOffset.b << 8) + packedEdgeIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (edgeIndex - edgeIndexBaseOffset) & 4095;\");\n src.push(\"int v_index = (edgeIndex - edgeIndexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdEdgeIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"int indexPositionH = uniqueVertexIndexes[gl_VertexID % 2] & 4095;\");\n src.push(\"int indexPositionV = uniqueVertexIndexes[gl_VertexID % 2] >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // get position\n src.push(\"vec3 position = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH, indexPositionV), 0));\");\n\n src.push(\"mat4 matrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags2 = flags2.r;\");\n }\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"vColor = vec4(color.r, color.g, color.b, color.a);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// DTXTrianglesEdgesRenderer fragment shader\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"#extension GL_EXT_frag_depth : enable\");\n }\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$j = math.vec3();\nconst tempVec3b$f = math.vec3();\nconst tempMat4a$9 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesEdgesColorRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const viewMatrix = camera.viewMatrix;\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$j;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3b$f);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$9);\n } else {\n rtcViewMatrix = viewMatrix;\n }\n\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$j);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numEdgeIndices8Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 8 // 8 bits edge indices\n );\n\n gl.drawArrays(gl.LINES, 0, state.numEdgeIndices8Bits);\n }\n\n if (state.numEdgeIndices16Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 16 // 16 bits edge indices\n );\n\n gl.drawArrays(gl.LINES, 0, state.numEdgeIndices16Bits);\n }\n\n if (state.numEdgeIndices32Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 32 // 32 bits edge indices\n );\n\n gl.drawArrays(gl.LINES, 0, state.numEdgeIndices32Bits);\n }\n\n frameCtx.drawElements++;\n }\n\n _allocate() {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n\n this._program = new Program(gl, this._buildShader());\n\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n\n const program = this._program;\n\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n\n //this._aOffset = program.getAttribute(\"offset\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdEdgeIndices = \"uTexturePerPolygonIdEdgeIndices\";\n this._uTexturePerEdgeIdPortionIds = \"uTexturePerEdgeIdPortionIds\";\n this._uTexturePerObjectMatrix = \"uTexturePerObjectMatrix\";\n }\n\n _bindProgram() {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const project = scene.camera.project;\n\n program.bind();\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// TrianglesDataTextureEdgesColorRenderer\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) ;\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform highp sampler2D uObjectPerObjectOffsets;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdEdgeIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerEdgeIdPortionIds;\");\n\n // src.push(\"uniform vec4 color;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n src.push(\"out vec4 vColor;\");\n\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int edgeIndex = gl_VertexID / 2;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (edgeIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (edgeIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerEdgeIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.z = NOT_RENDERED | EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT | EDGES_HIGHLIGHTED | EDGES_XRAYED | EDGES_SELECTED\n // renderPass = EDGES_COLOR_OPAQUE | EDGES_COLOR_TRANSPARENT\n\n src.push(`if (int(flags.z) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedEdgeIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+6, objectIndexCoords.y), 0));\");\n\n src.push(\"int edgeIndexBaseOffset = (packedEdgeIndexBaseOffset.r << 24) + (packedEdgeIndexBaseOffset.g << 16) + (packedEdgeIndexBaseOffset.b << 8) + packedEdgeIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (edgeIndex - edgeIndexBaseOffset) & 4095;\");\n src.push(\"int v_index = (edgeIndex - edgeIndexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdEdgeIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"int indexPositionH = uniqueVertexIndexes[gl_VertexID % 2] & 4095;\");\n src.push(\"int indexPositionV = uniqueVertexIndexes[gl_VertexID % 2] >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // get position\n src.push(\"vec3 position = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH, indexPositionV), 0));\");\n\n // get color\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n\n src.push(`if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\"};\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags2 = flags2.r;\");\n }\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"vec4 rgb = vec4(color.rgba);\");\n //src.push(\"vColor = vec4(float(color.r-100.0) / 255.0, float(color.g-100.0) / 255.0, float(color.b-100.0) / 255.0, float(color.a) / 255.0);\");\n src.push(\"vColor = vec4(float(rgb.r*0.5) / 255.0, float(rgb.g*0.5) / 255.0, float(rgb.b*0.5) / 255.0, float(rgb.a) / 255.0);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// TrianglesDataTextureEdgesColorRenderer\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vColor;\");\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outColor = vColor;\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$i = math.vec3();\nconst tempVec3b$e = math.vec3();\nconst tempVec3c$c = math.vec3();\nconst tempMat4a$8 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesPickMeshRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n }\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n if (!this._program) {\n this._allocate(dataTextureLayer);\n if (this.errors) {\n return;\n }\n }\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx);\n }\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n let rtcViewMatrix;\n let rtcCameraEye;\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$i;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3b$e);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(camera.viewMatrix, rtcOrigin, tempMat4a$8);\n rtcCameraEye = tempVec3c$c;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n } else {\n rtcViewMatrix = camera.viewMatrix;\n rtcCameraEye = camera.eye;\n }\n gl.uniform2fv(this._uPickClipPos, frameCtx.pickClipPos);\n gl.uniform2f(this._uDrawingBufferSize, gl.drawingBufferWidth, gl.drawingBufferHeight);\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform1i(this._uRenderPass, renderPass);\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(camera.project.far + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$i);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPickInvisible = program.getLocation(\"pickInvisible\");\n this._uPickClipPos = program.getLocation(\"pickClipPos\");\n this._uDrawingBufferSize = program.getLocation(\"drawingBufferSize\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdNormals = \"uTexturePerPolygonIdNormals\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix = \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram(frameCtx) {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program.bind();\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry picking vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform bool pickInvisible;\");\n // src.push(\"uniform sampler2D uOcclusionTexture;\"); \n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n\n src.push(\"vec3 positions[3];\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"uniform vec2 pickClipPos;\");\n src.push(\"uniform vec2 drawingBufferSize;\");\n\n src.push(\"vec4 remapClipPos(vec4 clipPos) {\");\n src.push(\" clipPos.xy /= clipPos.w;\");\n src.push(` clipPos.xy = (clipPos.xy - pickClipPos) * drawingBufferSize;`);\n src.push(\" clipPos.xy *= clipPos.w;\");\n src.push(\" return clipPos;\");\n src.push(\"}\");\n\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n\n if (clipping) {\n src.push(\"smooth out vec4 vWorldPosition;\");\n src.push(\"flat out uvec4 vFlags2;\");\n }\n\n src.push(\"out vec4 vPickColor;\");\n\n src.push(\"void main(void) {\");\n\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.w = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`if (int(flags.w) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n\n // get position\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n // get color\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n\n // get pick-color\n src.push(\"vPickColor = vec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+1, objectIndexCoords.y), 0)) / 255.0;\");\n\n // get normal\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n\n // when the geometry is not solid, if needed, flip the triangle winding\n src.push(\"if (solid != 1u) {\");\n src.push(\"if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\"vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n src.push(\"if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"}\");\n src.push(\"} else {\");\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n src.push(\"if (viewNormal.z < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"}\");\n src.push(\"}\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags2 = flags2;\");\n }\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Batched geometry picking fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uvec4 vFlags2;\");\n for (var i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vPickColor;\");\n src.push(\"out vec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (float(vFlags2.x) > 0.0);\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n //src.push(\" gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" outPickColor = vPickColor; \");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$h = math.vec3();\nconst tempVec3b$d = math.vec3();\nconst tempVec3c$b = math.vec3();\nmath.vec3();\nconst tempMat4a$7 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesPickDepthRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n }\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (!this._program) {\n this._allocate();\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3a$h;\n if (origin) {\n const rotatedOrigin = tempVec3b$d;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$7);\n rtcCameraEye = tempVec3c$b;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n gl.uniform2fv(this._uPickClipPos, frameCtx.pickClipPos);\n gl.uniform2f(this._uDrawingBufferSize, gl.drawingBufferWidth, gl.drawingBufferHeight);\n gl.uniform1f(this._uPickZNear, frameCtx.pickZNear);\n gl.uniform1f(this._uPickZFar, frameCtx.pickZFar);\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2); // TODO: Far from pick project matrix?\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$h);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPickInvisible = program.getLocation(\"pickInvisible\");\n this._uPickClipPos = program.getLocation(\"pickClipPos\");\n this._uDrawingBufferSize = program.getLocation(\"drawingBufferSize\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._uPickZNear = program.getLocation(\"pickZNear\");\n this._uPickZFar = program.getLocation(\"pickZFar\");\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdNormals = \"uTexturePerPolygonIdNormals\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles dataTexture pick depth vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"uniform bool pickInvisible;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n\n src.push(\"vec3 positions[3];\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"uniform vec2 pickClipPos;\");\n src.push(\"uniform vec2 drawingBufferSize;\");\n\n src.push(\"vec4 remapClipPos(vec4 clipPos) {\");\n src.push(\" clipPos.xy /= clipPos.w;\");\n src.push(` clipPos.xy = (clipPos.xy - pickClipPos) * drawingBufferSize;`);\n src.push(\" clipPos.xy *= clipPos.w;\");\n src.push(\" return clipPos;\");\n src.push(\"}\");\n\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.w = NOT_RENDERED | PICK\n // renderPass = PICK\n\n src.push(`if (int(flags.w) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n\n // get position\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n // get color\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n\n src.push(`if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\"};\");\n\n // get normal\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n\n // when the geometry is not solid, if needed, flip the triangle winding\n src.push(\"if (solid != 1u) {\");\n src.push(\"if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\"vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n src.push(\"if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"}\");\n src.push(\"} else {\");\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n src.push(\"if (viewNormal.z < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"}\");\n src.push(\"}\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags2 = flags2.r;\");\n }\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Triangles dataTexture pick depth fragment shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n src.push(\"uniform float pickZNear;\");\n src.push(\"uniform float pickZFar;\");\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (var i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec4 vViewPosition;\");\n src.push(\"vec4 packDepth(const in float depth) {\");\n src.push(\" const vec4 bitShift = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);\");\n src.push(\" const vec4 bitMask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);\");\n src.push(\" vec4 res = fract(depth * bitShift);\");\n src.push(\" res -= res.xxyz * bitMask;\");\n src.push(\" return res;\");\n src.push(\"}\");\n\n src.push(\"out vec4 outPackedDepth;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" float zNormalizedDepth = abs((pickZNear + vViewPosition.z) / (pickZFar - pickZNear));\");\n src.push(\" outPackedDepth = packDepth(zNormalizedDepth); \"); // Must be linear depth\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$g = math.vec3();\nconst tempVec3b$c = math.vec3();\nconst tempVec3c$a = math.vec3();\nconst tempVec3d$2 = math.vec3();\nmath.vec3();\nconst tempMat4a$6 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesSnapRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = dataTextureLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n const coordinateScaler = tempVec3a$g;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n \n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3b$c;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3c$a);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$6);\n rtcCameraEye = tempVec3d$2;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this.uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this.uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$g);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n const glMode = (frameCtx.snapMode === \"edge\") ? gl.LINES : gl.POINTS;\n if (state.numEdgeIndices8Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 8 // 8 bits edge indices\n );\n gl.drawArrays(glMode, 0, state.numEdgeIndices8Bits);\n }\n if (state.numEdgeIndices16Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 16 // 16 bits edge indices\n );\n gl.drawArrays(glMode, 0, state.numEdgeIndices16Bits);\n }\n if (state.numEdgeIndices32Bits > 0) {\n textureState.bindEdgeIndicesTextures(\n this._program,\n this._uTexturePerEdgeIdPortionIds,\n this._uTexturePerPolygonIdEdgeIndices,\n 32 // 32 bits edge indices\n );\n gl.drawArrays(glMode, 0, state.numEdgeIndices32Bits);\n }\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPickInvisible = program.getLocation(\"pickInvisible\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdEdgeIndices = \"uTexturePerPolygonIdEdgeIndices\";\n this._uTexturePerEdgeIdPortionIds = \"uTexturePerEdgeIdPortionIds\";\n this._uTextureModelMatrices = \"uTextureModelMatrices\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n this.uVectorA = program.getLocation(\"uSnapVectorA\");\n this.uInverseVectorAB = program.getLocation(\"uSnapInvVectorAB\");\n this._uLayerNumber = program.getLocation(\"uLayerNumber\");\n this._uCoordinateScaler = program.getLocation(\"uCoordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry edges drawing vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdEdgeIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerEdgeIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n src.push(\"uniform vec2 uSnapVectorA;\");\n src.push(\"uniform vec2 uSnapInvVectorAB;\");\n\n src.push(\"vec3 positions[3];\");\n\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - uSnapVectorA.x) * uSnapInvVectorAB.x;\");\n src.push(\" float y = (clipPos.y - uSnapVectorA.y) * uSnapInvVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n src.push(\"out vec4 vViewPosition;\");\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int edgeIndex = gl_VertexID / 2;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (edgeIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (edgeIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerEdgeIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n src.push(\"{\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n src.push(\"ivec4 packedEdgeIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+6, objectIndexCoords.y), 0));\");\n src.push(\"int edgeIndexBaseOffset = (packedEdgeIndexBaseOffset.r << 24) + (packedEdgeIndexBaseOffset.g << 16) + (packedEdgeIndexBaseOffset.b << 8) + packedEdgeIndexBaseOffset.a;\");\n \n src.push(\"int h_index = (edgeIndex - edgeIndexBaseOffset) & 4095;\");\n src.push(\"int v_index = (edgeIndex - edgeIndexBaseOffset) >> 12;\");\n \n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdEdgeIndices, ivec2(h_index, v_index), 0));\");\n \n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n \n src.push(\"int indexPositionH = uniqueVertexIndexes[gl_VertexID % 2] & 4095;\");\n src.push(\"int indexPositionV = uniqueVertexIndexes[gl_VertexID % 2] >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n \n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n \n src.push(\"vec3 position = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH, indexPositionV), 0));\");\n \n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags2 = flags2.r;\");\n }\n src.push(\"vViewPosition = viewPosition;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n src.push(\"vViewPosition = clipPos;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"gl_PointSize = 1.0;\"); // Windows needs this?\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Triangles dataTexture pick depth fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int uLayerNumber;\");\n src.push(\"uniform vec3 uCoordinateScaler;\");\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"out highp ivec4 outCoords;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz * uCoordinateScaler.xyz, uLayerNumber);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$f = math.vec3();\nconst tempVec3b$b = math.vec3();\nconst tempVec3c$9 = math.vec3();\nconst tempVec3d$1 = math.vec3();\nmath.vec3();\nconst tempMat4a$5 = math.mat4();\n/**\n * @private\n */\nclass DTXTrianglesSnapInitRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n if (!this._program) {\n this._allocate();\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const aabb = dataTextureLayer.aabb; // Per-layer AABB for best RTC accuracy\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n const coordinateScaler = tempVec3a$f;\n coordinateScaler[0] = math.safeInv(aabb[3] - aabb[0]) * math.MAX_INT;\n coordinateScaler[1] = math.safeInv(aabb[4] - aabb[1]) * math.MAX_INT;\n coordinateScaler[2] = math.safeInv(aabb[5] - aabb[2]) * math.MAX_INT;\n\n frameCtx.snapPickCoordinateScale[0] = math.safeInv(coordinateScaler[0]);\n frameCtx.snapPickCoordinateScale[1] = math.safeInv(coordinateScaler[1]);\n frameCtx.snapPickCoordinateScale[2] = math.safeInv(coordinateScaler[2]);\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n let rtcViewMatrix;\n let rtcCameraEye;\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3b$b;\n if (gotOrigin) {\n const rotatedOrigin = tempVec3c$9;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$5);\n rtcCameraEye = tempVec3d$1;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n frameCtx.snapPickOrigin[0] = rtcOrigin[0];\n frameCtx.snapPickOrigin[1] = rtcOrigin[1];\n frameCtx.snapPickOrigin[2] = rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n frameCtx.snapPickOrigin[0] = 0;\n frameCtx.snapPickOrigin[1] = 0;\n frameCtx.snapPickOrigin[2] = 0;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform2fv(this._uVectorA, frameCtx.snapVectorA);\n gl.uniform2fv(this._uInverseVectorAB, frameCtx.snapInvVectorAB);\n gl.uniform1i(this._uLayerNumber, frameCtx.snapPickLayerNumber);\n gl.uniform3fv(this._uCoordinateScaler, coordinateScaler);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniform1i(this._uPickInvisible, frameCtx.pickInvisible);\n gl.uniformMatrix4fv(this._uSceneWorldModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$f);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPickInvisible = program.getLocation(\"pickInvisible\");\n this._uSceneWorldModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n this._uVectorA = program.getLocation(\"uVectorAB\");\n this._uInverseVectorAB = program.getLocation(\"uInverseVectorAB\");\n this._uLayerNumber = program.getLocation(\"uLayerNumber\");\n this._uCoordinateScaler = program.getLocation(\"uCoordinateScaler\");\n }\n\n _bindProgram() {\n this._program.bind();\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// DTXTrianglesSnapInitRenderer vertex shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n src.push(\"uniform vec2 uVectorAB;\");\n src.push(\"uniform vec2 uInverseVectorAB;\");\n\n src.push(\"vec3 positions[3];\");\n\n {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n\n src.push(\"vec2 remapClipPos(vec2 clipPos) {\");\n src.push(\" float x = (clipPos.x - uVectorAB.x) * uInverseVectorAB.x;\");\n src.push(\" float y = (clipPos.y - uVectorAB.y) * uInverseVectorAB.y;\");\n src.push(\" return vec2(x, y);\");\n src.push(\"}\");\n\n src.push(\"flat out vec4 vPickColor;\");\n src.push(\"out vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"flat out uint vFlags2;\");\n }\n src.push(\"out highp vec3 relativeToOriginPosition;\");\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n src.push(\"{\");\n\n // get color\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n\n src.push(`if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\"};\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n\n // get position\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n // get normal\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n\n // when the geometry is not solid, if needed, flip the triangle winding\n src.push(\"if (solid != 1u) {\");\n src.push(\" if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\" vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n src.push(\" if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\" position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\" viewNormal = -viewNormal;\");\n src.push(\" }\");\n src.push(\" } else {\");\n src.push(\" if (viewNormal.z < 0.0) {\");\n src.push(\" position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\" viewNormal = -viewNormal;\");\n src.push(\" }\");\n src.push(\" }\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n src.push(\"relativeToOriginPosition = worldPosition.xyz;\");\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vWorldPosition = worldPosition;\");\n if (clipping) {\n src.push(\"vFlags2 = flags2.r;\");\n }\n src.push(\"vPickColor = vec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+1, objectIndexCoords.y), 0));\");\n\n // TODO: Normalized color? See here:\n //src.push(\"vPickColor = vec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+1, objectIndexCoords.y), 0)) /255.0;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n src.push(\"float tmp = clipPos.w;\");\n src.push(\"clipPos.xyzw /= tmp;\");\n src.push(\"clipPos.xy = remapClipPos(clipPos.xy);\");\n src.push(\"clipPos.xyzw *= tmp;\");\n {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// DTXTrianglesSnapInitRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"uniform int uLayerNumber;\");\n src.push(\"uniform vec3 uCoordinateScaler;\");\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in vec4 vPickColor;\");\n if (clipping) {\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in highp vec3 relativeToOriginPosition;\");\n src.push(\"layout(location = 0) out highp ivec4 outCoords;\");\n src.push(\"layout(location = 1) out highp ivec4 outNormal;\");\n src.push(\"layout(location = 2) out lowp uvec4 outPickColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n {\n src.push(\" float dx = dFdx(vFragDepth);\");\n src.push(\" float dy = dFdy(vFragDepth);\");\n src.push(\" float diff = sqrt(dx*dx+dy*dy);\");\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth + diff ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"outCoords = ivec4(relativeToOriginPosition.xyz * uCoordinateScaler.xyz, - uLayerNumber);\");\n\n src.push(\"vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n src.push(\"vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n src.push(\"vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(`outNormal = ivec4(worldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"outPickColor = uvec4(vPickColor);\");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$e = math.vec3();\nconst tempVec3b$a = math.vec3();\nconst tempVec3c$8 = math.vec3();\nmath.vec3();\nconst tempMat4a$4 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesOcclusionRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const viewMatrix = frameCtx.pickViewMatrix || camera.viewMatrix;\n\n if (!this._program) {\n this._allocate(dataTextureLayer);\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram();\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n if (origin || position[0] !== 0 || position[1] !== 0 || position[2] !== 0) {\n const rtcOrigin = tempVec3a$e;\n if (origin) {\n const rotatedOrigin = tempVec3b$a;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$4);\n rtcCameraEye = tempVec3c$8;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n }\n\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform1i(this._uRenderPass, renderPass);\n gl.uniformMatrix4fv(this._uWorldMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$e);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPickInvisible = program.getLocation(\"pickInvisible\");\n this._uWorldMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._uPickZNear = program.getLocation(\"pickZNear\");\n this._uPickZFar = program.getLocation(\"pickZFar\");\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdNormals = \"uTexturePerPolygonIdNormals\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram() {\n\n const scene = this._scene;\n scene.canvas.gl;\n scene.camera.project;\n\n this._program.bind();\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// TrianglesDataTextureOcclusionRenderer vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n\n src.push(\"vec3 positions[3];\");\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.x = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n // Only opaque objects can be occluders\n\n src.push(`if (int(flags.x) != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\"); // Cull vertex\n\n src.push(\" } else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n\n // get position\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n // get color\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n\n src.push(`if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\"};\");\n\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n\n // when the geometry is not solid, if needed, flip the triangle winding\n src.push(\"if (solid != 1u) {\");\n src.push(\" if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\" vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n src.push(\" if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\" position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\" }\");\n src.push(\" } else {\");\n src.push(\" vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n src.push(\" if (viewNormal.z < 0.0) {\");\n src.push(\" position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\" }\");\n src.push(\" }\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags2 = flags2.r;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const sectionPlanesState = scene._sectionPlanesState;\n const clipping = sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// TrianglesDataTextureColorRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out vec4 outColor;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (float(vFlags2) > 0.0);\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0; i < sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n src.push(\" outColor = vec4(0.0, 0.0, 1.0, 1.0); \"); // Occluders are blue\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$d = math.vec3();\nconst tempVec3b$9 = math.vec3();\nconst tempVec3c$7 = math.vec3();\nmath.vec3();\nconst tempMat4a$3 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesDepthRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._allocate();\n this._hash = this._getHash();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const scene = this._scene;\n const camera = scene.camera;\n const model = dataTextureLayer.model;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx, state);\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$d;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3b$9);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(camera.viewMatrix, rtcOrigin, tempMat4a$3);\n rtcCameraEye = tempVec3c$7;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n } else {\n rtcViewMatrix = camera.viewMatrix;\n rtcCameraEye = camera.eye;\n }\n\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform1i(this._uRenderPass, renderPass);\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$d);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n\n frameCtx.drawElements++;\n }\n\n _allocate() {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n\n this._program = new Program(gl, this._buildShader());\n\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n\n const program = this._program;\n\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPositionsDecodeMatrix = program.getLocation(\"objectDecodeAndInstanceMatrix\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdNormals = \"uTexturePerPolygonIdNormals\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix= \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram(frameCtx) {\n\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const project = scene.camera.project;\n\n program.bind();\n\n gl.uniformMatrix4fv(this._uProjMatrix, false, project.matrix);\n\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Triangles dataTexture draw vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n\n src.push(\"vec3 positions[3];\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n\n src.push(\"out highp vec2 vHighPrecisionZW;\");\n\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"flat out uint vFlags2;\");\n }\n\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\");\n\n // flags.x = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`if (int(flags.x) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n\n // get position\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n // get color\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n\n src.push(`if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\"};\");\n\n // get normal\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n\n // when the geometry is not solid, if needed, flip the triangle winding\n src.push(\"if (solid != 1u) {\");\n src.push(\"if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\"vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n src.push(\"if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"viewNormal = -viewNormal;\");\n src.push(\"}\");\n src.push(\"} else {\");\n src.push(\"if (viewNormal.z < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"viewNormal = -viewNormal;\");\n src.push(\"}\");\n src.push(\"}\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n if (clipping) {\n src.push(\"vWorldPosition = worldPosition;\");\n src.push(\"vFlags2 = flags2.r;\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\"vHighPrecisionZW = gl_Position.zw;\");\n src.push(\"}\");\n\n src.push(\"}\");\n\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// Triangles dataTexture draw fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n src.push(\"in highp vec2 vHighPrecisionZW;\");\n src.push(\"out vec4 outColor;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"void main(void) {\");\n\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n //src.push(\" gl_FragDepth = log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\"float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\");\n src.push(\" outColor = vec4(vec3(1.0 - fragCoordZ), 1.0); \");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$c = math.vec3();\nconst tempVec3b$8 = math.vec3();\nconst tempVec3c$6 = math.vec3();\nmath.vec3();\nconst tempMat4a$2 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesNormalsRenderer {\n\n constructor(scene) {\n this._scene = scene;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n return this._scene._sectionPlanesState.getHash();\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const model = dataTextureLayer.model;\n const scene = model.scene;\n const camera = scene.camera;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n const viewMatrix = camera.viewMatrix;\n\n if (!this._program) {\n this._allocate(dataTextureLayer);\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(dataTextureLayer);\n }\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$c;\n if (gotOrigin) {\n const rotatedOrigin = tempVec3b$8;\n math.transformPoint3(rotationMatrix, origin, rotatedOrigin);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(viewMatrix, rtcOrigin, tempMat4a$2);\n rtcCameraEye = tempVec3c$6;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n } else {\n rtcViewMatrix = viewMatrix;\n rtcCameraEye = camera.eye;\n }\n\n gl.uniform1i(this._uRenderPass, renderPass);\n\n gl.uniformMatrix4fv(this._uWorldMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix);\n\n gl.uniformMatrix4fv(this._uViewNormalMatrix, false, camera.viewNormalMatrix);\n gl.uniformMatrix4fv(this._uWorldNormalMatrix, false, model.worldNormalMatrix);\n\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$c);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, dataTextureLayer._state.objectDecodeAndInstanceMatrix);\n\n this._aPosition.bindArrayBuffer(state.positionsBuf);\n this._aOffset.bindArrayBuffer(state.offsetsBuf);\n this._aNormal.bindArrayBuffer(state.normalsBuf);\n this._aColor.bindArrayBuffer(state.colorsBuf);// Needed for masking out transparent entities using alpha channel\n this._aFlags.bindArrayBuffer(state.flagsBuf);\n if (this._aFlags2) {\n this._aFlags2.bindArrayBuffer(state.flags2Buf);\n }\n state.indicesBuf.bind();\n\n gl.drawElements(gl.TRIANGLES, state.indicesBuf.numItems, state.indicesBuf.itemType, 0);\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPositionsDecodeMatrix = program.getLocation(\"objectDecodeAndInstanceMatrix\");\n this._uWorldMatrix = program.getLocation(\"worldMatrix\");\n this._uWorldNormalMatrix = program.getLocation(\"worldNormalMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uViewNormalMatrix = program.getLocation(\"viewNormalMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n this._aPosition = program.getAttribute(\"position\");\n this._aOffset = program.getAttribute(\"offset\");\n this._aNormal = program.getAttribute(\"normal\");\n this._aColor = program.getAttribute(\"color\");\n this._aFlags = program.getAttribute(\"flags\");\n if (this._aFlags2) { // Won't be in shader when not clipping\n this._aFlags2 = program.getAttribute(\"flags2\");\n }\n if ( scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n }\n\n _bindProgram() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const project = scene.camera.project;\n this._program.bind();\n gl.uniformMatrix4fv(this._uProjMatrix, false, project.matrix);\n if ( scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"// Batched geometry normals vertex shader\");\n if (scene.logarithmicDepthBufferEnabled && WEBGL_INFO.SUPPORTED_EXTENSIONS[\"EXT_frag_depth\"]) {\n src.push(\"#extension GL_EXT_frag_depth : enable\");\n }\n src.push(\"uniform int renderPass;\");\n src.push(\"attribute vec3 position;\");\n if (scene.entityOffsetsEnabled) {\n src.push(\"attribute vec3 offset;\");\n }\n src.push(\"attribute vec3 normal;\");\n src.push(\"attribute vec4 color;\");\n src.push(\"attribute vec4 flags;\");\n src.push(\"attribute vec4 flags2;\");\n src.push(\"uniform mat4 worldMatrix;\");\n src.push(\"uniform mat4 worldNormalMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n src.push(\"uniform mat4 viewNormalMatrix;\");\n src.push(\"uniform mat4 objectDecodeAndInstanceMatrix;\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n if (WEBGL_INFO.SUPPORTED_EXTENSIONS[\"EXT_frag_depth\"]) {\n src.push(\"out float vFragDepth;\");\n }\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n src.push(\"varying float isPerspective;\");\n }\n src.push(\"vec3 octDecode(vec2 oct) {\");\n src.push(\" vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));\");\n src.push(\" if (v.z < 0.0) {\");\n src.push(\" v.xy = (1.0 - abs(v.yx)) * vec2(v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0);\");\n src.push(\" }\");\n src.push(\" return normalize(v);\");\n src.push(\"}\");\n if (clipping) {\n src.push(\"out vec4 vWorldPosition;\");\n src.push(\"out vec4 vFlags2;\");\n }\n src.push(\"out vec3 vViewNormal;\");\n src.push(\"void main(void) {\");\n\n // flags.x = NOT_RENDERED | COLOR_OPAQUE | COLOR_TRANSPARENT\n // renderPass = COLOR_OPAQUE\n\n src.push(`if (int(flags.x) != renderPass) {`);\n src.push(\" gl_Position = vec4(0.0, 0.0, 0.0, 0.0);\");\n\n src.push(\" } else {\");\n src.push(\" vec4 worldPosition = worldMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n if (scene.entityOffsetsEnabled) {\n src.push(\" worldPosition.xyz = worldPosition.xyz + offset;\");\n }\n src.push(\" vec4 viewPosition = viewMatrix * worldPosition; \");\n src.push(\" vec4 worldNormal = worldNormalMatrix * vec4(octDecode(normal.xy), 0.0); \");\n src.push(\" vec3 viewNormal = normalize((viewNormalMatrix * worldNormal).xyz);\");\n if (clipping) {\n src.push(\" vWorldPosition = worldPosition;\");\n src.push(\" vFlags2 = flags2;\");\n }\n src.push(\" vViewNormal = viewNormal;\");\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n if (scene.logarithmicDepthBufferEnabled) {\n if (WEBGL_INFO.SUPPORTED_EXTENSIONS[\"EXT_frag_depth\"]) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n } else {\n src.push(\"clipPos.z = log2( max( 1e-6, clipPos.w + 1.0 ) ) * logDepthBufFC - 1.0;\");\n src.push(\"clipPos.z *= clipPos.w;\");\n }\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n src.push(\"gl_Position = clipPos;\");\n src.push(\" }\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = (scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0);\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// Batched geometry normals fragment shader\");\n\n if (scene.logarithmicDepthBufferEnabled && WEBGL_INFO.SUPPORTED_EXTENSIONS[\"EXT_frag_depth\"]) {\n src.push(\"#extension GL_EXT_frag_depth : enable\");\n }\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n\n if (scene.logarithmicDepthBufferEnabled && WEBGL_INFO.SUPPORTED_EXTENSIONS[\"EXT_frag_depth\"]) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n\n if (clipping) {\n src.push(\"in vec4 vWorldPosition;\");\n src.push(\"in vec4 vFlags2;\");\n for (let i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"in vec3 vViewNormal;\");\n src.push(\"vec3 packNormalToRGB( const in vec3 normal ) {\");\n src.push(\" return normalize( normal ) * 0.5 + 0.5;\");\n src.push(\"}\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = (float(vFlags2.x) > 0.0);\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (var i = 0; i < scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i++) {\n src.push(\" if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\" }\");\n }\n src.push(\" if (dist > 0.0) { discard; }\");\n src.push(\" }\");\n }\n if (scene.logarithmicDepthBufferEnabled && WEBGL_INFO.SUPPORTED_EXTENSIONS[\"EXT_frag_depth\"]) {\n src.push(\" gl_FragDepthEXT = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" gl_FragColor = vec4(packNormalToRGB(vViewNormal), 1.0); \");\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\nconst tempVec3a$b = math.vec3();\nconst tempVec3b$7 = math.vec3();\nconst tempVec3c$5 = math.vec3();\nmath.vec3();\n\nmath.vec4();\n\nconst tempMat4a$1 = math.mat4();\n\n/**\n * @private\n */\nclass DTXTrianglesPickNormalsFlatRenderer {\n\n constructor(scene, withSAO) {\n this._scene = scene;\n this._withSAO = withSAO;\n this._hash = this._getHash();\n this._allocate();\n }\n\n getValid() {\n return this._hash === this._getHash();\n };\n\n _getHash() {\n const scene = this._scene;\n return [scene._lightsState.getHash(), scene._sectionPlanesState.getHash(), (this._withSAO ? \"sao\" : \"nosao\")].join(\";\");\n }\n\n drawLayer(frameCtx, dataTextureLayer, renderPass) {\n\n const scene = this._scene;\n const camera = scene.camera;\n const model = dataTextureLayer.model;\n const gl = scene.canvas.gl;\n const state = dataTextureLayer._state;\n const textureState = state.textureState;\n const origin = dataTextureLayer._state.origin;\n const {position, rotationMatrix, rotationMatrixConjugate} = model;\n\n if (!this._program) {\n this._allocate();\n if (this.errors) {\n return;\n }\n }\n\n if (frameCtx.lastProgramId !== this._program.id) {\n frameCtx.lastProgramId = this._program.id;\n this._bindProgram(frameCtx, state);\n }\n\n textureState.bindCommonTextures(\n this._program,\n this.uTexturePerObjectPositionsDecodeMatrix,\n this._uTexturePerVertexIdCoordinates,\n this.uTexturePerObjectColorsAndFlags,\n this._uTexturePerObjectMatrix\n );\n\n let rtcViewMatrix;\n let rtcCameraEye;\n\n const gotOrigin = (origin[0] !== 0 || origin[1] !== 0 || origin[2] !== 0);\n const gotPosition = (position[0] !== 0 || position[1] !== 0 || position[2] !== 0);\n if (gotOrigin || gotPosition) {\n const rtcOrigin = tempVec3a$b;\n if (gotOrigin) {\n const rotatedOrigin = math.transformPoint3(rotationMatrix, origin, tempVec3b$7);\n rtcOrigin[0] = rotatedOrigin[0];\n rtcOrigin[1] = rotatedOrigin[1];\n rtcOrigin[2] = rotatedOrigin[2];\n } else {\n rtcOrigin[0] = 0;\n rtcOrigin[1] = 0;\n rtcOrigin[2] = 0;\n }\n rtcOrigin[0] += position[0];\n rtcOrigin[1] += position[1];\n rtcOrigin[2] += position[2];\n rtcViewMatrix = createRTCViewMat(camera.viewMatrix, rtcOrigin, tempMat4a$1);\n rtcCameraEye = tempVec3c$5;\n rtcCameraEye[0] = camera.eye[0] - rtcOrigin[0];\n rtcCameraEye[1] = camera.eye[1] - rtcOrigin[1];\n rtcCameraEye[2] = camera.eye[2] - rtcOrigin[2];\n } else {\n rtcViewMatrix = camera.viewMatrix; // TODO: make pickMatrix\n rtcCameraEye = camera.eye;\n }\n gl.uniform2fv(this._uPickClipPos, frameCtx.pickClipPos);\n gl.uniform2f(this._uDrawingBufferSize, gl.drawingBufferWidth, gl.drawingBufferHeight);\n gl.uniformMatrix4fv(this._uSceneModelMatrix, false, rotationMatrixConjugate);\n gl.uniformMatrix4fv(this._uViewMatrix, false, rtcViewMatrix);\n gl.uniformMatrix4fv(this._uProjMatrix, false, camera.projMatrix); // TODO: pickProjMatrix\n gl.uniform3fv(this._uCameraEyeRtc, rtcCameraEye);\n gl.uniform1i(this._uRenderPass, renderPass);\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(frameCtx.pickZFar + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();\n const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;\n if (numAllocatedSectionPlanes > 0) {\n const sectionPlanes = scene._sectionPlanesState.sectionPlanes;\n const baseIndex = dataTextureLayer.layerIndex * numSectionPlanes;\n const renderFlags = model.renderFlags;\n for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {\n const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];\n if (sectionPlaneUniforms) {\n if (sectionPlaneIndex < numSectionPlanes) {\n const active = renderFlags.sectionPlanesActivePerLayer[baseIndex + sectionPlaneIndex];\n gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);\n if (active) {\n const sectionPlane = sectionPlanes[sectionPlaneIndex];\n if (origin) {\n const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a$b);\n gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);\n } else {\n gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);\n }\n gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);\n }\n } else {\n gl.uniform1i(sectionPlaneUniforms.active, 0);\n }\n }\n }\n }\n\n if (state.numIndices8Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 8 // 8 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices8Bits);\n }\n\n if (state.numIndices16Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 16 // 16 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices16Bits);\n }\n\n if (state.numIndices32Bits > 0) {\n textureState.bindTriangleIndicesTextures(\n this._program,\n this._uTexturePerPolygonIdPortionIds,\n this._uTexturePerPolygonIdIndices,\n 32 // 32 bits indices\n );\n gl.drawArrays(gl.TRIANGLES, 0, state.numIndices32Bits);\n }\n\n frameCtx.drawElements++;\n }\n\n _allocate() {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n this._program = new Program(gl, this._buildShader());\n if (this._program.errors) {\n this.errors = this._program.errors;\n return;\n }\n const program = this._program;\n this._uRenderPass = program.getLocation(\"renderPass\");\n this._uPickInvisible = program.getLocation(\"pickInvisible\");\n this._uPickClipPos = program.getLocation(\"pickClipPos\");\n this._uDrawingBufferSize = program.getLocation(\"drawingBufferSize\");\n this._uSceneModelMatrix = program.getLocation(\"sceneModelMatrix\");\n this._uViewMatrix = program.getLocation(\"viewMatrix\");\n this._uProjMatrix = program.getLocation(\"projMatrix\");\n this._uSectionPlanes = [];\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n this._uSectionPlanes.push({\n active: program.getLocation(\"sectionPlaneActive\" + i),\n pos: program.getLocation(\"sectionPlanePos\" + i),\n dir: program.getLocation(\"sectionPlaneDir\" + i)\n });\n }\n if (scene.logarithmicDepthBufferEnabled) {\n this._uLogDepthBufFC = program.getLocation(\"logDepthBufFC\");\n }\n this.uTexturePerObjectPositionsDecodeMatrix = \"uObjectPerObjectPositionsDecodeMatrix\";\n this.uTexturePerObjectColorsAndFlags = \"uObjectPerObjectColorsAndFlags\";\n this._uTexturePerVertexIdCoordinates = \"uTexturePerVertexIdCoordinates\";\n this._uTexturePerPolygonIdNormals = \"uTexturePerPolygonIdNormals\";\n this._uTexturePerPolygonIdIndices = \"uTexturePerPolygonIdIndices\";\n this._uTexturePerPolygonIdPortionIds = \"uTexturePerPolygonIdPortionIds\";\n this._uTexturePerObjectMatrix = \"uTexturePerObjectMatrix\";\n this._uCameraEyeRtc = program.getLocation(\"uCameraEyeRtc\");\n }\n\n _bindProgram(frameCtx) {\n const scene = this._scene;\n const gl = scene.canvas.gl;\n const program = this._program;\n const project = scene.camera.project;\n program.bind();\n if (scene.logarithmicDepthBufferEnabled) {\n const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);\n gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);\n }\n }\n\n _buildShader() {\n return {\n vertex: this._buildVertexShader(),\n fragment: this._buildFragmentShader()\n };\n }\n\n _buildVertexShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push(\"#version 300 es\");\n src.push(\"// trianglesDatatextureNormalsRenderer vertex shader\");\n\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"precision highp usampler2D;\");\n src.push(\"precision highp isampler2D;\");\n src.push(\"precision highp sampler2D;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"precision mediump usampler2D;\");\n src.push(\"precision mediump isampler2D;\");\n src.push(\"precision mediump sampler2D;\");\n src.push(\"#endif\");\n\n src.push(\"uniform int renderPass;\");\n\n if (scene.entityOffsetsEnabled) {\n src.push(\"in vec3 offset;\");\n }\n\n src.push(\"uniform mat4 sceneModelMatrix;\");\n src.push(\"uniform mat4 viewMatrix;\");\n src.push(\"uniform mat4 projMatrix;\");\n\n src.push(\"uniform highp sampler2D uObjectPerObjectPositionsDecodeMatrix;\");\n src.push(\"uniform lowp usampler2D uObjectPerObjectColorsAndFlags;\");\n src.push(\"uniform highp sampler2D uTexturePerObjectMatrix;\");\n src.push(\"uniform mediump usampler2D uTexturePerVertexIdCoordinates;\");\n src.push(\"uniform highp usampler2D uTexturePerPolygonIdIndices;\");\n src.push(\"uniform mediump usampler2D uTexturePerPolygonIdPortionIds;\");\n src.push(\"uniform vec3 uCameraEyeRtc;\");\n\n src.push(\"vec3 positions[3];\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"out float vFragDepth;\");\n src.push(\"out float isPerspective;\");\n }\n\n src.push(\"uniform vec2 pickClipPos;\");\n src.push(\"uniform vec2 drawingBufferSize;\");\n\n src.push(\"vec4 remapClipPos(vec4 clipPos) {\");\n src.push(\" clipPos.xy /= clipPos.w;\");\n src.push(` clipPos.xy = (clipPos.xy - pickClipPos) * drawingBufferSize;`);\n src.push(\" clipPos.xy *= clipPos.w;\");\n src.push(\" return clipPos;\");\n src.push(\"}\");\n\n src.push(\"bool isPerspectiveMatrix(mat4 m) {\");\n src.push(\" return (m[2][3] == - 1.0);\");\n src.push(\"}\");\n\n src.push(\"out vec4 vWorldPosition;\");\n\n if (clipping) {\n src.push(\"flat out uint vFlags2;\");\n }\n\n src.push(\"void main(void) {\");\n\n // constants\n src.push(\"int polygonIndex = gl_VertexID / 3;\");\n\n // get packed object-id\n src.push(\"int h_packed_object_id_index = (polygonIndex >> 3) & 4095;\");\n src.push(\"int v_packed_object_id_index = (polygonIndex >> 3) >> 12;\");\n\n src.push(\"int objectIndex = int(texelFetch(uTexturePerPolygonIdPortionIds, ivec2(h_packed_object_id_index, v_packed_object_id_index), 0).r);\");\n src.push(\"ivec2 objectIndexCoords = ivec2(objectIndex % 512, objectIndex / 512);\");\n\n // get flags & flags2\n src.push(\"uvec4 flags = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+2, objectIndexCoords.y), 0);\");\n src.push(\"uvec4 flags2 = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+3, objectIndexCoords.y), 0);\"); // pickFlag = NOT_RENDERED | PICK\n\n // renderPass = PICK\n src.push(`if (int(flags.w) != renderPass) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\"); // Cull vertex\n src.push(\"} else {\");\n\n // get vertex base\n src.push(\"ivec4 packedVertexBase = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+4, objectIndexCoords.y), 0));\");\n src.push(\"ivec4 packedIndexBaseOffset = ivec4(texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+5, objectIndexCoords.y), 0));\");\n src.push(\"int indexBaseOffset = (packedIndexBaseOffset.r << 24) + (packedIndexBaseOffset.g << 16) + (packedIndexBaseOffset.b << 8) + packedIndexBaseOffset.a;\");\n src.push(\"int h_index = (polygonIndex - indexBaseOffset) & 4095;\");\n src.push(\"int v_index = (polygonIndex - indexBaseOffset) >> 12;\");\n src.push(\"ivec3 vertexIndices = ivec3(texelFetch(uTexturePerPolygonIdIndices, ivec2(h_index, v_index), 0));\");\n src.push(\"ivec3 uniqueVertexIndexes = vertexIndices + (packedVertexBase.r << 24) + (packedVertexBase.g << 16) + (packedVertexBase.b << 8) + packedVertexBase.a;\");\n src.push(\"ivec3 indexPositionH = uniqueVertexIndexes & 4095;\");\n src.push(\"ivec3 indexPositionV = uniqueVertexIndexes >> 12;\");\n\n src.push(\"mat4 objectInstanceMatrix = mat4 (texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uTexturePerObjectMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"mat4 objectDecodeAndInstanceMatrix = objectInstanceMatrix * mat4 (texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+0, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+1, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+2, objectIndexCoords.y), 0), texelFetch (uObjectPerObjectPositionsDecodeMatrix, ivec2(objectIndexCoords.x*4+3, objectIndexCoords.y), 0));\");\n\n src.push(\"uint solid = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+7, objectIndexCoords.y), 0).r;\");\n // get position\n src.push(\"positions[0] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.r, indexPositionV.r), 0));\");\n src.push(\"positions[1] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.g, indexPositionV.g), 0));\");\n src.push(\"positions[2] = vec3(texelFetch(uTexturePerVertexIdCoordinates, ivec2(indexPositionH.b, indexPositionV.b), 0));\");\n\n // get color\n src.push(\"uvec4 color = texelFetch (uObjectPerObjectColorsAndFlags, ivec2(objectIndexCoords.x*8+0, objectIndexCoords.y), 0);\");\n\n src.push(`if (color.a == 0u) {`);\n src.push(\" gl_Position = vec4(3.0, 3.0, 3.0, 1.0);\"); // Cull vertex\n src.push(\" return;\");\n src.push(\"};\");\n\n // get normal\n\n src.push(\"vec3 normal = normalize(cross(positions[2] - positions[0], positions[1] - positions[0]));\");\n\n src.push(\"vec3 position;\");\n src.push(\"position = positions[gl_VertexID % 3];\");\n\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n\n // when the geometry is not solid, if needed, flip the triangle winding\n src.push(\"if (solid != 1u) {\");\n src.push(\"if (isPerspectiveMatrix(projMatrix)) {\");\n src.push(\"vec3 uCameraEyeRtcInQuantizedSpace = (inverse(sceneModelMatrix * objectDecodeAndInstanceMatrix) * vec4(uCameraEyeRtc, 1)).xyz;\");\n // src.push(\"vColor = vec4(vec3(1, -1, 0)*dot(normalize(position.xyz - uCameraEyeRtcInQuantizedSpace), normal), 1);\")\n src.push(\"if (dot(position.xyz - uCameraEyeRtcInQuantizedSpace, normal) < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"viewNormal = -viewNormal;\");\n src.push(\"}\");\n src.push(\"} else {\");\n src.push(\"vec3 viewNormal = -normalize((transpose(inverse(viewMatrix*objectDecodeAndInstanceMatrix)) * vec4(normal,1)).xyz);\");\n src.push(\"if (viewNormal.z < 0.0) {\");\n src.push(\"position = positions[2 - (gl_VertexID % 3)];\");\n src.push(\"}\");\n src.push(\"}\");\n src.push(\"}\");\n\n src.push(\"vec4 worldPosition = sceneModelMatrix * (objectDecodeAndInstanceMatrix * vec4(position, 1.0)); \");\n\n src.push(\"vec4 viewPosition = viewMatrix * worldPosition; \");\n\n src.push(\"vec4 clipPos = projMatrix * viewPosition;\");\n\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"vFragDepth = 1.0 + clipPos.w;\");\n src.push(\"isPerspective = float (isPerspectiveMatrix(projMatrix));\");\n }\n\n src.push(\"vWorldPosition = worldPosition;\");\n\n if (clipping) {\n src.push(\"vFlags2 = flags2.r;\");\n }\n src.push(\"gl_Position = remapClipPos(clipPos);\");\n src.push(\"}\");\n src.push(\"}\");\n return src;\n }\n\n _buildFragmentShader() {\n const scene = this._scene;\n const clipping = scene._sectionPlanesState.getNumAllocatedSectionPlanes() > 0;\n const src = [];\n src.push('#version 300 es');\n src.push(\"// TrianglesDataTexturePickNormalsRenderer fragment shader\");\n src.push(\"#ifdef GL_FRAGMENT_PRECISION_HIGH\");\n src.push(\"precision highp float;\");\n src.push(\"precision highp int;\");\n src.push(\"#else\");\n src.push(\"precision mediump float;\");\n src.push(\"precision mediump int;\");\n src.push(\"#endif\");\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\"in float isPerspective;\");\n src.push(\"uniform float logDepthBufFC;\");\n src.push(\"in float vFragDepth;\");\n }\n src.push(\"in vec4 vWorldPosition;\");\n if (clipping) {\n src.push(\"flat in uint vFlags2;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"uniform bool sectionPlaneActive\" + i + \";\");\n src.push(\"uniform vec3 sectionPlanePos\" + i + \";\");\n src.push(\"uniform vec3 sectionPlaneDir\" + i + \";\");\n }\n }\n src.push(\"out highp ivec4 outNormal;\");\n src.push(\"void main(void) {\");\n if (clipping) {\n src.push(\" bool clippable = vFlags2 > 0u;\");\n src.push(\" if (clippable) {\");\n src.push(\" float dist = 0.0;\");\n for (let i = 0, len = scene._sectionPlanesState.getNumAllocatedSectionPlanes(); i < len; i++) {\n src.push(\"if (sectionPlaneActive\" + i + \") {\");\n src.push(\" dist += clamp(dot(-sectionPlaneDir\" + i + \".xyz, vWorldPosition.xyz - sectionPlanePos\" + i + \".xyz), 0.0, 1000.0);\");\n src.push(\"}\");\n }\n src.push(\" if (dist > 0.0) { \");\n src.push(\" discard;\");\n src.push(\" }\");\n src.push(\"}\");\n }\n if (scene.logarithmicDepthBufferEnabled) {\n src.push(\" gl_FragDepth = isPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\");\n }\n src.push(\" vec3 xTangent = dFdx( vWorldPosition.xyz );\");\n src.push(\" vec3 yTangent = dFdy( vWorldPosition.xyz );\");\n src.push(\" vec3 worldNormal = normalize( cross( xTangent, yTangent ) );\");\n src.push(` outNormal = ivec4(worldNormal * float(${math.MAX_INT}), 1.0);`);\n src.push(\"}\");\n return src;\n }\n\n webglContextRestored() {\n this._program = null;\n }\n\n destroy() {\n if (this._program) {\n this._program.destroy();\n }\n this._program = null;\n }\n}\n\n/**\n * @private\n */\nclass DTXTrianglesRenderers {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n _compile() {\n if (this._colorRenderer && (!this._colorRenderer.getValid())) {\n this._colorRenderer.destroy();\n this._colorRenderer = null;\n }\n if (this._colorRendererWithSAO && (!this._colorRendererWithSAO.getValid())) {\n this._colorRendererWithSAO.destroy();\n this._colorRendererWithSAO = null;\n }\n if (this._flatColorRenderer && (!this._flatColorRenderer.getValid())) {\n this._flatColorRenderer.destroy();\n this._flatColorRenderer = null;\n }\n if (this._flatColorRendererWithSAO && (!this._flatColorRendererWithSAO.getValid())) {\n this._flatColorRendererWithSAO.destroy();\n this._flatColorRendererWithSAO = null;\n }\n if (this._colorQualityRendererWithSAO && (!this._colorQualityRendererWithSAO.getValid())) {\n this._colorQualityRendererWithSAO.destroy();\n this._colorQualityRendererWithSAO = null;\n }\n if (this._depthRenderer && (!this._depthRenderer.getValid())) {\n this._depthRenderer.destroy();\n this._depthRenderer = null;\n }\n if (this._normalsRenderer && (!this._normalsRenderer.getValid())) {\n this._normalsRenderer.destroy();\n this._normalsRenderer = null;\n }\n if (this._silhouetteRenderer && (!this._silhouetteRenderer.getValid())) {\n this._silhouetteRenderer.destroy();\n this._silhouetteRenderer = null;\n }\n if (this._edgesRenderer && (!this._edgesRenderer.getValid())) {\n this._edgesRenderer.destroy();\n this._edgesRenderer = null;\n }\n if (this._edgesColorRenderer && (!this._edgesColorRenderer.getValid())) {\n this._edgesColorRenderer.destroy();\n this._edgesColorRenderer = null;\n }\n if (this._pickMeshRenderer && (!this._pickMeshRenderer.getValid())) {\n this._pickMeshRenderer.destroy();\n this._pickMeshRenderer = null;\n }\n if (this._pickDepthRenderer && (!this._pickDepthRenderer.getValid())) {\n this._pickDepthRenderer.destroy();\n this._pickDepthRenderer = null;\n }\n if (this._snapRenderer && (!this._snapRenderer.getValid())) {\n this._snapRenderer.destroy();\n this._snapRenderer = null;\n }\n if (this._snapInitRenderer && (!this._snapInitRenderer.getValid())) {\n this._snapInitRenderer.destroy();\n this._snapInitRenderer = null;\n }\n if (this._pickNormalsRenderer && this._pickNormalsRenderer.getValid() === false) {\n this._pickNormalsRenderer.destroy();\n this._pickNormalsRenderer = null;\n }\n if (this._pickNormalsFlatRenderer && this._pickNormalsFlatRenderer.getValid() === false) {\n this._pickNormalsFlatRenderer.destroy();\n this._pickNormalsFlatRenderer = null;\n }\n if (this._occlusionRenderer && this._occlusionRenderer.getValid() === false) {\n this._occlusionRenderer.destroy();\n this._occlusionRenderer = null;\n }\n }\n\n eagerCreateRenders() {\n\n // Pre-initialize certain renderers that would otherwise be lazy-initialised\n // on user interaction, such as picking or emphasis, so that there is no delay\n // when user first begins interacting with the viewer.\n\n if (!this._silhouetteRenderer) { // Used for highlighting and selection\n this._silhouetteRenderer = new DTXTrianglesSilhouetteRenderer(this._scene);\n }\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new DTXTrianglesPickMeshRenderer(this._scene);\n }\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new DTXTrianglesPickDepthRenderer(this._scene);\n }\n if (!this._pickNormalsRenderer) {\n this._pickNormalsRenderer = new DTXTrianglesPickNormalsFlatRenderer(this._scene);\n }\n if (!this._snapRenderer) {\n this._snapRenderer = new DTXTrianglesSnapRenderer(this._scene);\n }\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new DTXTrianglesSnapInitRenderer(this._scene);\n }\n if (!this._snapRenderer) {\n this._snapRenderer = new DTXTrianglesSnapRenderer(this._scene);\n }\n }\n\n\n get colorRenderer() {\n if (!this._colorRenderer) {\n this._colorRenderer = new DTXTrianglesColorRenderer(this._scene, false);\n }\n return this._colorRenderer;\n }\n\n get colorRendererWithSAO() {\n if (!this._colorRendererWithSAO) {\n this._colorRendererWithSAO = new DTXTrianglesColorRenderer(this._scene, true);\n }\n return this._colorRendererWithSAO;\n }\n\n get colorQualityRendererWithSAO() {\n // if (!this._colorQualityRendererWithSAO) {\n // this._colorQualityRendererWithSAO = new TrianglesDataTextureColorQualityRenderer(this._scene, true);\n // }\n return this._colorQualityRendererWithSAO;\n }\n\n get silhouetteRenderer() {\n if (!this._silhouetteRenderer) {\n this._silhouetteRenderer = new DTXTrianglesSilhouetteRenderer(this._scene);\n }\n return this._silhouetteRenderer;\n }\n\n get depthRenderer() {\n if (!this._depthRenderer) {\n this._depthRenderer = new DTXTrianglesDepthRenderer(this._scene);\n }\n return this._depthRenderer;\n }\n\n get normalsRenderer() {\n if (!this._normalsRenderer) {\n this._normalsRenderer = new DTXTrianglesNormalsRenderer(this._scene);\n }\n return this._normalsRenderer;\n }\n\n get edgesRenderer() {\n if (!this._edgesRenderer) {\n this._edgesRenderer = new DTXTrianglesEdgesRenderer(this._scene);\n }\n return this._edgesRenderer;\n }\n\n get edgesColorRenderer() {\n if (!this._edgesColorRenderer) {\n this._edgesColorRenderer = new DTXTrianglesEdgesColorRenderer(this._scene);\n }\n return this._edgesColorRenderer;\n }\n\n get pickMeshRenderer() {\n if (!this._pickMeshRenderer) {\n this._pickMeshRenderer = new DTXTrianglesPickMeshRenderer(this._scene);\n }\n return this._pickMeshRenderer;\n }\n\n get pickNormalsRenderer() {\n if (!this._pickNormalsRenderer) {\n this._pickNormalsRenderer = new DTXTrianglesPickNormalsFlatRenderer(this._scene);\n }\n return this._pickNormalsRenderer;\n }\n\n get pickNormalsFlatRenderer() {\n if (!this._pickNormalsFlatRenderer) {\n this._pickNormalsFlatRenderer = new DTXTrianglesPickNormalsFlatRenderer(this._scene);\n }\n return this._pickNormalsFlatRenderer;\n }\n\n get pickDepthRenderer() {\n if (!this._pickDepthRenderer) {\n this._pickDepthRenderer = new DTXTrianglesPickDepthRenderer(this._scene);\n }\n return this._pickDepthRenderer;\n }\n\n get snapRenderer() {\n if (!this._snapRenderer) {\n this._snapRenderer = new DTXTrianglesSnapRenderer(this._scene);\n }\n return this._snapRenderer;\n }\n\n get snapInitRenderer() {\n if (!this._snapInitRenderer) {\n this._snapInitRenderer = new DTXTrianglesSnapInitRenderer(this._scene);\n }\n return this._snapInitRenderer;\n }\n\n get occlusionRenderer() {\n if (!this._occlusionRenderer) {\n this._occlusionRenderer = new DTXTrianglesOcclusionRenderer(this._scene);\n }\n return this._occlusionRenderer;\n }\n\n _destroy() {\n if (this._colorRenderer) {\n this._colorRenderer.destroy();\n }\n if (this._colorRendererWithSAO) {\n this._colorRendererWithSAO.destroy();\n }\n if (this._flatColorRenderer) {\n this._flatColorRenderer.destroy();\n }\n if (this._flatColorRendererWithSAO) {\n this._flatColorRendererWithSAO.destroy();\n }\n if (this._colorQualityRendererWithSAO) {\n this._colorQualityRendererWithSAO.destroy();\n }\n if (this._depthRenderer) {\n this._depthRenderer.destroy();\n }\n if (this._normalsRenderer) {\n this._normalsRenderer.destroy();\n }\n if (this._silhouetteRenderer) {\n this._silhouetteRenderer.destroy();\n }\n if (this._edgesRenderer) {\n this._edgesRenderer.destroy();\n }\n if (this._edgesColorRenderer) {\n this._edgesColorRenderer.destroy();\n }\n if (this._pickMeshRenderer) {\n this._pickMeshRenderer.destroy();\n }\n if (this._pickDepthRenderer) {\n this._pickDepthRenderer.destroy();\n }\n if (this._snapRenderer) {\n this._snapRenderer.destroy();\n }\n if (this._snapInitRenderer) {\n this._snapInitRenderer.destroy();\n }\n if (this._pickNormalsRenderer) {\n this._pickNormalsRenderer.destroy();\n }\n if (this._pickNormalsFlatRenderer) {\n this._pickNormalsFlatRenderer.destroy();\n }\n if (this._occlusionRenderer) {\n this._occlusionRenderer.destroy();\n }\n }\n}\n\nconst cachdRenderers = {};\n\n/**\n * @private\n */\nfunction getRenderers(scene) {\n const sceneId = scene.id;\n let dataTextureRenderers = cachdRenderers[sceneId];\n if (!dataTextureRenderers) {\n dataTextureRenderers = new DTXTrianglesRenderers(scene);\n cachdRenderers[sceneId] = dataTextureRenderers;\n dataTextureRenderers._compile();\n dataTextureRenderers.eagerCreateRenders();\n scene.on(\"compile\", () => {\n dataTextureRenderers._compile();\n dataTextureRenderers.eagerCreateRenders();\n });\n scene.on(\"destroyed\", () => {\n delete cachdRenderers[sceneId];\n dataTextureRenderers._destroy();\n });\n }\n return dataTextureRenderers;\n}\n\n/**\n * @private\n */\nclass DTXTrianglesBuffer {\n\n constructor() {\n this.positionsCompressed = [];\n this.lenPositionsCompressed = 0;\n\n this.metallicRoughness = [];\n\n this.indices8Bits = [];\n this.lenIndices8Bits = 0;\n\n this.indices16Bits = [];\n this.lenIndices16Bits = 0;\n\n this.indices32Bits = [];\n this.lenIndices32Bits = 0;\n\n this.edgeIndices8Bits = [];\n this.lenEdgeIndices8Bits = 0;\n\n this.edgeIndices16Bits = [];\n this.lenEdgeIndices16Bits = 0;\n\n this.edgeIndices32Bits = [];\n this.lenEdgeIndices32Bits = 0;\n\n this.perObjectColors = [];\n this.perObjectPickColors = [];\n this.perObjectSolid = [];\n this.perObjectOffsets = [];\n this.perObjectPositionsDecodeMatrices = [];\n this.perObjectInstancePositioningMatrices = [];\n this.perObjectVertexBases = [];\n this.perObjectIndexBaseOffsets = [];\n this.perObjectEdgeIndexBaseOffsets = [];\n this.perTriangleNumberPortionId8Bits = [];\n this.perTriangleNumberPortionId16Bits = [];\n this.perTriangleNumberPortionId32Bits = [];\n this.perEdgeNumberPortionId8Bits = [];\n this.perEdgeNumberPortionId16Bits = [];\n this.perEdgeNumberPortionId32Bits = [];\n }\n}\n\n// Imports used to complete the JSDocs arguments to methods\n\n/**\n * @private\n */\nclass DTXTrianglesState {\n constructor() {\n /**\n * Texture that holds colors/pickColors/flags/flags2 per-object:\n * - columns: one concept per column => color / pick-color / ...\n * - row: the object Id\n *\n * @type BindableDataTexture\n */\n this.texturePerObjectColorsAndFlags = null;\n\n /**\n * Texture that holds the XYZ offsets per-object:\n * - columns: just 1 column with the XYZ-offset\n * - row: the object Id\n *\n * @type BindableDataTexture\n */\n this.texturePerObjectOffsets = null;\n\n this.texturePerObjectInstanceMatrices = null;\n\n /**\n * Texture that holds the objectDecodeAndInstanceMatrix per-object:\n * - columns: each column is one column of the matrix\n * - row: the object Id\n *\n * @type BindableDataTexture\n */\n this.texturePerObjectPositionsDecodeMatrix = null;\n\n /**\n * Texture that holds all the `different-vertices` used by the layer.\n *\n * @type BindableDataTexture\n */\n this.texturePerVertexIdCoordinates = null;\n\n /**\n * Texture that holds the PortionId that corresponds to a given polygon-id.\n *\n * Variant of the texture for 8-bit based polygon-ids.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdPortionIds8Bits = null;\n\n /**\n * Texture that holds the PortionId that corresponds to a given polygon-id.\n *\n * Variant of the texture for 16-bit based polygon-ids.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdPortionIds16Bits = null;\n\n /**\n * Texture that holds the PortionId that corresponds to a given polygon-id.\n *\n * Variant of the texture for 32-bit based polygon-ids.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdPortionIds32Bits = null;\n\n /**\n * Texture that holds the PortionId that corresponds to a given edge-id.\n *\n * Variant of the texture for 8-bit based polygon-ids.\n *\n * @type BindableDataTexture\n */\n this.texturePerEdgeIdPortionIds8Bits = null;\n\n /**\n * Texture that holds the PortionId that corresponds to a given edge-id.\n *\n * Variant of the texture for 16-bit based polygon-ids.\n *\n * @type BindableDataTexture\n */\n this.texturePerEdgeIdPortionIds16Bits = null;\n\n /**\n * Texture that holds the PortionId that corresponds to a given edge-id.\n *\n * Variant of the texture for 32-bit based polygon-ids.\n *\n * @type BindableDataTexture\n */\n this.texturePerEdgeIdPortionIds32Bits = null;\n\n /**\n * Texture that holds the unique-vertex-indices for 8-bit based indices.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdIndices8Bits = null;\n\n /**\n * Texture that holds the unique-vertex-indices for 16-bit based indices.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdIndices16Bits = null;\n\n /**\n * Texture that holds the unique-vertex-indices for 32-bit based indices.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdIndices32Bits = null;\n\n /**\n * Texture that holds the unique-vertex-indices for 8-bit based edge indices.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdEdgeIndices8Bits = null;\n\n /**\n * Texture that holds the unique-vertex-indices for 16-bit based edge indices.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdEdgeIndices16Bits = null;\n\n /**\n * Texture that holds the unique-vertex-indices for 32-bit based edge indices.\n *\n * @type BindableDataTexture\n */\n this.texturePerPolygonIdEdgeIndices32Bits = null;\n\n /**\n * Texture that holds the model matrices\n * - columns: each column in the texture is a model matrix column.\n * - row: each row is a different model matrix.\n *\n * @type BindableDataTexture\n */\n this.textureModelMatrices = null;\n }\n\n finalize() {\n this.indicesPerBitnessTextures = {\n 8: this.texturePerPolygonIdIndices8Bits,\n 16: this.texturePerPolygonIdIndices16Bits,\n 32: this.texturePerPolygonIdIndices32Bits,\n };\n\n this.indicesPortionIdsPerBitnessTextures = {\n 8: this.texturePerPolygonIdPortionIds8Bits,\n 16: this.texturePerPolygonIdPortionIds16Bits,\n 32: this.texturePerPolygonIdPortionIds32Bits,\n };\n\n this.edgeIndicesPerBitnessTextures = {\n 8: this.texturePerPolygonIdEdgeIndices8Bits,\n 16: this.texturePerPolygonIdEdgeIndices16Bits,\n 32: this.texturePerPolygonIdEdgeIndices32Bits,\n };\n\n this.edgeIndicesPortionIdsPerBitnessTextures = {\n 8: this.texturePerEdgeIdPortionIds8Bits,\n 16: this.texturePerEdgeIdPortionIds16Bits,\n 32: this.texturePerEdgeIdPortionIds32Bits,\n };\n }\n\n /**\n *\n * @param {Program} glProgram\n * @param {string} objectDecodeMatricesShaderName\n * @param {string} vertexTextureShaderName\n * @param {string} objectAttributesTextureShaderName\n * @param {string} objectMatricesShaderName\n */\n bindCommonTextures(\n glProgram,\n objectDecodeMatricesShaderName,\n vertexTextureShaderName,\n objectAttributesTextureShaderName,\n objectMatricesShaderName\n ) {\n this.texturePerObjectPositionsDecodeMatrix.bindTexture(glProgram, objectDecodeMatricesShaderName, 1);\n this.texturePerVertexIdCoordinates.bindTexture(glProgram, vertexTextureShaderName, 2);\n this.texturePerObjectColorsAndFlags.bindTexture(glProgram, objectAttributesTextureShaderName, 3);\n this.texturePerObjectInstanceMatrices.bindTexture(glProgram, objectMatricesShaderName, 4);\n }\n\n /**\n *\n * @param {Program} glProgram\n * @param {string} portionIdsShaderName\n * @param {string} polygonIndicesShaderName\n * @param {8|16|32} textureBitness\n */\n bindTriangleIndicesTextures(\n glProgram,\n portionIdsShaderName,\n polygonIndicesShaderName,\n textureBitness,\n ) {\n this.indicesPortionIdsPerBitnessTextures[textureBitness].bindTexture(\n glProgram,\n portionIdsShaderName,\n 5 // webgl texture unit\n );\n\n this.indicesPerBitnessTextures[textureBitness].bindTexture(\n glProgram,\n polygonIndicesShaderName,\n 6 // webgl texture unit\n );\n }\n\n /**\n *\n * @param {Program} glProgram\n * @param {string} edgePortionIdsShaderName\n * @param {string} edgeIndicesShaderName\n * @param {8|16|32} textureBitness\n */\n bindEdgeIndicesTextures(\n glProgram,\n edgePortionIdsShaderName,\n edgeIndicesShaderName,\n textureBitness,\n ) {\n this.edgeIndicesPortionIdsPerBitnessTextures[textureBitness].bindTexture(\n glProgram,\n edgePortionIdsShaderName,\n 5 // webgl texture unit\n );\n\n this.edgeIndicesPerBitnessTextures[textureBitness].bindTexture(\n glProgram,\n edgeIndicesShaderName,\n 6 // webgl texture unit\n );\n }\n}\n\nconst dataTextureRamStats = {\n sizeDataColorsAndFlags: 0,\n sizeDataPositionDecodeMatrices: 0,\n sizeDataTextureOffsets: 0,\n sizeDataTexturePositions: 0,\n sizeDataTextureIndices: 0,\n sizeDataTextureEdgeIndices: 0,\n sizeDataTexturePortionIds: 0,\n numberOfGeometries: 0,\n numberOfPortions: 0,\n numberOfLayers: 0,\n numberOfTextures: 0,\n totalPolygons: 0,\n totalPolygons8Bits: 0,\n totalPolygons16Bits: 0,\n totalPolygons32Bits: 0,\n totalEdges: 0,\n totalEdges8Bits: 0,\n totalEdges16Bits: 0,\n totalEdges32Bits: 0,\n cannotCreatePortion: {\n because10BitsObjectId: 0,\n becauseTextureSize: 0,\n },\n overheadSizeAlignementIndices: 0,\n overheadSizeAlignementEdgeIndices: 0,\n};\n\nwindow.printDataTextureRamStats = function () {\n\n console.log(JSON.stringify(dataTextureRamStats, null, 4));\n\n let totalRamSize = 0;\n\n Object.keys(dataTextureRamStats).forEach(key => {\n if (key.startsWith(\"size\")) {\n totalRamSize += dataTextureRamStats[key];\n }\n });\n\n console.log(`Total size ${totalRamSize} bytes (${(totalRamSize / 1000 / 1000).toFixed(2)} MB)`);\n console.log(`Avg bytes / triangle: ${(totalRamSize / dataTextureRamStats.totalPolygons).toFixed(2)}`);\n\n let percentualRamStats = {};\n\n Object.keys(dataTextureRamStats).forEach(key => {\n if (key.startsWith(\"size\")) {\n percentualRamStats[key] =\n `${(dataTextureRamStats[key] / totalRamSize * 100).toFixed(2)} % of total`;\n }\n });\n\n console.log(JSON.stringify({percentualRamUsage: percentualRamStats}, null, 4));\n};\n\n/**\n * @private\n */\nclass DTXTrianglesTextureFactory {\n\n constructor() {\n\n }\n\n /**\n * Enables the currently binded ````WebGLTexture```` to be used as a data texture.\n *\n * @param {WebGL2RenderingContext} gl\n *\n * @private\n */\n disableBindedTextureFiltering(gl) {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n }\n\n /**\n * This will generate an RGBA texture for:\n * - colors\n * - pickColors\n * - flags\n * - flags2\n * - vertex bases\n * - vertex base offsets\n *\n * The texture will have:\n * - 4 RGBA columns per row: for each object (pick) color and flags(2)\n * - N rows where N is the number of objects\n *\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike>} colors Array of colors for all objects in the layer\n * @param {ArrayLike>} pickColors Array of pickColors for all objects in the layer\n * @param {ArrayLike} vertexBases Array of position-index-bases foteh all objects in the layer\n * @param {ArrayLike} indexBaseOffsets For triangles: array of offests between the (gl_VertexID / 3) and the position where the indices start in the texture layer\n * @param {ArrayLike} edgeIndexBaseOffsets For edges: Array of offests between the (gl_VertexID / 2) and the position where the edge indices start in the texture layer\n * @param {ArrayLike} solid Array is-solid flag for all objects in the layer\n *\n * @returns {BindableDataTexture}\n */\n createTextureForColorsAndFlags(gl, colors, pickColors, vertexBases, indexBaseOffsets, edgeIndexBaseOffsets, solid) {\n const numPortions = colors.length;\n\n // The number of rows in the texture is the number of\n // objects in the layer.\n\n this.numPortions = numPortions;\n\n const textureWidth = 512 * 8;\n const textureHeight = Math.ceil(numPortions / (textureWidth / 8));\n\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n\n // 8 columns per texture row:\n // - col0: (RGBA) object color RGBA\n // - col1: (packed Uint32 as RGBA) object pick color\n // - col2: (packed 4 bytes as RGBA) object flags\n // - col3: (packed 4 bytes as RGBA) object flags2\n // - col4: (packed Uint32 bytes as RGBA) vertex base\n // - col5: (packed Uint32 bytes as RGBA) index base offset\n // - col6: (packed Uint32 bytes as RGBA) edge index base offset\n // - col7: (packed 4 bytes as RGBA) is-solid flag for objects\n\n const texArray = new Uint8Array(4 * textureWidth * textureHeight);\n\n dataTextureRamStats.sizeDataColorsAndFlags += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n\n for (let i = 0; i < numPortions; i++) {\n // object color\n texArray.set(colors [i], i * 32 + 0);\n texArray.set(pickColors [i], i * 32 + 4); // object pick color\n texArray.set([0, 0, 0, 0], i * 32 + 8); // object flags\n texArray.set([0, 0, 0, 0], i * 32 + 12); // object flags2\n\n // vertex base\n texArray.set([\n (vertexBases[i] >> 24) & 255,\n (vertexBases[i] >> 16) & 255,\n (vertexBases[i] >> 8) & 255,\n (vertexBases[i]) & 255,\n ],\n i * 32 + 16\n );\n\n // triangles index base offset\n texArray.set(\n [\n (indexBaseOffsets[i] >> 24) & 255,\n (indexBaseOffsets[i] >> 16) & 255,\n (indexBaseOffsets[i] >> 8) & 255,\n (indexBaseOffsets[i]) & 255,\n ],\n i * 32 + 20\n );\n\n // edge index base offset\n texArray.set(\n [\n (edgeIndexBaseOffsets[i] >> 24) & 255,\n (edgeIndexBaseOffsets[i] >> 16) & 255,\n (edgeIndexBaseOffsets[i] >> 8) & 255,\n (edgeIndexBaseOffsets[i]) & 255,\n ],\n i * 32 + 24\n );\n\n // is-solid flag\n texArray.set([solid[i] ? 1 : 0, 0, 0, 0], i * 32 + 28);\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray);\n }\n\n /**\n * This will generate a texture for all object offsets.\n *\n * @param {WebGL2RenderingContext} gl\n * @param {int[]} offsets Array of int[3], one XYZ offset array for each object\n *\n * @returns {BindableDataTexture}\n */\n createTextureForObjectOffsets(gl, numOffsets) {\n const textureWidth = 512;\n const textureHeight = Math.ceil(numOffsets / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArray = new Float32Array(3 * textureWidth * textureHeight).fill(0);\n dataTextureRamStats.sizeDataTextureOffsets += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32F, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB, gl.FLOAT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray);\n }\n\n /**\n * This will generate a texture for all positions decode matrices in the layer.\n *\n * The texture will have:\n * - 4 RGBA columns per row (each column will contain 4 packed half-float (16 bits) components).\n * Thus, each row will contain 16 packed half-floats corresponding to a complete positions decode matrix)\n * - N rows where N is the number of objects\n *\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} instanceMatrices Array of geometry instancing matrices for all objects in the layer. Null if the objects are not instanced.\n *\n * @returns {BindableDataTexture}\n */\n createTextureForInstancingMatrices(gl, instanceMatrices) {\n const numMatrices = instanceMatrices.length;\n if (numMatrices === 0) {\n throw \"num instance matrices===0\";\n }\n // in one row we can fit 512 matrices\n const textureWidth = 512 * 4;\n const textureHeight = Math.ceil(numMatrices / (textureWidth / 4));\n const texArray = new Float32Array(4 * textureWidth * textureHeight);\n // dataTextureRamStats.sizeDataPositionDecodeMatrices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0; i < instanceMatrices.length; i++) { // 4x4 values\n texArray.set(instanceMatrices[i], i * 16);\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA, gl.FLOAT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight, texArray);\n }\n\n /**\n * This will generate a texture for all positions decode matrices in the layer.\n *\n * The texture will have:\n * - 4 RGBA columns per row (each column will contain 4 packed half-float (16 bits) components).\n * Thus, each row will contain 16 packed half-floats corresponding to a complete positions decode matrix)\n * - N rows where N is the number of objects\n *\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} positionDecodeMatrices Array of positions decode matrices for all objects in the layer\n *\n * @returns {BindableDataTexture}\n */\n createTextureForPositionsDecodeMatrices(gl, positionDecodeMatrices) {\n const numMatrices = positionDecodeMatrices.length;\n if (numMatrices === 0) {\n throw \"num decode+entity matrices===0\";\n }\n // in one row we can fit 512 matrices\n const textureWidth = 512 * 4;\n const textureHeight = Math.ceil(numMatrices / (textureWidth / 4));\n const texArray = new Float32Array(4 * textureWidth * textureHeight);\n dataTextureRamStats.sizeDataPositionDecodeMatrices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0; i < positionDecodeMatrices.length; i++) { // 4x4 values\n texArray.set(positionDecodeMatrices[i], i * 16);\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGBA, gl.FLOAT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n createTextureFor8BitIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 3 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint8Array(texArraySize);\n dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB8UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_BYTE, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n createTextureFor16BitIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 3 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint16Array(texArraySize);\n dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_SHORT, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n createTextureFor32BitIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 3 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint32Array(texArraySize);\n dataTextureRamStats.sizeDataTextureIndices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB32UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_INT, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n createTextureFor8BitsEdgeIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 2 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 2;\n const texArray = new Uint8Array(texArraySize);\n dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG8UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RG_INTEGER, gl.UNSIGNED_BYTE, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n createTextureFor16BitsEdgeIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 2 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 2;\n const texArray = new Uint16Array(texArraySize);\n dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG16UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RG_INTEGER, gl.UNSIGNED_SHORT, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param indicesArrays\n * @param lenIndices\n *\n * @returns {BindableDataTexture}\n */\n createTextureFor32BitsEdgeIndices(gl, indicesArrays, lenIndices) {\n if (lenIndices === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenIndices / 2 / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 2;\n const texArray = new Uint32Array(texArraySize);\n dataTextureRamStats.sizeDataTextureEdgeIndices += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0, j = 0, len = indicesArrays.length; i < len; i++) {\n const pc = indicesArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG32UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RG_INTEGER, gl.UNSIGNED_INT, texArray, 0);\n this.disableBindedTextureFiltering(gl);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} positionsArrays Arrays of quantized positions in the layer\n * @param lenPositions\n *\n * This will generate a texture for positions in the layer.\n *\n * The texture will have:\n * - 1024 columns, where each pixel will be a 16-bit-per-component RGB texture, corresponding to the XYZ of the position\n * - a number of rows R where R*1024 is just >= than the number of vertices (positions / 3)\n *\n * @returns {BindableDataTexture}\n */\n createTextureForPositions(gl, positionsArrays, lenPositions) {\n const numVertices = lenPositions / 3;\n const textureWidth = 4096;\n const textureHeight = Math.ceil(numVertices / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight * 3;\n const texArray = new Uint16Array(texArraySize);\n dataTextureRamStats.sizeDataTexturePositions += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n for (let i = 0, j = 0, len = positionsArrays.length; i < len; i++) {\n const pc = positionsArrays[i];\n texArray.set(pc, j);\n j += pc.length;\n }\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB16UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RGB_INTEGER, gl.UNSIGNED_SHORT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n\n /**\n * @param {WebGL2RenderingContext} gl\n * @param {ArrayLike} portionIdsArray\n *\n * @returns {BindableDataTexture}\n */\n createTextureForPackedPortionIds(gl, portionIdsArray) {\n if (portionIdsArray.length === 0) {\n return {texture: null, textureHeight: 0,};\n }\n const lenArray = portionIdsArray.length;\n const textureWidth = 4096;\n const textureHeight = Math.ceil(lenArray / textureWidth);\n if (textureHeight === 0) {\n throw \"texture height===0\";\n }\n const texArraySize = textureWidth * textureHeight;\n const texArray = new Uint16Array(texArraySize);\n texArray.set(portionIdsArray, 0);\n dataTextureRamStats.sizeDataTexturePortionIds += texArray.byteLength;\n dataTextureRamStats.numberOfTextures++;\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R16UI, textureWidth, textureHeight);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, gl.RED_INTEGER, gl.UNSIGNED_SHORT, texArray, 0);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return new BindableDataTexture(gl, texture, textureWidth, textureHeight);\n }\n}\n\nconst configs = new Configs();\n\n/**\n * 12-bits allowed for object ids.\n * Limits the per-object texture height in the layer.\n */\nconst MAX_NUMBER_OF_OBJECTS_IN_LAYER = (1 << 16);\n\n/**\n * 4096 is max data texture height.\n * Limits the aggregated geometry texture height in the layer.\n */\nconst MAX_DATA_TEXTURE_HEIGHT = configs.maxDataTextureHeight;\n\n/**\n * Align `indices` and `edgeIndices` memory layout to 8 elements.\n *\n * Used as an optimization for the `...portionIds...` texture, so it\n * can just be stored 1 out of 8 `portionIds` corresponding to a given\n * `triangle-index` or `edge-index`.\n */\nconst INDICES_EDGE_INDICES_ALIGNEMENT_SIZE = 8;\n\n/**\n * Number of maximum allowed per-object flags update per render frame\n * before switching to batch update mode.\n */\nconst MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE = 10;\n\nconst tempMat4a = new Float32Array(16);\nconst tempUint8Array4 = new Uint8Array(4);\nconst tempFloat32Array3 = new Float32Array(3);\n\nlet numLayers = 0;\n\nconst DEFAULT_MATRIX$1 = math.identityMat4();\n\n/**\n * @private\n */\nclass DTXTrianglesLayer {\n\n constructor(model, cfg) {\n\n console.info(\"Creating DTXTrianglesLayer\");\n\n dataTextureRamStats.numberOfLayers++;\n\n this._layerNumber = numLayers++;\n this.sortId = `TriDTX-${this._layerNumber}`; // State sorting key.\n this.layerIndex = cfg.layerIndex; // Index of this TrianglesDataTextureLayer in {@link SceneModel#_layerList}.\n\n this._renderers = getRenderers(model.scene);\n this.model = model;\n this._buffer = new DTXTrianglesBuffer();\n this._dtxState = new DTXTrianglesState();\n this._dtxTextureFactory = new DTXTrianglesTextureFactory();\n\n this._state = new RenderState({\n origin: math.vec3(cfg.origin),\n metallicRoughnessBuf: null,\n textureState: this._dtxState,\n numIndices8Bits: 0,\n numIndices16Bits: 0,\n numIndices32Bits: 0,\n numEdgeIndices8Bits: 0,\n numEdgeIndices16Bits: 0,\n numEdgeIndices32Bits: 0,\n numVertices: 0,\n });\n\n this._numPortions = 0; // These counts are used to avoid unnecessary render passes\n this._numVisibleLayerPortions = 0;\n this._numTransparentLayerPortions = 0;\n this._numXRayedLayerPortions = 0;\n this._numSelectedLayerPortions = 0;\n this._numHighlightedLayerPortions = 0;\n this._numClippableLayerPortions = 0;\n this._numEdgesLayerPortions = 0;\n this._numPickableLayerPortions = 0;\n this._numCulledLayerPortions = 0;\n\n this._subPortions = [];\n\n /**\n * Due to `index rebucketting` process in ```prepareMeshGeometry``` function, it's possible that a single\n * portion is expanded to more than 1 real sub-portion.\n *\n * This Array tracks the mapping between:\n *\n * - external `portionIds` as seen by consumers of this class.\n * - internal `sub-portionIds` actually managed by this class.\n *\n * The outer index of this array is the externally seen `portionId`.\n * The inner value of the array, are `sub-portionIds` corresponding to the `portionId`.\n */\n this._portionToSubPortionsMap = [];\n\n this._bucketGeometries = {};\n\n this._meshes = [];\n\n /**\n * The axis-aligned World-space boundary of this TrianglesDataTextureLayer's positions.\n */\n this._aabb = math.collapseAABB3();\n this.aabbDirty = true;\n\n /**\n * The number of updates in the current frame;\n */\n this._numUpdatesInFrame = 0;\n\n this._finalized = false;\n }\n\n get aabb() {\n if (this.aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._meshes.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._meshes[i].aabb);\n }\n this.aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * Returns whether the ```TrianglesDataTextureLayer``` has room for more portions.\n *\n * @param {object} portionCfg An object containing the geometrical data (`positions`, `indices`, `edgeIndices`) for the portion.\n * @returns {Boolean} Wheter the requested portion can be created\n */\n canCreatePortion(portionCfg) {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n const numNewPortions = portionCfg.buckets.length;\n if ((this._numPortions + numNewPortions) > MAX_NUMBER_OF_OBJECTS_IN_LAYER) {\n dataTextureRamStats.cannotCreatePortion.because10BitsObjectId++;\n }\n let retVal = (this._numPortions + numNewPortions) <= MAX_NUMBER_OF_OBJECTS_IN_LAYER;\n const bucketIndex = 0; // TODO: Is this a bug?\n const bucketGeometryId = portionCfg.geometryId !== undefined && portionCfg.geometryId !== null\n ? `${portionCfg.geometryId}#${bucketIndex}`\n : `${portionCfg.id}#${bucketIndex}`;\n const alreadyHasPortionGeometry = this._bucketGeometries[bucketGeometryId];\n if (!alreadyHasPortionGeometry) {\n const maxIndicesOfAnyBits = Math.max(this._state.numIndices8Bits, this._state.numIndices16Bits, this._state.numIndices32Bits,);\n let numVertices = 0;\n let numIndices = 0;\n portionCfg.buckets.forEach(bucket => {\n numVertices += bucket.positionsCompressed.length / 3;\n numIndices += bucket.indices.length / 3;\n });\n if ((this._state.numVertices + numVertices) > MAX_DATA_TEXTURE_HEIGHT * 4096 ||\n (maxIndicesOfAnyBits + numIndices) > MAX_DATA_TEXTURE_HEIGHT * 4096) {\n dataTextureRamStats.cannotCreatePortion.becauseTextureSize++;\n }\n retVal &&=\n (this._state.numVertices + numVertices) <= MAX_DATA_TEXTURE_HEIGHT * 4096 &&\n (maxIndicesOfAnyBits + numIndices) <= MAX_DATA_TEXTURE_HEIGHT * 4096;\n }\n return retVal;\n }\n\n /**\n * Creates a new portion within this TrianglesDataTextureLayer, returns the new portion ID.\n *\n * Gives the portion the specified geometry, color and matrix.\n *\n * @param mesh The SceneModelMesh that owns the portion\n * @param portionCfg.positionsCompressed Flat float Local-space positionsCompressed array.\n * @param [portionCfg.normals] Flat float normals array.\n * @param [portionCfg.colors] Flat float colors array.\n * @param portionCfg.indices Flat int indices array.\n * @param [portionCfg.edgeIndices] Flat int edges indices array.\n * @param portionCfg.color Quantized RGB color [0..255,0..255,0..255,0..255]\n * @param portionCfg.metallic Metalness factor [0..255]\n * @param portionCfg.roughness Roughness factor [0..255]\n * @param portionCfg.opacity Opacity [0..255]\n * @param [portionCfg.meshMatrix] Flat float 4x4 matrix - transforms the portion within the coordinate system that's local to the SceneModel\n * @param portionCfg.worldAABB Flat float AABB World-space AABB\n * @param portionCfg.pickColor Quantized pick color\n * @returns {number} Portion ID\n */\n createPortion(mesh, portionCfg) {\n if (this._finalized) {\n throw \"Already finalized\";\n }\n const subPortionIds = [];\n // const portionAABB = portionCfg.worldAABB;\n portionCfg.buckets.forEach((bucket, bucketIndex) => {\n const bucketGeometryId = portionCfg.geometryId !== undefined && portionCfg.geometryId !== null\n ? `${portionCfg.geometryId}#${bucketIndex}`\n : `${portionCfg.id}#${bucketIndex}`;\n let bucketGeometry = this._bucketGeometries[bucketGeometryId];\n if (!bucketGeometry) {\n bucketGeometry = this._createBucketGeometry(portionCfg, bucket);\n this._bucketGeometries[bucketGeometryId] = bucketGeometry;\n }\n // const subPortionAABB = math.collapseAABB3(tempAABB3b);\n const subPortionId = this._createSubPortion(portionCfg, bucketGeometry, bucket);\n //math.expandAABB3(portionAABB, subPortionAABB);\n subPortionIds.push(subPortionId);\n });\n const portionId = this._portionToSubPortionsMap.length;\n this._portionToSubPortionsMap.push(subPortionIds);\n this.model.numPortions++;\n this._meshes.push(mesh);\n return portionId;\n }\n\n _createBucketGeometry(portionCfg, bucket) {\n\n // Indices alignement\n // This will make every mesh consume a multiple of INDICES_EDGE_INDICES_ALIGNEMENT_SIZE\n // array items for storing the triangles of the mesh, and it supports:\n // - a memory optimization of factor INDICES_EDGE_INDICES_ALIGNEMENT_SIZE\n // - in exchange for a small RAM overhead\n // (by adding some padding until a size that is multiple of INDICES_EDGE_INDICES_ALIGNEMENT_SIZE)\n\n if (bucket.indices) {\n const alignedIndicesLen = Math.ceil((bucket.indices.length / 3) / INDICES_EDGE_INDICES_ALIGNEMENT_SIZE) * INDICES_EDGE_INDICES_ALIGNEMENT_SIZE * 3;\n dataTextureRamStats.overheadSizeAlignementIndices += 2 * (alignedIndicesLen - bucket.indices.length);\n const alignedIndices = new Uint32Array(alignedIndicesLen);\n alignedIndices.fill(0);\n alignedIndices.set(bucket.indices);\n bucket.indices = alignedIndices;\n }\n\n // EdgeIndices alignement\n // This will make every mesh consume a multiple of INDICES_EDGE_INDICES_ALIGNEMENT_SIZE\n // array items for storing the edges of the mesh, and it supports:\n // - a memory optimization of factor INDICES_EDGE_INDICES_ALIGNEMENT_SIZE\n // - in exchange for a small RAM overhead\n // (by adding some padding until a size that is multiple of INDICES_EDGE_INDICES_ALIGNEMENT_SIZE)\n\n if (bucket.edgeIndices) {\n const alignedEdgeIndicesLen = Math.ceil((bucket.edgeIndices.length / 2) / INDICES_EDGE_INDICES_ALIGNEMENT_SIZE) * INDICES_EDGE_INDICES_ALIGNEMENT_SIZE * 2;\n dataTextureRamStats.overheadSizeAlignementEdgeIndices += 2 * (alignedEdgeIndicesLen - bucket.edgeIndices.length);\n const alignedEdgeIndices = new Uint32Array(alignedEdgeIndicesLen);\n alignedEdgeIndices.fill(0);\n alignedEdgeIndices.set(bucket.edgeIndices);\n bucket.edgeIndices = alignedEdgeIndices;\n }\n\n const positionsCompressed = bucket.positionsCompressed;\n const indices = bucket.indices;\n const edgeIndices = bucket.edgeIndices;\n const buffer = this._buffer;\n\n buffer.positionsCompressed.push(positionsCompressed);\n const vertexBase = buffer.lenPositionsCompressed / 3;\n const numVertices = positionsCompressed.length / 3;\n buffer.lenPositionsCompressed += positionsCompressed.length;\n\n let indicesBase;\n let numTriangles = 0;\n if (indices) {\n numTriangles = indices.length / 3;\n let indicesBuffer;\n if (numVertices <= (1 << 8)) {\n indicesBuffer = buffer.indices8Bits;\n indicesBase = buffer.lenIndices8Bits / 3;\n buffer.lenIndices8Bits += indices.length;\n } else if (numVertices <= (1 << 16)) {\n indicesBuffer = buffer.indices16Bits;\n indicesBase = buffer.lenIndices16Bits / 3;\n buffer.lenIndices16Bits += indices.length;\n } else {\n indicesBuffer = buffer.indices32Bits;\n indicesBase = buffer.lenIndices32Bits / 3;\n buffer.lenIndices32Bits += indices.length;\n }\n indicesBuffer.push(indices);\n }\n\n let edgeIndicesBase;\n let numEdges = 0;\n if (edgeIndices) {\n numEdges = edgeIndices.length / 2;\n let edgeIndicesBuffer;\n if (numVertices <= (1 << 8)) {\n edgeIndicesBuffer = buffer.edgeIndices8Bits;\n edgeIndicesBase = buffer.lenEdgeIndices8Bits / 2;\n buffer.lenEdgeIndices8Bits += edgeIndices.length;\n } else if (numVertices <= (1 << 16)) {\n edgeIndicesBuffer = buffer.edgeIndices16Bits;\n edgeIndicesBase = buffer.lenEdgeIndices16Bits / 2;\n buffer.lenEdgeIndices16Bits += edgeIndices.length;\n } else {\n edgeIndicesBuffer = buffer.edgeIndices32Bits;\n edgeIndicesBase = buffer.lenEdgeIndices32Bits / 2;\n buffer.lenEdgeIndices32Bits += edgeIndices.length;\n }\n edgeIndicesBuffer.push(edgeIndices);\n }\n\n this._state.numVertices += numVertices;\n\n dataTextureRamStats.numberOfGeometries++;\n\n const bucketGeometry = {\n vertexBase,\n numVertices,\n numTriangles,\n numEdges,\n indicesBase,\n edgeIndicesBase\n };\n\n return bucketGeometry;\n }\n\n _createSubPortion(portionCfg, bucketGeometry, bucket, subPortionAABB) {\n\n const color = portionCfg.color;\n portionCfg.metallic;\n portionCfg.roughness;\n const colors = portionCfg.colors;\n const opacity = portionCfg.opacity;\n const meshMatrix = portionCfg.meshMatrix;\n const pickColor = portionCfg.pickColor;\n const buffer = this._buffer;\n const state = this._state;\n\n buffer.perObjectPositionsDecodeMatrices.push(portionCfg.positionsDecodeMatrix);\n buffer.perObjectInstancePositioningMatrices.push(meshMatrix || DEFAULT_MATRIX$1);\n\n buffer.perObjectSolid.push(!!portionCfg.solid);\n\n if (colors) {\n buffer.perObjectColors.push([colors[0] * 255, colors[1] * 255, colors[2] * 255, 255]);\n } else if (color) { // Color is pre-quantized by SceneModel\n buffer.perObjectColors.push([color[0], color[1], color[2], opacity]);\n }\n\n buffer.perObjectPickColors.push(pickColor);\n buffer.perObjectVertexBases.push(bucketGeometry.vertexBase);\n\n {\n let currentNumIndices;\n if (bucketGeometry.numVertices <= (1 << 8)) {\n currentNumIndices = state.numIndices8Bits;\n } else if (bucketGeometry.numVertices <= (1 << 16)) {\n currentNumIndices = state.numIndices16Bits;\n } else {\n currentNumIndices = state.numIndices32Bits;\n }\n buffer.perObjectIndexBaseOffsets.push(currentNumIndices / 3 - bucketGeometry.indicesBase);\n }\n\n {\n let currentNumEdgeIndices;\n if (bucketGeometry.numVertices <= (1 << 8)) {\n currentNumEdgeIndices = state.numEdgeIndices8Bits;\n } else if (bucketGeometry.numVertices <= (1 << 16)) {\n currentNumEdgeIndices = state.numEdgeIndices16Bits;\n } else {\n currentNumEdgeIndices = state.numEdgeIndices32Bits;\n }\n buffer.perObjectEdgeIndexBaseOffsets.push(currentNumEdgeIndices / 2 - bucketGeometry.edgeIndicesBase);\n }\n\n const subPortionId = this._subPortions.length;\n if (bucketGeometry.numTriangles > 0) {\n let numIndices = bucketGeometry.numTriangles * 3;\n let indicesPortionIdBuffer;\n if (bucketGeometry.numVertices <= (1 << 8)) {\n indicesPortionIdBuffer = buffer.perTriangleNumberPortionId8Bits;\n state.numIndices8Bits += numIndices;\n dataTextureRamStats.totalPolygons8Bits += bucketGeometry.numTriangles;\n } else if (bucketGeometry.numVertices <= (1 << 16)) {\n indicesPortionIdBuffer = buffer.perTriangleNumberPortionId16Bits;\n state.numIndices16Bits += numIndices;\n dataTextureRamStats.totalPolygons16Bits += bucketGeometry.numTriangles;\n } else {\n indicesPortionIdBuffer = buffer.perTriangleNumberPortionId32Bits;\n state.numIndices32Bits += numIndices;\n dataTextureRamStats.totalPolygons32Bits += bucketGeometry.numTriangles;\n }\n dataTextureRamStats.totalPolygons += bucketGeometry.numTriangles;\n for (let i = 0; i < bucketGeometry.numTriangles; i += INDICES_EDGE_INDICES_ALIGNEMENT_SIZE) {\n indicesPortionIdBuffer.push(subPortionId);\n }\n }\n\n if (bucketGeometry.numEdges > 0) {\n let numEdgeIndices = bucketGeometry.numEdges * 2;\n let edgeIndicesPortionIdBuffer;\n if (bucketGeometry.numVertices <= (1 << 8)) {\n edgeIndicesPortionIdBuffer = buffer.perEdgeNumberPortionId8Bits;\n state.numEdgeIndices8Bits += numEdgeIndices;\n dataTextureRamStats.totalEdges8Bits += bucketGeometry.numEdges;\n } else if (bucketGeometry.numVertices <= (1 << 16)) {\n edgeIndicesPortionIdBuffer = buffer.perEdgeNumberPortionId16Bits;\n state.numEdgeIndices16Bits += numEdgeIndices;\n dataTextureRamStats.totalEdges16Bits += bucketGeometry.numEdges;\n } else {\n edgeIndicesPortionIdBuffer = buffer.perEdgeNumberPortionId32Bits;\n state.numEdgeIndices32Bits += numEdgeIndices;\n dataTextureRamStats.totalEdges32Bits += bucketGeometry.numEdges;\n }\n dataTextureRamStats.totalEdges += bucketGeometry.numEdges;\n for (let i = 0; i < bucketGeometry.numEdges; i += INDICES_EDGE_INDICES_ALIGNEMENT_SIZE) {\n edgeIndicesPortionIdBuffer.push(subPortionId);\n }\n }\n\n // buffer.perObjectOffsets.push([0, 0, 0]);\n\n this._subPortions.push({\n // vertsBase: vertsIndex,\n numVertices: bucketGeometry.numTriangles\n });\n\n this._numPortions++;\n\n dataTextureRamStats.numberOfPortions++;\n\n return subPortionId;\n }\n\n /**\n * Builds data textures from the appended geometries and loads them into the GPU.\n *\n * No more portions can then be created.\n */\n finalize() {\n\n if (this._finalized) {\n return;\n }\n\n const state = this._state;\n const textureState = this._dtxState;\n const gl = this.model.scene.canvas.gl;\n const buffer = this._buffer;\n\n state.gl = gl;\n textureState.texturePerObjectColorsAndFlags = this._dtxTextureFactory.createTextureForColorsAndFlags(\n gl,\n buffer.perObjectColors,\n buffer.perObjectPickColors,\n buffer.perObjectVertexBases,\n buffer.perObjectIndexBaseOffsets,\n buffer.perObjectEdgeIndexBaseOffsets,\n buffer.perObjectSolid);\n\n textureState.texturePerObjectInstanceMatrices\n = this._dtxTextureFactory.createTextureForInstancingMatrices(gl, buffer.perObjectInstancePositioningMatrices);\n\n textureState.texturePerObjectPositionsDecodeMatrix\n = this._dtxTextureFactory.createTextureForPositionsDecodeMatrices(\n gl,\n buffer.perObjectPositionsDecodeMatrices);\n\n textureState.texturePerVertexIdCoordinates = this._dtxTextureFactory.createTextureForPositions(\n gl,\n buffer.positionsCompressed,\n buffer.lenPositionsCompressed);\n\n textureState.texturePerPolygonIdPortionIds8Bits = this._dtxTextureFactory.createTextureForPackedPortionIds(\n gl,\n buffer.perTriangleNumberPortionId8Bits);\n\n textureState.texturePerPolygonIdPortionIds16Bits = this._dtxTextureFactory.createTextureForPackedPortionIds(\n gl,\n buffer.perTriangleNumberPortionId16Bits);\n\n textureState.texturePerPolygonIdPortionIds32Bits = this._dtxTextureFactory.createTextureForPackedPortionIds(\n gl,\n buffer.perTriangleNumberPortionId32Bits);\n\n if (buffer.perEdgeNumberPortionId8Bits.length > 0) {\n textureState.texturePerEdgeIdPortionIds8Bits = this._dtxTextureFactory.createTextureForPackedPortionIds(\n gl,\n buffer.perEdgeNumberPortionId8Bits);\n }\n\n if (buffer.perEdgeNumberPortionId16Bits.length > 0) {\n textureState.texturePerEdgeIdPortionIds16Bits = this._dtxTextureFactory.createTextureForPackedPortionIds(\n gl,\n buffer.perEdgeNumberPortionId16Bits);\n }\n\n if (buffer.perEdgeNumberPortionId32Bits.length > 0) {\n textureState.texturePerEdgeIdPortionIds32Bits = this._dtxTextureFactory.createTextureForPackedPortionIds(\n gl,\n buffer.perEdgeNumberPortionId32Bits);\n }\n\n if (buffer.lenIndices8Bits > 0) {\n textureState.texturePerPolygonIdIndices8Bits = this._dtxTextureFactory.createTextureFor8BitIndices(\n gl,\n buffer.indices8Bits, buffer.lenIndices8Bits);\n }\n\n if (buffer.lenIndices16Bits > 0) {\n textureState.texturePerPolygonIdIndices16Bits = this._dtxTextureFactory.createTextureFor16BitIndices(\n gl,\n buffer.indices16Bits, buffer.lenIndices16Bits);\n }\n\n if (buffer.lenIndices32Bits > 0) {\n textureState.texturePerPolygonIdIndices32Bits = this._dtxTextureFactory.createTextureFor32BitIndices(\n gl,\n buffer.indices32Bits, buffer.lenIndices32Bits);\n }\n\n if (buffer.lenEdgeIndices8Bits > 0) {\n textureState.texturePerPolygonIdEdgeIndices8Bits = this._dtxTextureFactory.createTextureFor8BitsEdgeIndices(\n gl,\n buffer.edgeIndices8Bits, buffer.lenEdgeIndices8Bits);\n }\n\n if (buffer.lenEdgeIndices16Bits > 0) {\n textureState.texturePerPolygonIdEdgeIndices16Bits = this._dtxTextureFactory.createTextureFor16BitsEdgeIndices(\n gl,\n buffer.edgeIndices16Bits, buffer.lenEdgeIndices16Bits);\n }\n\n if (buffer.lenEdgeIndices32Bits > 0) {\n textureState.texturePerPolygonIdEdgeIndices32Bits = this._dtxTextureFactory.createTextureFor32BitsEdgeIndices(\n gl,\n buffer.edgeIndices32Bits, buffer.lenEdgeIndices32Bits);\n }\n\n textureState.finalize();\n\n // Free up memory\n this._buffer = null;\n this._bucketGeometries = {};\n this._finalized = true;\n this._deferredSetFlagsDirty = false; //\n\n this._onSceneRendering = this.model.scene.on(\"rendering\", () => {\n if (this._deferredSetFlagsDirty) {\n this._uploadDeferredFlags();\n }\n this._numUpdatesInFrame = 0;\n });\n }\n\n isEmpty() {\n return this._numPortions === 0;\n }\n\n initFlags(portionId, flags, meshTransparent) {\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions++;\n this.model.numCulledLayerPortions++;\n }\n if (meshTransparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n }\n const deferred = true;\n this._setFlags(portionId, flags, meshTransparent, deferred);\n this._setFlags2(portionId, flags, deferred);\n }\n\n flushInitFlags() {\n this._setDeferredFlags();\n this._setDeferredFlags2();\n }\n\n setVisible(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.VISIBLE) {\n this._numVisibleLayerPortions++;\n this.model.numVisibleLayerPortions++;\n } else {\n this._numVisibleLayerPortions--;\n this.model.numVisibleLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setHighlighted(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.HIGHLIGHTED) {\n this._numHighlightedLayerPortions++;\n this.model.numHighlightedLayerPortions++;\n } else {\n this._numHighlightedLayerPortions--;\n this.model.numHighlightedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setXRayed(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.XRAYED) {\n this._numXRayedLayerPortions++;\n this.model.numXRayedLayerPortions++;\n } else {\n this._numXRayedLayerPortions--;\n this.model.numXRayedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setSelected(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.SELECTED) {\n this._numSelectedLayerPortions++;\n this.model.numSelectedLayerPortions++;\n } else {\n this._numSelectedLayerPortions--;\n this.model.numSelectedLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setEdges(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.EDGES) {\n this._numEdgesLayerPortions++;\n this.model.numEdgesLayerPortions++;\n } else {\n this._numEdgesLayerPortions--;\n this.model.numEdgesLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setClippable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CLIPPABLE) {\n this._numClippableLayerPortions++;\n this.model.numClippableLayerPortions++;\n } else {\n this._numClippableLayerPortions--;\n this.model.numClippableLayerPortions--;\n }\n this._setFlags2(portionId, flags);\n }\n\n /**\n * This will _start_ a \"set-flags transaction\".\n *\n * After invoking this method, calling setFlags/setFlags2 will not update\n * the colors+flags texture but only store the new flags/flag2 in the\n * colors+flags texture data array.\n *\n * After invoking this method, and when all desired setFlags/setFlags2 have\n * been called on needed portions of the layer, invoke `_uploadDeferredFlags`\n * to actually upload the data array into the texture.\n *\n * In massive \"set-flags\" scenarios like VFC or LOD mechanisms, the combination of\n * `_beginDeferredFlags` + `_uploadDeferredFlags`brings a speed-up of\n * up to 80x when e.g. objects are massively (un)culled 🚀.\n */\n _beginDeferredFlags() {\n this._deferredSetFlagsActive = true;\n }\n\n /**\n * This will _commit_ a \"set-flags transaction\".\n *\n * Invoking this method will update the colors+flags texture data with new\n * flags/flags2 set since the previous invocation of `_beginDeferredFlags`.\n */\n _uploadDeferredFlags() {\n this._deferredSetFlagsActive = false;\n if (!this._deferredSetFlagsDirty) {\n return;\n }\n this._deferredSetFlagsDirty = false;\n const gl = this.model.scene.canvas.gl;\n const textureState = this._dtxState;\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n 0, // xoffset\n 0, // yoffset\n textureState.texturePerObjectColorsAndFlags._textureWidth, // width\n textureState.texturePerObjectColorsAndFlags._textureHeight, // width\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n textureState.texturePerObjectColorsAndFlags._textureData\n );\n // gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectInstanceMatrices._texture);\n // gl.texSubImage2D(\n // gl.TEXTURE_2D,\n // 0, // level\n // 0, // xoffset\n // 0, // yoffset\n // textureState.texturePerObjectInstanceMatrices._textureWidth, // width\n // textureState.texturePerObjectInstanceMatrices._textureHeight, // width\n // gl.RGB,\n // gl.FLOAT,\n // textureState.texturePerObjectInstanceMatrices._textureData\n // );\n }\n\n setCulled(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.CULLED) {\n this._numCulledLayerPortions += this._portionToSubPortionsMap[portionId].length;\n this.model.numCulledLayerPortions++;\n } else {\n this._numCulledLayerPortions -= this._portionToSubPortionsMap[portionId].length;\n this.model.numCulledLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setCollidable(portionId, flags) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n }\n\n setPickable(portionId, flags, transparent) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n if (flags & ENTITY_FLAGS.PICKABLE) {\n this._numPickableLayerPortions++;\n this.model.numPickableLayerPortions++;\n } else {\n this._numPickableLayerPortions--;\n this.model.numPickableLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n setColor(portionId, color) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetColor(subPortionIds[i], color);\n }\n }\n\n _subPortionSetColor(subPortionId, color) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n // Color\n const textureState = this._dtxState;\n const gl = this.model.scene.canvas.gl;\n tempUint8Array4 [0] = color[0];\n tempUint8Array4 [1] = color[1];\n tempUint8Array4 [2] = color[2];\n tempUint8Array4 [3] = color[3];\n // object colors\n textureState.texturePerObjectColorsAndFlags._textureData.set(tempUint8Array4, subPortionId * 32);\n if (this._deferredSetFlagsActive) {\n console.info(\"_subPortionSetColor defer\");\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n console.info(\"_subPortionSetColor write through\");\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 8, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n 1, // width\n 1, //height\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n tempUint8Array4\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n setTransparent(portionId, flags, transparent) {\n if (transparent) {\n this._numTransparentLayerPortions++;\n this.model.numTransparentLayerPortions++;\n } else {\n this._numTransparentLayerPortions--;\n this.model.numTransparentLayerPortions--;\n }\n this._setFlags(portionId, flags, transparent);\n }\n\n _setFlags(portionId, flags, transparent, deferred = false) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetFlags(subPortionIds[i], flags, transparent, deferred);\n }\n }\n\n _subPortionSetFlags(subPortionId, flags, transparent, deferred = false) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n\n const visible = !!(flags & ENTITY_FLAGS.VISIBLE);\n const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);\n const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);\n const selected = !!(flags & ENTITY_FLAGS.SELECTED);\n const edges = !!(flags & ENTITY_FLAGS.EDGES);\n const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);\n const culled = !!(flags & ENTITY_FLAGS.CULLED);\n\n // Color\n\n let f0;\n if (!visible || culled || xrayed\n || (highlighted && !this.model.scene.highlightMaterial.glowThrough)\n || (selected && !this.model.scene.selectedMaterial.glowThrough)) {\n f0 = RENDER_PASSES.NOT_RENDERED;\n } else {\n if (transparent) {\n f0 = RENDER_PASSES.COLOR_TRANSPARENT;\n } else {\n f0 = RENDER_PASSES.COLOR_OPAQUE;\n }\n }\n\n // Silhouette\n\n let f1;\n if (!visible || culled) {\n f1 = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n f1 = RENDER_PASSES.SILHOUETTE_SELECTED;\n } else if (highlighted) {\n f1 = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;\n } else if (xrayed) {\n f1 = RENDER_PASSES.SILHOUETTE_XRAYED;\n } else {\n f1 = RENDER_PASSES.NOT_RENDERED;\n }\n\n // Edges\n\n let f2 = 0;\n if (!visible || culled) {\n f2 = RENDER_PASSES.NOT_RENDERED;\n } else if (selected) {\n f2 = RENDER_PASSES.EDGES_SELECTED;\n } else if (highlighted) {\n f2 = RENDER_PASSES.EDGES_HIGHLIGHTED;\n } else if (xrayed) {\n f2 = RENDER_PASSES.EDGES_XRAYED;\n } else if (edges) {\n if (transparent) {\n f2 = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;\n } else {\n f2 = RENDER_PASSES.EDGES_COLOR_OPAQUE;\n }\n } else {\n f2 = RENDER_PASSES.NOT_RENDERED;\n }\n\n // Pick\n\n let f3 = (visible && (!culled) && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;\n const textureState = this._dtxState;\n const gl = this.model.scene.canvas.gl;\n tempUint8Array4 [0] = f0;\n tempUint8Array4 [1] = f1;\n tempUint8Array4 [2] = f2;\n tempUint8Array4 [3] = f3;\n // object flags\n textureState.texturePerObjectColorsAndFlags._textureData.set(tempUint8Array4, subPortionId * 32 + 8);\n if (this._deferredSetFlagsActive || deferred) {\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 8 + 2, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n 1, // width\n 1, //height\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n tempUint8Array4\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n _setDeferredFlags() {\n }\n\n _setFlags2(portionId, flags, deferred = false) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetFlags2(subPortionIds[i], flags, deferred);\n }\n }\n\n _subPortionSetFlags2(subPortionId, flags, deferred = false) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n const clippable = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 255 : 0;\n const textureState = this._dtxState;\n const gl = this.model.scene.canvas.gl;\n tempUint8Array4 [0] = clippable;\n tempUint8Array4 [1] = 0;\n tempUint8Array4 [2] = 1;\n tempUint8Array4 [3] = 2;\n // object flags2\n textureState.texturePerObjectColorsAndFlags._textureData.set(tempUint8Array4, subPortionId * 32 + 12);\n if (this._deferredSetFlagsActive || deferred) {\n // console.log(\"_subPortionSetFlags2 set flags defer\");\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectColorsAndFlags._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 8 + 3, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n 1, // width\n 1, //height\n gl.RGBA_INTEGER,\n gl.UNSIGNED_BYTE,\n tempUint8Array4\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n _setDeferredFlags2() {\n }\n\n setOffset(portionId, offset) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetOffset(subPortionIds[i], offset);\n }\n }\n\n _subPortionSetOffset(subPortionId, offset) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n // if (!this.model.scene.entityOffsetsEnabled) {\n // this.model.error(\"Entity#offset not enabled for this Viewer\"); // See Viewer entityOffsetsEnabled\n // return;\n // }\n const textureState = this._dtxState;\n const gl = this.model.scene.canvas.gl;\n tempFloat32Array3 [0] = offset[0];\n tempFloat32Array3 [1] = offset[1];\n tempFloat32Array3 [2] = offset[2];\n // object offset\n textureState.texturePerObjectOffsets._textureData.set(tempFloat32Array3, subPortionId * 3);\n if (this._deferredSetFlagsActive) {\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectOffsets._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n 0, // x offset\n subPortionId, // yoffset\n 1, // width\n 1, // height\n gl.RGB,\n gl.FLOAT,\n tempFloat32Array3\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n setMatrix(portionId, matrix) {\n const subPortionIds = this._portionToSubPortionsMap[portionId];\n for (let i = 0, len = subPortionIds.length; i < len; i++) {\n this._subPortionSetMatrix(subPortionIds[i], matrix);\n }\n }\n\n _subPortionSetMatrix(subPortionId, matrix) {\n if (!this._finalized) {\n throw \"Not finalized\";\n }\n // if (!this.model.scene.entityMatrixsEnabled) {\n // this.model.error(\"Entity#matrix not enabled for this Viewer\"); // See Viewer entityMatrixsEnabled\n // return;\n // }\n const textureState = this._dtxState;\n const gl = this.model.scene.canvas.gl;\n tempMat4a.set(matrix);\n textureState.texturePerObjectInstanceMatrices._textureData.set(tempMat4a, subPortionId * 16);\n if (this._deferredSetFlagsActive) {\n this._deferredSetFlagsDirty = true;\n return;\n }\n if (++this._numUpdatesInFrame >= MAX_OBJECT_UPDATES_IN_FRAME_WITHOUT_BATCHED_UPDATE) {\n this._beginDeferredFlags(); // Subsequent flags updates now deferred\n }\n gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectInstanceMatrices._texture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0, // level\n (subPortionId % 512) * 4, // xoffset\n Math.floor(subPortionId / 512), // yoffset\n // 1,\n 4, // width\n 1, // height\n gl.RGBA,\n gl.FLOAT,\n tempMat4a\n );\n // gl.bindTexture (gl.TEXTURE_2D, null);\n }\n\n // ---------------------- COLOR RENDERING -----------------------------------\n\n drawColorOpaque(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (frameCtx.withSAO && this.model.saoEnabled) {\n if (this._renderers.colorRendererWithSAO) {\n this._renderers.colorRendererWithSAO.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n } else {\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n }\n\n _updateBackfaceCull(renderFlags, frameCtx) {\n const backfaces = this.model.backfaces || renderFlags.sectioned;\n if (frameCtx.backfaces !== backfaces) {\n const gl = frameCtx.gl;\n if (backfaces) {\n gl.disable(gl.CULL_FACE);\n } else {\n gl.enable(gl.CULL_FACE);\n }\n frameCtx.backfaces = backfaces;\n }\n }\n\n drawColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.colorRenderer) {\n this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);\n }\n }\n\n // ---------------------- RENDERING SAO POST EFFECT TARGETS --------------\n\n drawDepth(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.depthRenderer) {\n this._renderers.depthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE); // Assume whatever post-effect uses depth (eg SAO) does not apply to transparent objects\n }\n }\n\n drawNormals(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.normalsRenderer) {\n this._renderers.normalsRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE); // Assume whatever post-effect uses normals (eg SAO) does not apply to transparent objects\n }\n }\n\n // ---------------------- SILHOUETTE RENDERING -----------------------------------\n\n drawSilhouetteXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);\n }\n }\n\n drawSilhouetteHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);\n }\n }\n\n drawSilhouetteSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.silhouetteRenderer) {\n this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);\n }\n }\n\n // ---------------------- EDGES RENDERING -----------------------------------\n\n drawEdgesColorOpaque(renderFlags, frameCtx) {\n if (this.model.scene.logarithmicDepthBufferEnabled) {\n if (!this.model.scene._loggedWarning) {\n console.log(\"Edge enhancement for SceneModel data texture layers currently disabled with logarithmic depth buffer\");\n this.model.scene._loggedWarning = true;\n }\n return;\n }\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numEdgesLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesColorRenderer) {\n this._renderers.edgesColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_COLOR_OPAQUE);\n }\n }\n\n drawEdgesColorTransparent(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numEdgesLayerPortions === 0 || this._numTransparentLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesColorRenderer) {\n this._renderers.edgesColorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_COLOR_TRANSPARENT);\n }\n }\n\n drawEdgesHighlighted(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_HIGHLIGHTED);\n }\n }\n\n drawEdgesSelected(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_SELECTED);\n }\n }\n\n drawEdgesXRayed(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {\n return;\n }\n if (this._renderers.edgesRenderer) {\n this._renderers.edgesRenderer.drawLayer(frameCtx, this, RENDER_PASSES.EDGES_XRAYED);\n }\n }\n\n // ---------------------- OCCLUSION CULL RENDERING -----------------------------------\n\n drawOcclusion(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.occlusionRenderer) {\n this._renderers.occlusionRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n // ---------------------- SHADOW BUFFER RENDERING -----------------------------------\n\n drawShadow(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.shadowRenderer) {\n this._renderers.shadowRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);\n }\n }\n\n //---- PICKING ----------------------------------------------------------------------------------------------------\n\n setPickMatrices(pickViewMatrix, pickProjMatrix) {\n // if (this._numVisibleLayerPortions === 0) {\n // return;\n // }\n // this._dtxState.texturePickCameraMatrices.updateViewMatrix(pickViewMatrix, pickProjMatrix);\n }\n\n drawPickMesh(renderFlags, frameCtx) {\n if (this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.pickMeshRenderer) {\n this._renderers.pickMeshRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickDepths(renderFlags, frameCtx) {\n if (this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.pickDepthRenderer) {\n this._renderers.pickDepthRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnapInit(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.snapInitRenderer) {\n this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawSnap(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.snapRenderer) {\n this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n drawPickNormals(renderFlags, frameCtx) {\n if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) {\n return;\n }\n this._updateBackfaceCull(renderFlags, frameCtx);\n if (this._renderers.pickNormalsRenderer) {\n this._renderers.pickNormalsRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK);\n }\n }\n\n destroy() {\n if (this._destroyed) {\n return;\n }\n const state = this._state;\n if (state.metallicRoughnessBuf) {\n state.metallicRoughnessBuf.destroy();\n state.metallicRoughnessBuf = null;\n }\n this.model.scene.off(this._onSceneRendering);\n state.destroy();\n this._destroyed = true;\n }\n}\n\n/**\n * A texture set within a {@link SceneModel}.\n *\n * * Created with {@link SceneModel#createTextureSet}\n * * Belongs to many {@link SceneModelMesh}es\n * * Stored by ID in {@link SceneModel#textureSets}\n * * Referenced by {@link SceneModelMesh#textureSet}\n */\nclass SceneModelTextureSet {\n\n /**\n * @private\n */\n constructor(cfg) {\n\n /**\n * Unique ID of this SceneModelTextureSet.\n *\n * The SceneModelTextureSet is registered against this ID in {@link SceneModel#textureSets}.\n */\n this.id = cfg.id;\n\n /**\n * The color texture.\n * @type {SceneModelTexture|*}\n */\n this.colorTexture = cfg.colorTexture;\n\n /**\n * The metallic-roughness texture.\n * @type {SceneModelTexture|*}\n */\n this.metallicRoughnessTexture = cfg.metallicRoughnessTexture;\n\n /**\n * The normal map texture.\n * @type {SceneModelTexture|*}\n */\n this.normalsTexture = cfg.normalsTexture;\n\n /**\n * The emissive color texture.\n * @type {SceneModelTexture|*}\n */\n this.emissiveTexture = cfg.emissiveTexture;\n\n /**\n * The ambient occlusion texture.\n * @type {SceneModelTexture|*}\n */\n this.occlusionTexture = cfg.occlusionTexture;\n }\n\n /**\n * @private\n */\n destroy() {\n }\n}\n\n/**\n * A texture within a {@link SceneModelTextureSet}.\n *\n * * Created with {@link SceneModel#createTexture}\n * * Belongs to many {@link SceneModelTextureSet}s\n * * Stored by ID in {@link SceneModel#textures}}\n */\nclass SceneModelTexture {\n\n /**\n * @private\n * @param cfg\n */\n constructor(cfg) {\n\n /**\n * Unique ID of this SceneModelTexture.\n *\n * The SceneModelTexture is registered against this ID in {@link SceneModel#textures}.\n */\n this.id = cfg.id;\n\n /**\n * @private\n */\n this.texture = cfg.texture;\n }\n\n /**\n * @private\n */\n destroy() {\n if (this.texture) {\n this.texture.destroy();\n this.texture = null;\n }\n }\n}\n\nconst Cache$1 = {\n\n enabled: false,\n files: {},\n\n add: function (key, file) {\n if (this.enabled === false) {\n return;\n }\n this.files[key] = file;\n },\n\n get: function (key) {\n if (this.enabled === false) {\n return;\n }\n return this.files[key];\n },\n\n remove: function (key) {\n delete this.files[key];\n },\n\n clear: function () {\n this.files = {};\n }\n};\n\nclass LoadingManager {\n\n constructor(onLoad, onProgress, onError) {\n\n this.isLoading = false;\n this.itemsLoaded = 0;\n this.itemsTotal = 0;\n this.urlModifier = undefined;\n this.handlers = [];\n\n this.onStart = undefined;\n this.onLoad = onLoad;\n this.onProgress = onProgress;\n this.onError = onError;\n }\n\n itemStart(url) {\n this.itemsTotal++;\n if (this.isLoading === false) {\n if (this.onStart !== undefined) {\n this.onStart(url, this.itemsLoaded, this.itemsTotal);\n }\n }\n this.isLoading = true;\n }\n\n itemEnd(url) {\n this.itemsLoaded++;\n if (this.onProgress !== undefined) {\n this.onProgress(url, this.itemsLoaded, this.itemsTotal);\n }\n if (this.itemsLoaded === this.itemsTotal) {\n this.isLoading = false;\n if (this.onLoad !== undefined) {\n this.onLoad();\n }\n }\n }\n\n itemError(url) {\n if (this.onError !== undefined) {\n this.onError(url);\n }\n }\n\n resolveURL(url) {\n if (this.urlModifier) {\n return this.urlModifier(url);\n }\n return url;\n }\n\n setURLModifier(transform) {\n this.urlModifier = transform;\n return this;\n }\n\n addHandler(regex, loader) {\n this.handlers.push(regex, loader);\n return this;\n }\n\n removeHandler(regex) {\n const index = this.handlers.indexOf(regex);\n if (index !== -1) {\n this.handlers.splice(index, 2);\n }\n return this;\n }\n\n getHandler(file) {\n for (let i = 0, l = this.handlers.length; i < l; i += 2) {\n const regex = this.handlers[i];\n const loader = this.handlers[i + 1];\n if (regex.global) regex.lastIndex = 0; // see #17920\n if (regex.test(file)) {\n return loader;\n }\n }\n return null;\n }\n}\n\nconst DefaultLoadingManager = new LoadingManager();\n\nclass Loader {\n\n constructor(manager) {\n\n this.manager = (manager !== undefined) ? manager : DefaultLoadingManager;\n\n this.crossOrigin = 'anonymous';\n this.withCredentials = false;\n this.path = '';\n this.resourcePath = '';\n this.requestHeader = {};\n }\n\n load( /* url, onLoad, onProgress, onError */) {\n }\n\n loadAsync(url, onProgress) {\n const scope = this;\n return new Promise(function (resolve, reject) {\n scope.load(url, resolve, onProgress, reject);\n });\n }\n\n parse( /* data */) {\n }\n\n setCrossOrigin(crossOrigin) {\n this.crossOrigin = crossOrigin;\n return this;\n }\n\n setWithCredentials(value) {\n this.withCredentials = value;\n return this;\n }\n\n setPath(path) {\n this.path = path;\n return this;\n }\n\n setResourcePath(resourcePath) {\n this.resourcePath = resourcePath;\n return this;\n }\n\n setRequestHeader(requestHeader) {\n this.requestHeader = requestHeader;\n return this;\n }\n}\n\nconst loading = {};\n\nclass FileLoader extends Loader {\n\n constructor(manager) {\n super(manager);\n }\n\n load(url, onLoad, onProgress, onError) {\n if (url === undefined) {\n url = '';\n }\n if (this.path !== undefined) {\n url = this.path + url;\n }\n url = this.manager.resolveURL(url);\n const cached = Cache$1.get(url);\n if (cached !== undefined) {\n this.manager.itemStart(url);\n core.scheduleTask(() => {\n if (onLoad) {\n onLoad(cached);\n }\n this.manager.itemEnd(url);\n }, 0);\n return cached;\n }\n if (loading[url] !== undefined) {\n loading[url].push({onLoad, onProgress, onError});\n return;\n }\n loading[url] = [];\n loading[url].push({onLoad, onProgress, onError});\n const req = new Request(url, {\n headers: new Headers(this.requestHeader),\n credentials: this.withCredentials ? 'include' : 'same-origin'\n });\n const mimeType = this.mimeType;\n const responseType = this.responseType;\n fetch(req).then(response => {\n if (response.status === 200 || response.status === 0) {\n // Some browsers return HTTP Status 0 when using non-http protocol\n // e.g. 'file://' or 'data://'. Handle as success.\n if (response.status === 0) {\n console.warn('FileLoader: HTTP Status 0 received.');\n }\n if (typeof ReadableStream === 'undefined' || response.body.getReader === undefined) {\n return response;\n }\n const callbacks = loading[url];\n const reader = response.body.getReader();\n const contentLength = response.headers.get('Content-Length');\n const total = contentLength ? parseInt(contentLength) : 0;\n const lengthComputable = total !== 0;\n let loaded = 0;\n const stream = new ReadableStream({\n start(controller) {\n readData();\n\n function readData() {\n reader.read().then(({done, value}) => {\n if (done) {\n controller.close();\n } else {\n loaded += value.byteLength;\n const event = new ProgressEvent('progress', {lengthComputable, loaded, total});\n for (let i = 0, il = callbacks.length; i < il; i++) {\n const callback = callbacks[i];\n if (callback.onProgress) {\n callback.onProgress(event);\n }\n }\n controller.enqueue(value);\n readData();\n }\n });\n }\n }\n });\n return new Response(stream);\n } else {\n throw Error(`fetch for \"${response.url}\" responded with ${response.status}: ${response.statusText}`);\n }\n }).then(response => {\n switch (responseType) {\n case 'arraybuffer':\n return response.arrayBuffer();\n case 'blob':\n return response.blob();\n case 'document':\n return response.text()\n .then(text => {\n const parser = new DOMParser();\n return parser.parseFromString(text, mimeType);\n });\n case 'json':\n return response.json();\n default:\n if (mimeType === undefined) {\n return response.text();\n } else {\n // sniff encoding\n const re = /charset=\"?([^;\"\\s]*)\"?/i;\n const exec = re.exec(mimeType);\n const label = exec && exec[1] ? exec[1].toLowerCase() : undefined;\n const decoder = new TextDecoder(label);\n return response.arrayBuffer().then(ab => decoder.decode(ab));\n }\n }\n }).then(data => {\n // Add to cache only on HTTP success, so that we do not cache\n // error response bodies as proper responses to requests.\n Cache$1.add(url, data);\n const callbacks = loading[url];\n delete loading[url];\n for (let i = 0, il = callbacks.length; i < il; i++) {\n const callback = callbacks[i];\n if (callback.onLoad) {\n callback.onLoad(data);\n }\n }\n }).catch(err => {\n // Abort errors and other errors are handled the same\n const callbacks = loading[url];\n if (callbacks === undefined) {\n // When onLoad was called and url was deleted in `loading`\n this.manager.itemError(url);\n throw err;\n\n }\n delete loading[url];\n for (let i = 0, il = callbacks.length; i < il; i++) {\n const callback = callbacks[i];\n if (callback.onError) {\n callback.onError(err);\n }\n }\n this.manager.itemError(url);\n }).finally(() => {\n this.manager.itemEnd(url);\n });\n this.manager.itemStart(url);\n }\n\n setResponseType(value) {\n this.responseType = value;\n return this;\n }\n\n setMimeType(value) {\n this.mimeType = value;\n return this;\n }\n}\n\n/**\n * @author Deepkolos / https://github.com/deepkolos\n */\n\nclass WorkerPool$1 {\n\n constructor(pool = 4) {\n this.pool = pool;\n this.queue = [];\n this.workers = [];\n this.workersResolve = [];\n this.workerStatus = 0;\n }\n\n _initWorker(workerId) {\n if (!this.workers[workerId]) {\n const worker = this.workerCreator();\n worker.addEventListener('message', this._onMessage.bind(this, workerId));\n this.workers[workerId] = worker;\n }\n }\n\n _getIdleWorker() {\n for (let i = 0; i < this.pool; i++)\n if (!(this.workerStatus & (1 << i))) return i;\n return -1;\n }\n\n _onMessage(workerId, msg) {\n const resolve = this.workersResolve[workerId];\n resolve && resolve(msg);\n if (this.queue.length) {\n const {resolve, msg, transfer} = this.queue.shift();\n this.workersResolve[workerId] = resolve;\n this.workers[workerId].postMessage(msg, transfer);\n } else {\n this.workerStatus ^= 1 << workerId;\n }\n }\n\n setWorkerCreator(workerCreator) {\n this.workerCreator = workerCreator;\n }\n\n setWorkerLimit(pool) {\n this.pool = pool;\n }\n\n postMessage(msg, transfer) {\n return new Promise((resolve) => {\n const workerId = this._getIdleWorker();\n if (workerId !== -1) {\n this._initWorker(workerId);\n this.workerStatus |= 1 << workerId;\n this.workersResolve[workerId] = resolve;\n this.workers[workerId].postMessage(msg, transfer);\n } else {\n this.queue.push({resolve, msg, transfer});\n }\n });\n }\n\n destroy() {\n\n this.workers.forEach((worker) => worker.terminate());\n this.workersResolve.length = 0;\n this.workers.length = 0;\n this.queue.length = 0;\n this.workerStatus = 0;\n\n }\n\n}\n\nconst KTX2TransferSRGB = 2;\nconst KTX2_ALPHA_PREMULTIPLIED = 1;\n\nlet activeTranscoders = 0;\n\n/**\n * Transcodes texture data from KTX2.\n *\n * ## Overview\n *\n * * Uses the [Basis Universal GPU Texture Codec](https://github.com/BinomialLLC/basis_universal) to\n * transcode [KTX2](https://github.khronos.org/KTX-Specification/) textures.\n * * {@link XKTLoaderPlugin} uses a KTX2TextureTranscoder to load textures in XKT files.\n * * {@link VBOSceneModel} uses a KTX2TextureTranscoder to enable us to add KTX2-encoded textures.\n * * Loads the Basis Codec from [CDN](https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/) by default, but can\n * also be configured to load the Codec from local files.\n * * We also bundle the Basis Codec with the xeokit-sdk npm package, and in the [repository](https://github.com/xeokit/xeokit-sdk/tree/master/dist/basis).\n *\n * ## What is KTX2?\n *\n * A [KTX2](https://github.khronos.org/KTX-Specification/) file stores GPU texture data in the Khronos Texture 2.0 (KTX2) container format. It contains image data for\n * a texture asset compressed with Basis Universal (BasisU) supercompression that can be transcoded to different formats\n * depending on the support provided by the target devices. KTX2 provides a lightweight format for distributing texture\n * assets to GPUs. Due to BasisU compression, KTX2 files can store any image format supported by GPUs.\n *\n * ## Loading XKT files containing KTX2 textures\n *\n * {@link XKTLoaderPlugin} uses a KTX2TextureTranscoder to load textures in XKT files. An XKTLoaderPlugin has its own\n * default KTX2TextureTranscoder, configured to load the Basis Codec from the [CDN](https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/). If we wish, we can override that with our own\n * KTX2TextureTranscoder, configured to load the Codec locally.\n *\n * In the example below, we'll create a {@link Viewer} and add an {@link XKTLoaderPlugin}\n * configured with a KTX2TextureTranscoder. Then we'll use the XKTLoaderPlugin to load an\n * XKT file that contains KTX2 textures, which the plugin will transcode using\n * its KTX2TextureTranscoder.\n *\n * We'll configure our KTX2TextureTranscoder to load the Basis Codec from a local directory. If we were happy with loading the\n * Codec from our [CDN](https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/) (ie. our app will always have an Internet connection) then we could just leave out the\n * KTX2TextureTranscoder altogether, and let the XKTLoaderPlugin use its internal default KTX2TextureTranscoder, which is configured to\n * load the Codec from the CDN. We'll stick with loading our own Codec, in case we want to run our app without an Internet connection.\n *\n * \n *\n * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/buildings/#xkt_vbo_textures_HousePlan)]\n *\n * ````javascript\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.camera.eye = [-2.56, 8.38, 8.27];\n * viewer.camera.look = [13.44, 3.31, -14.83];\n * viewer.camera.up = [0.10, 0.98, -0.14];\n *\n * const textureTranscoder = new KTX2TextureTranscoder({\n * viewer,\n * transcoderPath: \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\" // <------ Path to Basis Universal transcoder\n * });\n *\n * const xktLoader = new XKTLoaderPlugin(viewer, {\n * textureTranscoder // <<------------- Transcodes KTX2 textures in XKT files\n * });\n *\n * const sceneModel = xktLoader.load({\n * id: \"myModel\",\n * src: \"./HousePlan.xkt\" // <<------ XKT file with KTX2 textures\n * });\n * ````\n *\n * ## Loading KTX2 files into a VBOSceneModel\n *\n * A {@link SceneModel} that is configured with a KTX2TextureTranscoder will\n * allow us to load textures into it from KTX2-transcoded buffers or files.\n *\n * In the example below, we'll create a {@link Viewer}, containing a {@link VBOSceneModel} configured with a\n * KTX2TextureTranscoder.\n *\n * We'll then programmatically create a simple object within the VBOSceneModel, consisting of\n * a single box mesh with a texture loaded from a KTX2 file, which our VBOSceneModel internally transcodes, using\n * its KTX2TextureTranscoder.\n *\n * As in the previous example, we'll configure our KTX2TextureTranscoder to load the Basis Codec from a local directory.\n *\n * * [Run a similar example](https://xeokit.github.io/xeokit-sdk/examples/scenemodel/#vbo_batching_autocompressed_triangles_textures_ktx2)\n *\n * ````javascript\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * const textureTranscoder = new KTX2TextureTranscoder({\n * viewer,\n * transcoderPath: \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\" // <------ Path to BasisU transcoder module\n * });\n *\n * const vboSceneModel = new VBOSceneModel(viewer.scene, {\n * id: \"myModel\",\n * textureTranscoder // <<-------------------- Configure model with our transcoder\n * });\n *\n * vboSceneModel.createTexture({\n * id: \"myColorTexture\",\n * src: \"../assets/textures/compressed/sample_uastc_zstd.ktx2\" // <<----- KTX2 texture asset\n * });\n *\n * vboSceneModel.createTexture({\n * id: \"myMetallicRoughnessTexture\",\n * src: \"../assets/textures/alpha/crosshatchAlphaMap.jpg\" // <<----- JPEG texture asset\n * });\n *\n * vboSceneModel.createTextureSet({\n * id: \"myTextureSet\",\n * colorTextureId: \"myColorTexture\",\n * metallicRoughnessTextureId: \"myMetallicRoughnessTexture\"\n * });\n *\n * vboSceneModel.createMesh({\n * id: \"myMesh\",\n * textureSetId: \"myTextureSet\",\n * primitive: \"triangles\",\n * positions: [1, 1, 1, ...],\n * normals: [0, 0, 1, 0, ...],\n * uv: [1, 0, 0, ...],\n * indices: [0, 1, 2, ...],\n * });\n *\n * vboSceneModel.createEntity({\n * id: \"myEntity\",\n * meshIds: [\"myMesh\"]\n * });\n *\n * vboSceneModel.finalize();\n * ````\n *\n * ## Loading KTX2 ArrayBuffers into a VBOSceneModel\n *\n * A {@link SceneModel} that is configured with a KTX2TextureTranscoder will also allow us to load textures into\n * it from KTX2 ArrayBuffers.\n *\n * In the example below, we'll create a {@link Viewer}, containing a {@link VBOSceneModel} configured with a\n * KTX2TextureTranscoder.\n *\n * We'll then programmatically create a simple object within the VBOSceneModel, consisting of\n * a single mesh with a texture loaded from a KTX2 ArrayBuffer, which our VBOSceneModel internally transcodes, using\n * its KTX2TextureTranscoder.\n *\n * ````javascript\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * const textureTranscoder = new KTX2TextureTranscoder({\n * viewer,\n * transcoderPath: \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\" // <------ Path to BasisU transcoder module\n * });\n *\n * const vboSceneModel = new VBOSceneModel(viewer.scene, {\n * id: \"myModel\",\n * textureTranscoder // <<-------------------- Configure model with our transcoder\n * });\n *\n * utils.loadArraybuffer(\"../assets/textures/compressed/sample_uastc_zstd.ktx2\",(arrayBuffer) => {\n *\n * vboSceneModel.createTexture({\n * id: \"myColorTexture\",\n * buffers: [arrayBuffer] // <<----- KTX2 texture asset\n * });\n *\n * vboSceneModel.createTexture({\n * id: \"myMetallicRoughnessTexture\",\n * src: \"../assets/textures/alpha/crosshatchAlphaMap.jpg\" // <<----- JPEG texture asset\n * });\n *\n * vboSceneModel.createTextureSet({\n * id: \"myTextureSet\",\n * colorTextureId: \"myColorTexture\",\n * metallicRoughnessTextureId: \"myMetallicRoughnessTexture\"\n * });\n *\n * vboSceneModel.createMesh({\n * id: \"myMesh\",\n * textureSetId: \"myTextureSet\",\n * primitive: \"triangles\",\n * positions: [1, 1, 1, ...],\n * normals: [0, 0, 1, 0, ...],\n * uv: [1, 0, 0, ...],\n * indices: [0, 1, 2, ...],\n * });\n *\n * vboSceneModel.createEntity({\n * id: \"myEntity\",\n * meshIds: [\"myMesh\"]\n * });\n *\n * vboSceneModel.finalize();\n * });\n * ````\n *\n * @implements {TextureTranscoder}\n */\nclass KTX2TextureTranscoder {\n\n /**\n * Creates a new KTX2TextureTranscoder.\n *\n * @param {Viewer} viewer The Viewer that our KTX2TextureTranscoder will be used with. This KTX2TextureTranscoder\n * must only be used to transcode textures for this Viewer. This is because the Viewer's capabilities will decide\n * what target GPU formats this KTX2TextureTranscoder will transcode to.\n * @param {String} [transcoderPath=\"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\"] Path to the Basis\n * transcoder module that internally does the heavy lifting for our KTX2TextureTranscoder. If we omit this configuration,\n * then our KTX2TextureTranscoder will load it from ````https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/```` by\n * default. Therefore, make sure your application is connected to the internet if you wish to use the default transcoder path.\n * @param {Number} [workerLimit] The maximum number of Workers to use for transcoding.\n */\n constructor({viewer, transcoderPath, workerLimit}) {\n\n this._transcoderPath = transcoderPath || \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\";\n this._transcoderBinary = null;\n this._transcoderPending = null;\n this._workerPool = new WorkerPool$1();\n this._workerSourceURL = '';\n\n if (workerLimit) {\n this._workerPool.setWorkerLimit(workerLimit);\n }\n\n const viewerCapabilities = viewer.capabilities;\n\n this._workerConfig = {\n astcSupported: viewerCapabilities.astcSupported,\n etc1Supported: viewerCapabilities.etc1Supported,\n etc2Supported: viewerCapabilities.etc2Supported,\n dxtSupported: viewerCapabilities.dxtSupported,\n bptcSupported: viewerCapabilities.bptcSupported,\n pvrtcSupported: viewerCapabilities.pvrtcSupported\n };\n\n this._supportedFileTypes = [\"xkt2\"];\n }\n\n _init() {\n if (!this._transcoderPending) {\n const jsLoader = new FileLoader();\n jsLoader.setPath(this._transcoderPath);\n jsLoader.setWithCredentials(this.withCredentials);\n const jsContent = jsLoader.loadAsync('basis_transcoder.js');\n const binaryLoader = new FileLoader();\n binaryLoader.setPath(this._transcoderPath);\n binaryLoader.setResponseType('arraybuffer');\n binaryLoader.setWithCredentials(this.withCredentials);\n const binaryContent = binaryLoader.loadAsync('basis_transcoder.wasm');\n this._transcoderPending = Promise.all([jsContent, binaryContent])\n .then(([jsContent, binaryContent]) => {\n const fn = KTX2TextureTranscoder.BasisWorker.toString();\n const body = [\n '/* constants */',\n 'let _EngineFormat = ' + JSON.stringify(KTX2TextureTranscoder.EngineFormat),\n 'let _TranscoderFormat = ' + JSON.stringify(KTX2TextureTranscoder.TranscoderFormat),\n 'let _BasisFormat = ' + JSON.stringify(KTX2TextureTranscoder.BasisFormat),\n '/* basis_transcoder.js */',\n jsContent,\n '/* worker */',\n fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}'))\n ].join('\\n');\n this._workerSourceURL = URL.createObjectURL(new Blob([body]));\n this._transcoderBinary = binaryContent;\n this._workerPool.setWorkerCreator(() => {\n const worker = new Worker(this._workerSourceURL);\n const transcoderBinary = this._transcoderBinary.slice(0);\n worker.postMessage({\n type: 'init',\n config: this._workerConfig,\n transcoderBinary\n }, [transcoderBinary]);\n return worker;\n });\n });\n if (activeTranscoders > 0) {\n console.warn('KTX2TextureTranscoder: Multiple active KTX2TextureTranscoder may cause performance issues.' + ' Use a single KTX2TextureTranscoder instance, or call .dispose() on old instances.');\n }\n activeTranscoders++;\n }\n return this._transcoderPending;\n }\n\n /**\n * Transcodes texture data from transcoded buffers into a {@link Texture2D}.\n *\n * @param {ArrayBuffer[]} buffers Transcoded texture data. Given as an array of buffers so that we can support multi-image textures, such as cube maps.\n * @param {*} config Transcoding options.\n * @param {Texture2D} texture The texture to load.\n * @returns {Promise} Resolves when the texture has loaded.\n */\n transcode(buffers, texture, config = {}) {\n return new Promise((resolve, reject) => {\n const taskConfig = config;\n this._init().then(() => {\n return this._workerPool.postMessage({\n type: 'transcode',\n buffers,\n taskConfig: taskConfig\n }, buffers);\n }).then((e) => {\n const transcodeResult = e.data;\n const {mipmaps, width, height, format, type, error, dfdTransferFn, dfdFlags} = transcodeResult;\n if (type === 'error') {\n return reject(error);\n }\n texture.setCompressedData({\n mipmaps,\n props: {\n format: format,\n minFilter: mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter,\n magFilter: mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter,\n encoding: dfdTransferFn === KTX2TransferSRGB ? sRGBEncoding : LinearEncoding,\n premultiplyAlpha: !!(dfdFlags & KTX2_ALPHA_PREMULTIPLIED)\n }\n });\n resolve();\n });\n });\n }\n\n /**\n * Destroys this KTX2TextureTranscoder\n */\n destroy() {\n URL.revokeObjectURL(this._workerSourceURL);\n this._workerPool.destroy();\n activeTranscoders--;\n }\n}\n\n/**\n * @private\n */\nKTX2TextureTranscoder.BasisFormat = {\n ETC1S: 0,\n UASTC_4x4: 1\n};\n\n/**\n * @private\n */\nKTX2TextureTranscoder.TranscoderFormat = {\n ETC1: 0,\n ETC2: 1,\n BC1: 2,\n BC3: 3,\n BC4: 4,\n BC5: 5,\n BC7_M6_OPAQUE_ONLY: 6,\n BC7_M5: 7,\n PVRTC1_4_RGB: 8,\n PVRTC1_4_RGBA: 9,\n ASTC_4x4: 10,\n ATC_RGB: 11,\n ATC_RGBA_INTERPOLATED_ALPHA: 12,\n RGBA32: 13,\n RGB565: 14,\n BGR565: 15,\n RGBA4444: 16\n};\n\n/**\n * @private\n */\nKTX2TextureTranscoder.EngineFormat = {\n RGBAFormat: RGBAFormat,\n RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format,\n RGBA_BPTC_Format: RGBA_BPTC_Format,\n RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format,\n RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format,\n RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format,\n RGB_ETC1_Format: RGB_ETC1_Format,\n RGB_ETC2_Format: RGB_ETC2_Format,\n RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format,\n RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format\n};\n\n/* WEB WORKER */\n\n/**\n * @private\n * @constructor\n */\nKTX2TextureTranscoder.BasisWorker = function () {\n\n let config;\n let transcoderPending;\n let BasisModule;\n\n const EngineFormat = _EngineFormat; // eslint-disable-line no-undef\n const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef\n const BasisFormat = _BasisFormat; // eslint-disable-line no-undef\n\n self.addEventListener('message', function (e) {\n const message = e.data;\n switch (message.type) {\n case 'init':\n config = message.config;\n init(message.transcoderBinary);\n break;\n case 'transcode':\n transcoderPending.then(() => {\n try {\n const {\n width,\n height,\n hasAlpha,\n mipmaps,\n format,\n dfdTransferFn,\n dfdFlags\n } = transcode(message.buffers[0]);\n const buffers = [];\n for (let i = 0; i < mipmaps.length; ++i) {\n buffers.push(mipmaps[i].data.buffer);\n }\n self.postMessage({\n type: 'transcode',\n id: message.id,\n width,\n height,\n hasAlpha,\n mipmaps,\n format,\n dfdTransferFn,\n dfdFlags\n }, buffers);\n } catch (error) {\n console.error(`[KTX2TextureTranscoder.BasisWorker]: ${error}`);\n self.postMessage({type: 'error', id: message.id, error: error.message});\n }\n });\n break;\n }\n });\n\n function init(wasmBinary) {\n transcoderPending = new Promise(resolve => {\n BasisModule = {\n wasmBinary,\n onRuntimeInitialized: resolve\n };\n BASIS(BasisModule); // eslint-disable-line no-undef\n }).then(() => {\n BasisModule.initializeBasis();\n if (BasisModule.KTX2File === undefined) {\n console.warn('KTX2TextureTranscoder: Please update Basis Universal transcoder.');\n }\n });\n }\n\n function transcode(buffer) {\n const ktx2File = new BasisModule.KTX2File(new Uint8Array(buffer));\n\n function cleanup() {\n ktx2File.close();\n ktx2File.delete();\n }\n\n if (!ktx2File.isValid()) {\n cleanup();\n throw new Error('KTX2TextureTranscoder: Invalid or unsupported .ktx2 file');\n }\n const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;\n const width = ktx2File.getWidth();\n const height = ktx2File.getHeight();\n const levels = ktx2File.getLevels();\n const hasAlpha = ktx2File.getHasAlpha();\n const dfdTransferFn = ktx2File.getDFDTransferFunc();\n const dfdFlags = ktx2File.getDFDFlags();\n const {transcoderFormat, engineFormat} = getTranscoderFormat(basisFormat, width, height, hasAlpha);\n if (!width || !height || !levels) {\n cleanup();\n throw new Error('KTX2TextureTranscoder: Invalid texture');\n }\n if (!ktx2File.startTranscoding()) {\n cleanup();\n throw new Error('KTX2TextureTranscoder: .startTranscoding failed');\n }\n const mipmaps = [];\n for (let mip = 0; mip < levels; mip++) {\n const levelInfo = ktx2File.getImageLevelInfo(mip, 0, 0);\n const mipWidth = levelInfo.origWidth;\n const mipHeight = levelInfo.origHeight;\n const dst = new Uint8Array(ktx2File.getImageTranscodedSizeInBytes(mip, 0, 0, transcoderFormat));\n const status = ktx2File.transcodeImage(dst, mip, 0, 0, transcoderFormat, 0, -1, -1);\n if (!status) {\n cleanup();\n throw new Error('KTX2TextureTranscoder: .transcodeImage failed.');\n }\n mipmaps.push({data: dst, width: mipWidth, height: mipHeight});\n }\n cleanup();\n return {width, height, hasAlpha, mipmaps, format: engineFormat, dfdTransferFn, dfdFlags};\n }\n\n // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),\n // device capabilities, and texture dimensions. The list below ranks the formats separately\n // for ETC1S and UASTC.\n //\n // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at\n // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently\n // chooses RGBA32 only as a last resort and does not expose that option to the caller.\n\n const FORMAT_OPTIONS = [{\n if: 'astcSupported',\n basisFormat: [BasisFormat.UASTC_4x4],\n transcoderFormat: [TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4],\n engineFormat: [EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format],\n priorityETC1S: Infinity,\n priorityUASTC: 1,\n needsPowerOfTwo: false\n }, {\n if: 'bptcSupported',\n basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],\n transcoderFormat: [TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5],\n engineFormat: [EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format],\n priorityETC1S: 3,\n priorityUASTC: 2,\n needsPowerOfTwo: false\n }, {\n if: 'dxtSupported',\n basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],\n transcoderFormat: [TranscoderFormat.BC1, TranscoderFormat.BC3],\n engineFormat: [EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format],\n priorityETC1S: 4,\n priorityUASTC: 5,\n needsPowerOfTwo: false\n }, {\n if: 'etc2Supported',\n basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],\n transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC2],\n engineFormat: [EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format],\n priorityETC1S: 1,\n priorityUASTC: 3,\n needsPowerOfTwo: false\n }, {\n if: 'etc1Supported',\n basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],\n transcoderFormat: [TranscoderFormat.ETC1],\n engineFormat: [EngineFormat.RGB_ETC1_Format],\n priorityETC1S: 2,\n priorityUASTC: 4,\n needsPowerOfTwo: false\n }, {\n if: 'pvrtcSupported',\n basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4],\n transcoderFormat: [TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA],\n engineFormat: [EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format],\n priorityETC1S: 5,\n priorityUASTC: 6,\n needsPowerOfTwo: true\n }];\n const ETC1S_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) {\n return a.priorityETC1S - b.priorityETC1S;\n });\n const UASTC_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) {\n return a.priorityUASTC - b.priorityUASTC;\n });\n\n function getTranscoderFormat(basisFormat, width, height, hasAlpha) {\n let transcoderFormat;\n let engineFormat;\n const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;\n for (let i = 0; i < options.length; i++) {\n const opt = options[i];\n if (!config[opt.if]) continue;\n if (!opt.basisFormat.includes(basisFormat)) continue;\n if (hasAlpha && opt.transcoderFormat.length < 2) continue;\n if (opt.needsPowerOfTwo && !(isPowerOfTwo(width) && isPowerOfTwo(height))) continue;\n transcoderFormat = opt.transcoderFormat[hasAlpha ? 1 : 0];\n engineFormat = opt.engineFormat[hasAlpha ? 1 : 0];\n return {\n transcoderFormat,\n engineFormat\n };\n }\n console.warn('KTX2TextureTranscoder: No suitable compressed texture format found. Decoding to RGBA32.');\n transcoderFormat = TranscoderFormat.RGBA32;\n engineFormat = EngineFormat.RGBAFormat;\n return {\n transcoderFormat,\n engineFormat\n };\n }\n\n function isPowerOfTwo(value) {\n if (value <= 2) return true;\n return (value & value - 1) === 0 && value !== 0;\n }\n};\n\nconst cachedTranscoders = {};\n\n/**\n * Returns a new {@link KTX2TextureTranscoder}.\n *\n * The ````transcoderPath```` config will be set to: \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\"\n *\n * @private\n */\nfunction getKTX2TextureTranscoder(viewer) {\n const sceneId = viewer.scene.id;\n let transcoder = cachedTranscoders[sceneId];\n if (!transcoder) {\n transcoder = new KTX2TextureTranscoder({viewer});\n cachedTranscoders[sceneId] = transcoder;\n viewer.scene.on(\"destroyed\", () => {\n delete cachedTranscoders[sceneId];\n transcoder.destroy();\n });\n }\n return transcoder;\n}\n\n/**\n * @author https://github.com/tmarti, with support from https://tribia.com/\n * @license MIT\n *\n * This file takes a geometry given by { positionsCompressed, indices }, and returns\n * equivalent { positionsCompressed, indices } arrays but which only contain unique\n * positionsCompressed.\n *\n * The time is O(N logN) with the number of positionsCompressed due to a pre-sorting\n * step, but is much more GC-friendly and actually faster than the classic O(N)\n * approach based in keeping a hash-based LUT to identify unique positionsCompressed.\n */\nlet comparePositions = null;\n\nfunction compareVertex(a, b) {\n let res;\n for (let i = 0; i < 3; i++) {\n if (0 !== (res = comparePositions[a * 3 + i] - comparePositions[b * 3 + i])) {\n return res;\n }\n }\n return 0;\n}\n\nlet seqInit = null;\n\nfunction setMaxNumberOfPositions(maxPositions) {\n if (seqInit !== null && seqInit.length >= maxPositions) {\n return;\n }\n seqInit = new Uint32Array(maxPositions);\n for (let i = 0; i < maxPositions; i++) {\n seqInit[i] = i;\n }\n}\n\n/**\n * This function obtains unique positionsCompressed in the provided object\n * .positionsCompressed array and calculates an index mapping, which is then\n * applied to the provided object .indices and .edgeindices.\n *\n * The input object items are not modified, and instead new set\n * of positionsCompressed, indices and edgeIndices with the applied optimization\n * are returned.\n *\n * The algorithm, instead of being based in a hash-like LUT for\n * identifying unique positionsCompressed, is based in pre-sorting the input\n * positionsCompressed...\n *\n * (it's possible to define a _\"consistent ordering\"_ for the positionsCompressed\n * as positionsCompressed are quantized and thus not suffer from float number\n * comparison artifacts)\n *\n * ... so same positionsCompressed are adjacent in the sorted array, and then\n * it's easy to scan linearly the sorted array. During the linear run,\n * we will know that we found a different position because the comparison\n * function will return != 0 between current and previous element.\n *\n * During this linear traversal of the array, a `unique counter` is used\n * in order to calculate the mapping between original indices and unique\n * indices.\n *\n * @param {{positionsCompressed: number[],indices: number[], edgeIndices: number[]}} mesh The input mesh to process, with `positionsCompressed`, `indices` and `edgeIndices` keys.\n *\n * @returns {[Uint16Array, Uint32Array, Uint32Array]} An array with 3 elements: 0 => the uniquified positionsCompressed; 1 and 2 => the remapped edges and edgeIndices arrays\n */\nfunction uniquifyPositions(mesh) {\n const _positions = mesh.positionsCompressed;\n const _indices = mesh.indices;\n const _edgeIndices = mesh.edgeIndices;\n\n setMaxNumberOfPositions(_positions.length / 3);\n\n const seq = seqInit.slice(0, _positions.length / 3);\n const remappings = seqInit.slice(0, _positions.length / 3);\n\n comparePositions = _positions;\n\n seq.sort(compareVertex);\n\n let uniqueIdx = 0;\n\n remappings[seq[0]] = 0;\n\n for (let i = 1, len = seq.length; i < len; i++) {\n if (0 !== compareVertex(seq[i], seq[i - 1])) {\n uniqueIdx++;\n }\n remappings[seq[i]] = uniqueIdx;\n }\n\n const numUniquePositions = uniqueIdx + 1;\n const newPositions = new Uint16Array(numUniquePositions * 3);\n\n uniqueIdx = 0;\n\n newPositions [uniqueIdx * 3 + 0] = _positions [seq[0] * 3 + 0];\n newPositions [uniqueIdx * 3 + 1] = _positions [seq[0] * 3 + 1];\n newPositions [uniqueIdx * 3 + 2] = _positions [seq[0] * 3 + 2];\n\n for (let i = 1, len = seq.length; i < len; i++) {\n if (0 !== compareVertex(seq[i], seq[i - 1])) {\n uniqueIdx++;\n newPositions [uniqueIdx * 3 + 0] = _positions [seq[i] * 3 + 0];\n newPositions [uniqueIdx * 3 + 1] = _positions [seq[i] * 3 + 1];\n newPositions [uniqueIdx * 3 + 2] = _positions [seq[i] * 3 + 2];\n }\n remappings[seq[i]] = uniqueIdx;\n }\n\n comparePositions = null;\n\n const newIndices = new Uint32Array(_indices.length);\n\n for (let i = 0, len = _indices.length; i < len; i++) {\n newIndices[i] = remappings [_indices[i]];\n }\n\n const newEdgeIndices = new Uint32Array(_edgeIndices.length);\n\n for (let i = 0, len = _edgeIndices.length; i < len; i++) {\n newEdgeIndices[i] = remappings [_edgeIndices[i]];\n }\n\n return [newPositions, newIndices, newEdgeIndices];\n}\n\n/**\n * @author https://github.com/tmarti, with support from https://tribia.com/\n * @license MIT\n **/\n\nconst MAX_RE_BUCKET_FAN_OUT = 8;\n\nlet bucketsForIndices = null;\n\nfunction compareBuckets(a, b) {\n const aa = a * 3;\n const bb = b * 3;\n let aa1, aa2, aa3, bb1, bb2, bb3;\n const minBucketA = Math.min(\n aa1 = bucketsForIndices[aa],\n aa2 = bucketsForIndices[aa + 1],\n aa3 = bucketsForIndices[aa + 2]\n );\n const minBucketB = Math.min(\n bb1 = bucketsForIndices[bb],\n bb2 = bucketsForIndices[bb + 1],\n bb3 = bucketsForIndices[bb + 2]\n );\n if (minBucketA !== minBucketB) {\n return minBucketA - minBucketB;\n }\n const maxBucketA = Math.max(aa1, aa2, aa3);\n const maxBucketB = Math.max(bb1, bb2, bb3,);\n if (maxBucketA !== maxBucketB) {\n return maxBucketA - maxBucketB;\n }\n return 0;\n}\n\nfunction preSortIndices(indices, bitsPerBucket) {\n const seq = new Int32Array(indices.length / 3);\n for (let i = 0, len = seq.length; i < len; i++) {\n seq[i] = i;\n }\n bucketsForIndices = new Int32Array(indices.length);\n for (let i = 0, len = indices.length; i < len; i++) {\n bucketsForIndices[i] = indices[i] >> bitsPerBucket;\n }\n seq.sort(compareBuckets);\n const sortedIndices = new Int32Array(indices.length);\n for (let i = 0, len = seq.length; i < len; i++) {\n sortedIndices[i * 3 + 0] = indices[seq[i] * 3 + 0];\n sortedIndices[i * 3 + 1] = indices[seq[i] * 3 + 1];\n sortedIndices[i * 3 + 2] = indices[seq[i] * 3 + 2];\n }\n return sortedIndices;\n}\n\nlet compareEdgeIndices = null;\n\nfunction compareIndices(a, b) {\n let retVal = compareEdgeIndices[a * 2] - compareEdgeIndices[b * 2];\n if (retVal !== 0) {\n return retVal;\n }\n return compareEdgeIndices[a * 2 + 1] - compareEdgeIndices[b * 2 + 1];\n}\n\nfunction preSortEdgeIndices(edgeIndices) {\n if ((edgeIndices || []).length === 0) {\n return [];\n }\n let seq = new Int32Array(edgeIndices.length / 2);\n for (let i = 0, len = seq.length; i < len; i++) {\n seq[i] = i;\n }\n for (let i = 0, len = edgeIndices.length; i < len; i += 2) {\n if (edgeIndices[i] > edgeIndices[i + 1]) {\n let tmp = edgeIndices[i];\n edgeIndices[i] = edgeIndices[i + 1];\n edgeIndices[i + 1] = tmp;\n }\n }\n compareEdgeIndices = new Int32Array(edgeIndices);\n seq.sort(compareIndices);\n const sortedEdgeIndices = new Int32Array(edgeIndices.length);\n for (let i = 0, len = seq.length; i < len; i++) {\n sortedEdgeIndices[i * 2 + 0] = edgeIndices[seq[i] * 2 + 0];\n sortedEdgeIndices[i * 2 + 1] = edgeIndices[seq[i] * 2 + 1];\n }\n return sortedEdgeIndices;\n}\n\n/**\n * @param {{positionsCompressed: number[], indices: number[], edgeIndices: number[]}} mesh \n * @param {number} bitsPerBucket \n * @param {boolean} checkResult \n * \n * @returns {{positionsCompressed: number[], indices: number[], edgeIndices: number[]}[]}\n */\nfunction rebucketPositions(mesh, bitsPerBucket, checkResult = false) {\n const positionsCompressed = (mesh.positionsCompressed || []);\n const indices = preSortIndices(mesh.indices || [], bitsPerBucket);\n const edgeIndices = preSortEdgeIndices(mesh.edgeIndices || []);\n\n function edgeSearch(el0, el1) { // Code adapted from https://stackoverflow.com/questions/22697936/binary-search-in-javascript\n if (el0 > el1) {\n let tmp = el0;\n el0 = el1;\n el1 = tmp;\n }\n\n function compare_fn(a, b) {\n if (a !== el0) {\n return el0 - a;\n }\n if (b !== el1) {\n return el1 - b;\n }\n return 0;\n }\n\n let m = 0;\n let n = (edgeIndices.length >> 1) - 1;\n while (m <= n) {\n const k = (n + m) >> 1;\n const cmp = compare_fn(edgeIndices[k * 2], edgeIndices[k * 2 + 1]);\n if (cmp > 0) {\n m = k + 1;\n } else if (cmp < 0) {\n n = k - 1;\n } else {\n return k;\n }\n }\n return -m - 1;\n }\n\n const alreadyOutputEdgeIndices = new Int32Array(edgeIndices.length / 2);\n alreadyOutputEdgeIndices.fill(0);\n\n const numPositions = positionsCompressed.length / 3;\n\n if (numPositions > ((1 << bitsPerBucket) * MAX_RE_BUCKET_FAN_OUT)) {\n return [mesh];\n }\n\n const bucketIndicesRemap = new Int32Array(numPositions);\n bucketIndicesRemap.fill(-1);\n\n const buckets = [];\n\n function addEmptyBucket() {\n bucketIndicesRemap.fill(-1);\n\n const newBucket = {\n positionsCompressed: [],\n indices: [],\n edgeIndices: [],\n maxNumPositions: (1 << bitsPerBucket) - bitsPerBucket,\n numPositions: 0,\n bucketNumber: buckets.length,\n };\n\n buckets.push(newBucket);\n\n return newBucket;\n }\n\n let currentBucket = addEmptyBucket();\n\n for (let i = 0, len = indices.length; i < len; i += 3) {\n let additonalPositionsInBucket = 0;\n\n const ii0 = indices[i];\n const ii1 = indices[i + 1];\n const ii2 = indices[i + 2];\n\n if (bucketIndicesRemap[ii0] === -1) {\n additonalPositionsInBucket++;\n }\n\n if (bucketIndicesRemap[ii1] === -1) {\n additonalPositionsInBucket++;\n }\n\n if (bucketIndicesRemap[ii2] === -1) {\n additonalPositionsInBucket++;\n }\n\n if ((additonalPositionsInBucket + currentBucket.numPositions) > currentBucket.maxNumPositions) {\n currentBucket = addEmptyBucket();\n }\n\n if (currentBucket.bucketNumber > MAX_RE_BUCKET_FAN_OUT) {\n return [mesh];\n }\n\n if (bucketIndicesRemap[ii0] === -1) {\n bucketIndicesRemap[ii0] = currentBucket.numPositions++;\n currentBucket.positionsCompressed.push(positionsCompressed[ii0 * 3]);\n currentBucket.positionsCompressed.push(positionsCompressed[ii0 * 3 + 1]);\n currentBucket.positionsCompressed.push(positionsCompressed[ii0 * 3 + 2]);\n }\n\n if (bucketIndicesRemap[ii1] === -1) {\n bucketIndicesRemap[ii1] = currentBucket.numPositions++;\n currentBucket.positionsCompressed.push(positionsCompressed[ii1 * 3]);\n currentBucket.positionsCompressed.push(positionsCompressed[ii1 * 3 + 1]);\n currentBucket.positionsCompressed.push(positionsCompressed[ii1 * 3 + 2]);\n }\n\n if (bucketIndicesRemap[ii2] === -1) {\n bucketIndicesRemap[ii2] = currentBucket.numPositions++;\n currentBucket.positionsCompressed.push(positionsCompressed[ii2 * 3]);\n currentBucket.positionsCompressed.push(positionsCompressed[ii2 * 3 + 1]);\n currentBucket.positionsCompressed.push(positionsCompressed[ii2 * 3 + 2]);\n }\n\n currentBucket.indices.push(bucketIndicesRemap[ii0]);\n currentBucket.indices.push(bucketIndicesRemap[ii1]);\n currentBucket.indices.push(bucketIndicesRemap[ii2]);\n\n // Check possible edge1\n let edgeIndex;\n\n if ((edgeIndex = edgeSearch(ii0, ii1)) >= 0) {\n if (alreadyOutputEdgeIndices[edgeIndex] === 0) {\n alreadyOutputEdgeIndices[edgeIndex] = 1;\n\n currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]);\n currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]);\n }\n }\n\n if ((edgeIndex = edgeSearch(ii0, ii2)) >= 0) {\n if (alreadyOutputEdgeIndices[edgeIndex] === 0) {\n alreadyOutputEdgeIndices[edgeIndex] = 1;\n\n currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]);\n currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]);\n }\n }\n\n if ((edgeIndex = edgeSearch(ii1, ii2)) >= 0) {\n if (alreadyOutputEdgeIndices[edgeIndex] === 0) {\n alreadyOutputEdgeIndices[edgeIndex] = 1;\n\n currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2]]);\n currentBucket.edgeIndices.push(bucketIndicesRemap[edgeIndices[edgeIndex * 2 + 1]]);\n }\n }\n }\n\n const prevBytesPerIndex = bitsPerBucket / 8 * 2;\n const newBytesPerIndex = bitsPerBucket / 8;\n\n const originalSize = positionsCompressed.length * 2 + (indices.length + edgeIndices.length) * prevBytesPerIndex;\n\n let newSize = 0;\n let newPositions = -positionsCompressed.length / 3;\n\n buckets.forEach(bucket => {\n newSize += bucket.positionsCompressed.length * 2 + (bucket.indices.length + bucket.edgeIndices.length) * newBytesPerIndex;\n newPositions += bucket.positionsCompressed.length / 3;\n });\n if (newSize > originalSize) {\n return [mesh];\n }\n if (checkResult) {\n doCheckResult(buckets, mesh);\n }\n return buckets;\n}\n\nfunction doCheckResult(buckets, mesh) {\n const meshDict = {};\n const edgesDict = {};\n\n let edgeIndicesCount = 0;\n\n buckets.forEach(bucket => {\n const indices = bucket.indices;\n const edgeIndices = bucket.edgeIndices;\n const positionsCompressed = bucket.positionsCompressed;\n\n for (let i = 0, len = indices.length; i < len; i += 3) {\n const key = positionsCompressed[indices[i] * 3] + \"_\" + positionsCompressed[indices[i] * 3 + 1] + \"_\" + positionsCompressed[indices[i] * 3 + 2] + \"/\" +\n positionsCompressed[indices[i + 1] * 3] + \"_\" + positionsCompressed[indices[i + 1] * 3 + 1] + \"_\" + positionsCompressed[indices[i + 1] * 3 + 2] + \"/\" +\n positionsCompressed[indices[i + 2] * 3] + \"_\" + positionsCompressed[indices[i + 2] * 3 + 1] + \"_\" + positionsCompressed[indices[i + 2] * 3 + 2];\n meshDict[key] = true;\n }\n\n edgeIndicesCount += bucket.edgeIndices.length / 2;\n\n for (let i = 0, len = edgeIndices.length; i < len; i += 2) {\n const key = positionsCompressed[edgeIndices[i] * 3] + \"_\" + positionsCompressed[edgeIndices[i] * 3 + 1] + \"_\" + positionsCompressed[edgeIndices[i] * 3 + 2] + \"/\" +\n positionsCompressed[edgeIndices[i + 1] * 3] + \"_\" + positionsCompressed[edgeIndices[i + 1] * 3 + 1] + \"_\" + positionsCompressed[edgeIndices[i + 1] * 3 + 2] + \"/\";\n edgesDict[key] = true;\n }\n });\n\n {\n const indices = mesh.indices;\n mesh.edgeIndices;\n const positionsCompressed = mesh.positionsCompressed;\n\n for (let i = 0, len = indices.length; i < len; i += 3) {\n const key = positionsCompressed[indices[i] * 3] + \"_\" + positionsCompressed[indices[i] * 3 + 1] + \"_\" + positionsCompressed[indices[i] * 3 + 2] + \"/\" +\n positionsCompressed[indices[i + 1] * 3] + \"_\" + positionsCompressed[indices[i + 1] * 3 + 1] + \"_\" + positionsCompressed[indices[i + 1] * 3 + 2] + \"/\" +\n positionsCompressed[indices[i + 2] * 3] + \"_\" + positionsCompressed[indices[i + 2] * 3 + 1] + \"_\" + positionsCompressed[indices[i + 2] * 3 + 2];\n\n if (!(key in meshDict)) {\n console.log(\"Not found \" + key);\n throw \"Ohhhh!\";\n }\n }\n\n // for (var i = 0, len = edgeIndices.length; i < len; i+=2)\n // {\n // var key = positionsCompressed[edgeIndices[i]*3] + \"_\" + positionsCompressed[edgeIndices[i]*3+1] + \"_\" + positionsCompressed[edgeIndices[i]*3+2] + \"/\" +\n // positionsCompressed[edgeIndices[i+1]*3] + \"_\" + positionsCompressed[edgeIndices[i+1]*3+1] + \"_\" + positionsCompressed[edgeIndices[i+1]*3+2] + \"/\";\n\n // if (!(key in edgesDict)) {\n // var key2 = edgeIndices[i] + \"_\" + edgeIndices[i+1];\n\n // console.log (\" - Not found \" + key);\n // console.log (\" - Not found \" + key2);\n // // throw \"Ohhhh2!\";\n // }\n // }\n }\n}\n\nconst angleAxis = math.vec4(4);\nconst q1 = math.vec4();\nconst q2 = math.vec4();\nconst xAxis = math.vec3([1, 0, 0]);\nconst yAxis = math.vec3([0, 1, 0]);\nconst zAxis = math.vec3([0, 0, 1]);\n\nmath.vec3(3);\nmath.vec3(3);\n\nconst identityMat = math.identityMat4();\n\n/**\n * A dynamically-updatable transform within a {@link SceneModel}.\n *\n * * Can be composed into hierarchies\n * * Shared by multiple {@link SceneModelMesh}es\n * * Created with {@link SceneModel#createTransform}\n * * Stored by ID in {@link SceneModel#transforms}\n * * Referenced by {@link SceneModelMesh#transform}\n */\nclass SceneModelTransform {\n\n /**\n * @private\n */\n constructor(cfg) {\n this._model = cfg.model;\n\n /**\n * Unique ID of this SceneModelTransform.\n *\n * The SceneModelTransform is registered against this ID in {@link SceneModel#transforms}.\n */\n this.id = cfg.id;\n\n this._parentTransform = cfg.parent;\n this._childTransforms = [];\n this._meshes = [];\n this._scale = new Float32Array([1,1,1]);\n this._quaternion = math.identityQuaternion(new Float32Array(4));\n this._rotation = new Float32Array(3);\n this._position = new Float32Array(3);\n this._localMatrix = math.identityMat4(new Float32Array(16));\n this._worldMatrix = math.identityMat4(new Float32Array(16));\n this._localMatrixDirty = true;\n this._worldMatrixDirty = true;\n\n if (cfg.matrix) {\n this.matrix = cfg.matrix;\n } else {\n this.scale = cfg.scale;\n this.position = cfg.position;\n if (cfg.quaternion) ; else {\n this.rotation = cfg.rotation;\n }\n }\n if (cfg.parent) {\n cfg.parent._addChildTransform(this);\n }\n }\n\n _addChildTransform(childTransform) {\n this._childTransforms.push(childTransform);\n childTransform._parentTransform = this;\n childTransform._transformDirty();\n childTransform._setSubtreeAABBsDirty(this);\n }\n\n _addMesh(mesh) {\n this._meshes.push(mesh);\n mesh.transform = this;\n // childTransform._setWorldMatrixDirty();\n // childTransform._setAABBDirty();\n }\n\n /**\n * The optional parent SceneModelTransform.\n *\n * @type {SceneModelTransform}\n */\n get parentTransform() {\n return this._parentTransform;\n }\n\n /**\n * The {@link SceneModelMesh}es transformed by this SceneModelTransform.\n *\n * @returns {[]}\n */\n get meshes() {\n return this._meshes;\n }\n\n /**\n * Sets the SceneModelTransform's local translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set position(value) {\n this._position.set(value || [0, 0, 0]);\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n }\n\n /**\n * Gets the SceneModelTransform's translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get position() {\n return this._position;\n }\n\n /**\n * Sets the SceneModelTransform's rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set rotation(value) {\n this._rotation.set(value || [0, 0, 0]);\n math.eulerToQuaternion(this._rotation, \"XYZ\", this._quaternion);\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n }\n\n /**\n * Gets the SceneModelTransform's rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get rotation() {\n return this._rotation;\n }\n\n /**\n * Sets the SceneModelTransform's rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n set quaternion(value) {\n this._quaternion.set(value || [0, 0, 0, 1]);\n math.quaternionToEuler(this._quaternion, \"XYZ\", this._rotation);\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n }\n\n /**\n * Gets the SceneModelTransform's rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n get quaternion() {\n return this._quaternion;\n }\n\n /**\n * Sets the SceneModelTransform's scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n */\n set scale(value) {\n this._scale.set(value || [1, 1, 1]);\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n }\n\n /**\n * Gets the SceneModelTransform's scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the SceneModelTransform's transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n set matrix(value) {\n if (!this._localMatrix) {\n this._localMatrix = math.identityMat4();\n }\n this._localMatrix.set(value || identityMat);\n math.decomposeMat4(this._localMatrix, this._position, this._quaternion, this._scale);\n this._localMatrixDirty = false;\n this._transformDirty();\n this._model.glRedraw();\n }\n\n /**\n * Gets the SceneModelTransform's transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n get matrix() {\n if (this._localMatrixDirty) {\n if (!this._localMatrix) {\n this._localMatrix = math.identityMat4();\n }\n math.composeMat4(this._position, this._quaternion, this._scale, this._localMatrix);\n this._localMatrixDirty = false;\n }\n return this._localMatrix;\n }\n\n /**\n * Gets the SceneModelTransform's World matrix.\n *\n * @property worldMatrix\n * @type {Number[]}\n */\n get worldMatrix() {\n if (this._worldMatrixDirty) {\n this._buildWorldMatrix();\n }\n return this._worldMatrix;\n }\n\n /**\n * Rotates the SceneModelTransform about the given axis by the given increment.\n *\n * @param {Number[]} axis Local axis about which to rotate.\n * @param {Number} angle Angle increment in degrees.\n */\n rotate(axis, angle) {\n angleAxis[0] = axis[0];\n angleAxis[1] = axis[1];\n angleAxis[2] = axis[2];\n angleAxis[3] = angle * math.DEGTORAD;\n math.angleAxisToQuaternion(angleAxis, q1);\n math.mulQuaternions(this.quaternion, q1, q2);\n this.quaternion = q2;\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n return this;\n }\n\n /**\n * Rotates the SceneModelTransform about the given World-space axis by the given increment.\n *\n * @param {Number[]} axis Local axis about which to rotate.\n * @param {Number} angle Angle increment in degrees.\n */\n rotateOnWorldAxis(axis, angle) {\n angleAxis[0] = axis[0];\n angleAxis[1] = axis[1];\n angleAxis[2] = axis[2];\n angleAxis[3] = angle * math.DEGTORAD;\n math.angleAxisToQuaternion(angleAxis, q1);\n math.mulQuaternions(q1, this.quaternion, q1);\n //this.quaternion.premultiply(q1);\n return this;\n }\n\n /**\n * Rotates the SceneModelTransform about the local X-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateX(angle) {\n return this.rotate(xAxis, angle);\n }\n\n /**\n * Rotates the SceneModelTransform about the local Y-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateY(angle) {\n return this.rotate(yAxis, angle);\n }\n\n /**\n * Rotates the SceneModelTransform about the local Z-axis by the given increment.\n *\n * @param {Number} angle Angle increment in degrees.\n */\n rotateZ(angle) {\n return this.rotate(zAxis, angle);\n }\n\n /**\n * Translates the SceneModelTransform along the local axis by the given increment.\n *\n * @param {Number[]} axis Normalized local space 3D vector along which to translate.\n * @param {Number} distance Distance to translate along the vector.\n */\n translate(axis) {\n this._position[0] += axis[0];\n this._position[1] += axis[1];\n this._position[2] += axis[2];\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n return this;\n }\n\n /**\n * Translates the SceneModelTransform along the local X-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the X-axis.\n */\n translateX(distance) {\n this._position[0] += distance;\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n return this;\n }\n\n /**\n * Translates the SceneModelTransform along the local Y-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the Y-axis.\n */\n translateY(distance) {\n this._position[1] += distance;\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n return this;\n }\n\n /**\n * Translates the SceneModelTransform along the local Z-axis by the given increment.\n *\n * @param {Number} distance Distance to translate along the Z-axis.\n */\n translateZ(distance) {\n this._position[2] += distance;\n this._setLocalMatrixDirty();\n this._model.glRedraw();\n return this;\n }\n\n _setLocalMatrixDirty() {\n this._localMatrixDirty = true;\n this._transformDirty();\n }\n\n _transformDirty() {\n this._worldMatrixDirty = true;\n for (let i = 0, len = this._childTransforms.length; i < len; i++) {\n const childTransform = this._childTransforms[i];\n childTransform._transformDirty();\n if (childTransform._meshes && childTransform._meshes.length > 0) {\n const meshes = childTransform._meshes;\n for (let j =0, lenj = meshes.length; j < lenj; j++) {\n meshes[j]._transformDirty();\n }\n }\n }\n if (this._meshes && this._meshes.length > 0) {\n const meshes = this._meshes;\n for (let j =0, lenj = meshes.length; j < lenj; j++) {\n meshes[j]._transformDirty();\n }\n }\n }\n\n _buildWorldMatrix() {\n const localMatrix = this.matrix;\n if (!this._parentTransform) {\n for (let i = 0, len = localMatrix.length; i < len; i++) {\n this._worldMatrix[i] = localMatrix[i];\n }\n } else {\n math.mulMat4(this._parentTransform.worldMatrix, localMatrix, this._worldMatrix);\n }\n this._worldMatrixDirty = false;\n }\n\n _setSubtreeAABBsDirty(sceneTransform) {\n sceneTransform._aabbDirty = true;\n if (sceneTransform._childTransforms) {\n for (let i = 0, len = sceneTransform._childTransforms.length; i < len; i++) {\n this._setSubtreeAABBsDirty(sceneTransform._childTransforms[i]);\n }\n }\n }\n}\n\nconst tempVec3a$a = math.vec3();\n\nconst tempOBB3 = math.OBB3();\nconst tempQuaternion = math.vec4();\n\nconst DEFAULT_SCALE = math.vec3([1, 1, 1]);\nconst DEFAULT_POSITION = math.vec3([0, 0, 0]);\nmath.vec3([0, 0, 0]);\nconst DEFAULT_QUATERNION = math.identityQuaternion();\nconst DEFAULT_MATRIX = math.identityMat4();\n\nconst DEFAULT_COLOR_TEXTURE_ID = \"defaultColorTexture\";\nconst DEFAULT_METAL_ROUGH_TEXTURE_ID = \"defaultMetalRoughTexture\";\nconst DEFAULT_NORMALS_TEXTURE_ID = \"defaultNormalsTexture\";\nconst DEFAULT_EMISSIVE_TEXTURE_ID = \"defaultEmissiveTexture\";\nconst DEFAULT_OCCLUSION_TEXTURE_ID = \"defaultOcclusionTexture\";\nconst DEFAULT_TEXTURE_SET_ID = \"defaultTextureSet\";\n\nconst defaultCompressedColor = new Uint8Array([255, 255, 255]);\n\nconst VBO_INSTANCED = 0;\nconst VBO_BATCHED = 1;\nconst DTX = 2;\n\n/**\n * @desc A high-performance model representation for efficient rendering and low memory usage.\n *\n * # Examples\n *\n * Internally, SceneModel uses a combination of several different techniques to render and represent\n * the different parts of a typical model. Each of the live examples at these links is designed to \"unit test\" one of these\n * techniques, in isolation. If some bug occurs in SceneModel, we use these tests to debug, but they also\n * serve to demonstrate how to use the capabilities of SceneModel programmatically.\n *\n * * [Loading building models into SceneModels](/examples/buildings)\n * * [Loading city models into SceneModels](/examples/cities)\n * * [Loading LiDAR scans into SceneModels](/examples/lidar)\n * * [Loading CAD models into SceneModels](/examples/cad)\n * * [SceneModel feature tests](/examples/scenemodel)\n *\n * # Overview\n *\n * While xeokit's standard [scene graph](https://github.com/xeokit/xeokit-sdk/wiki/Scene-Graphs) is great for gizmos and medium-sized models, it doesn't scale up to millions of objects in terms of memory and rendering efficiency.\n *\n * For huge models, we have the ````SceneModel```` representation, which is optimized to pack large amounts of geometry into memory and render it efficiently using WebGL.\n *\n * ````SceneModel```` is the default model representation loaded by (at least) {@link GLTFLoaderPlugin}, {@link XKTLoaderPlugin} and {@link WebIFCLoaderPlugin}.\n *\n * In this tutorial you'll learn how to use ````SceneModel```` to create high-detail content programmatically. Ordinarily you'd be learning about ````SceneModel```` if you were writing your own model loader plugins.\n *\n * # Contents\n *\n * - [SceneModel](#DataTextureSceneModel)\n * - [GPU-Resident Geometry](#gpu-resident-geometry)\n * - [Picking](#picking)\n * - [Example 1: Geometry Instancing](#example-1--geometry-instancing)\n * - [Finalizing a SceneModel](#finalizing-a-DataTextureSceneModel)\n * - [Finding Entities](#finding-entities)\n * - [Example 2: Geometry Batching](#example-2--geometry-batching)\n * - [Classifying with Metadata](#classifying-with-metadata)\n * - [Querying Metadata](#querying-metadata)\n * - [Metadata Structure](#metadata-structure)\n * - [RTC Coordinates](#rtc-coordinates-for-double-precision)\n * - [Example 3: RTC Coordinates with Geometry Instancing](#example-2--rtc-coordinates-with-geometry-instancing)\n * - [Example 4: RTC Coordinates with Geometry Batching](#example-2--rtc-coordinates-with-geometry-batching)\n *\n * ## SceneModel\n *\n * ````SceneModel```` uses two rendering techniques internally:\n *\n * 1. ***Geometry batching*** for unique geometries, combining those into a single WebGL geometry buffer, to render in one draw call, and\n * 2. ***geometry instancing*** for geometries that are shared by multiple meshes, rendering all instances of each shared geometry in one draw call.\n *\n *
\n * These techniques come with certain limitations:\n *\n * * Non-realistic rendering - while scene graphs can use xeokit's full set of material workflows, ````SceneModel```` uses simple Lambertian shading without textures.\n * * Static transforms - transforms within a ````SceneModel```` are static and cannot be dynamically translated, rotated and scaled the way {@link Node}s and {@link Mesh}es in scene graphs can.\n * * Immutable model representation - while scene graph {@link Node}s and\n * {@link Mesh}es can be dynamically plugged together, ````SceneModel```` is immutable,\n * since it packs its geometries into buffers and instanced arrays.\n *\n * ````SceneModel````'s API allows us to exploit batching and instancing, while exposing its elements as\n * abstract {@link Entity} types.\n *\n * {@link Entity} is the abstract base class for\n * the various xeokit components that represent models, objects, or anonymous visible elements. An Entity has a unique ID and can be\n * individually shown, hidden, selected, highlighted, ghosted, culled, picked and clipped, and has its own World-space boundary.\n *\n * * A ````SceneModel```` is an {@link Entity} that represents a model.\n * * A ````SceneModel```` represents each of its objects with an {@link Entity}.\n * * Each {@link Entity} has one or more meshes that define its shape.\n * * Each mesh has either its own unique geometry, or shares a geometry with other meshes.\n *\n * ## GPU-Resident Geometry\n *\n * For a low memory footprint, ````SceneModel```` stores its geometries in GPU memory only, compressed (quantized) as integers. Unfortunately, GPU-resident geometry is\n * not readable by JavaScript.\n *\n *\n * ## Example 1: Geometry Instancing\n *\n * In the example below, we'll use a ````SceneModel````\n * to build a simple table model using geometry instancing.\n *\n * We'll start by adding a reusable box-shaped geometry to our ````SceneModel````.\n *\n * Then, for each object in our model we'll add an {@link Entity}\n * that has a mesh that instances our box geometry, transforming and coloring the instance.\n *\n * [![](http://xeokit.io/img/docs/sceneGraph.png)](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_instancing)\n *\n * ````javascript\n * import {Viewer, SceneModel} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * // Build a SceneModel representing a table\n * // with four legs, using geometry instancing\n *\n * const sceneModel = new SceneModel(viewer.scene, {\n * id: \"table\",\n * isModel: true, // <--- Registers SceneModel in viewer.scene.models\n * position: [0, 0, 0],\n * scale: [1, 1, 1],\n * rotation: [0, 0, 0]\n * });\n *\n * // Create a reusable geometry within the SceneModel\n * // We'll instance this geometry by five meshes\n *\n * sceneModel.createGeometry({\n *\n * id: \"myBoxGeometry\",\n *\n * // The primitive type - allowed values are \"points\", \"lines\" and \"triangles\".\n * // See the OpenGL/WebGL specification docs\n * // for how the coordinate arrays are supposed to be laid out.\n * primitive: \"triangles\",\n *\n * // The vertices - eight for our cube, each\n * // one spanning three array elements for X,Y and Z\n * positions: [\n * 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // v0-v1-v2-v3 front\n * 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, // v0-v3-v4-v1 right\n * 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, // v0-v1-v6-v1 top\n * -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // v1-v6-v7-v2 left\n * -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, // v7-v4-v3-v2 bottom\n * 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1 // v4-v7-v6-v1 back\n * ],\n *\n * // Normal vectors, one for each vertex\n * normals: [\n * 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2-v3 front\n * 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right\n * 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6-v1 top\n * -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7-v2 left\n * 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, // v7-v4-v3-v2 bottom\n * 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1 // v4-v7-v6-v5 back\n * ],\n *\n * // Indices - these organise the positions and and normals\n * // into geometric primitives in accordance with the \"primitive\" parameter,\n * // in this case a set of three indices for each triangle.\n * //\n * // Note that each triangle is specified in counter-clockwise winding order.\n * //\n * indices: [\n * 0, 1, 2, 0, 2, 3, // front\n * 4, 5, 6, 4, 6, 7, // right\n * 8, 9, 10, 8, 10, 11, // top\n * 12, 13, 14, 12, 14, 15, // left\n * 16, 17, 18, 16, 18, 19, // bottom\n * 20, 21, 22, 20, 22, 23\n * ]\n * });\n *\n * // Red table leg\n *\n * sceneModel.createMesh({\n * id: \"redLegMesh\",\n * geometryId: \"myBoxGeometry\",\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1, 0.3, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * id: \"redLeg\",\n * meshIds: [\"redLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Green table leg\n *\n * sceneModel.createMesh({\n * id: \"greenLegMesh\",\n * geometryId: \"myBoxGeometry\",\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 1.0, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * id: \"greenLeg\",\n * meshIds: [\"greenLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Blue table leg\n *\n * sceneModel.createMesh({\n * id: \"blueLegMesh\",\n * geometryId: \"myBoxGeometry\",\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * id: \"blueLeg\",\n * meshIds: [\"blueLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Yellow table leg\n *\n * sceneModel.createMesh({\n * id: \"yellowLegMesh\",\n * geometryId: \"myBoxGeometry\",\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1.0, 1.0, 0.0]\n * });\n *\n * sceneModel.createEntity({\n * id: \"yellowLeg\",\n * meshIds: [\"yellowLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Purple table top\n *\n * sceneModel.createMesh({\n * id: \"purpleTableTopMesh\",\n * geometryId: \"myBoxGeometry\",\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * color: [1.0, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * id: \"purpleTableTop\",\n * meshIds: [\"purpleTableTopMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n * ````\n *\n * ## Finalizing a SceneModel\n *\n * Before we can view and interact with our ````SceneModel````, we need to **finalize** it. Internally, this causes the ````SceneModel```` to build the\n * vertex buffer objects (VBOs) that support our geometry instances. When using geometry batching (see next example),\n * this causes ````SceneModel```` to build the VBOs that combine the batched geometries. Note that you can do both instancing and\n * batching within the same ````SceneModel````.\n *\n * Once finalized, we can't add anything more to our ````SceneModel````.\n *\n * ```` javascript\n * SceneModel.finalize();\n * ````\n *\n * ## Finding Entities\n *\n * As mentioned earlier, {@link Entity} is\n * the abstract base class for components that represent models, objects, or just\n * anonymous visible elements.\n *\n * Since we created configured our ````SceneModel```` with ````isModel: true````,\n * we're able to find it as an Entity by ID in ````viewer.scene.models````. Likewise, since\n * we configured each of its Entities with ````isObject: true````, we're able to\n * find them in ````viewer.scene.objects````.\n *\n *\n * ````javascript\n * // Get the whole table model Entity\n * const table = viewer.scene.models[\"table\"];\n *\n * // Get some leg object Entities\n * const redLeg = viewer.scene.objects[\"redLeg\"];\n * const greenLeg = viewer.scene.objects[\"greenLeg\"];\n * const blueLeg = viewer.scene.objects[\"blueLeg\"];\n * ````\n *\n * ## Example 2: Geometry Batching\n *\n * Let's once more use a ````SceneModel````\n * to build the simple table model, this time exploiting geometry batching.\n *\n * [![](http://xeokit.io/img/docs/sceneGraph.png)](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_batching)\n *\n * ````javascript\n * import {Viewer, SceneModel} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * // Create a SceneModel representing a table with four legs, using geometry batching\n * const sceneModel = new SceneModel(viewer.scene, {\n * id: \"table\",\n * isModel: true, // <--- Registers SceneModel in viewer.scene.models\n * position: [0, 0, 0],\n * scale: [1, 1, 1],\n * rotation: [0, 0, 0]\n * });\n *\n * // Red table leg\n *\n * sceneModel.createMesh({\n * id: \"redLegMesh\",\n *\n * // Geometry arrays are same as for the earlier batching example\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1, 0.3, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * id: \"redLeg\",\n * meshIds: [\"redLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Green table leg\n *\n * sceneModel.createMesh({\n * id: \"greenLegMesh\",\n * primitive: \"triangles\",\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 1.0, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * id: \"greenLeg\",\n * meshIds: [\"greenLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Blue table leg\n *\n * sceneModel.createMesh({\n * id: \"blueLegMesh\",\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * id: \"blueLeg\",\n * meshIds: [\"blueLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Yellow table leg object\n *\n * sceneModel.createMesh({\n * id: \"yellowLegMesh\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1.0, 1.0, 0.0]\n * });\n *\n * sceneModel.createEntity({\n * id: \"yellowLeg\",\n * meshIds: [\"yellowLegMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Purple table top\n *\n * sceneModel.createMesh({\n * id: \"purpleTableTopMesh\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * color: [1.0, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * id: \"purpleTableTop\",\n * meshIds: [\"purpleTableTopMesh\"],\n * isObject: true // <---- Registers Entity by ID on viewer.scene.objects\n * });\n *\n * // Finalize the SceneModel.\n *\n * SceneModel.finalize();\n *\n * // Find BigModelNodes by their model and object IDs\n *\n * // Get the whole table model\n * const table = viewer.scene.models[\"table\"];\n *\n * // Get some leg objects\n * const redLeg = viewer.scene.objects[\"redLeg\"];\n * const greenLeg = viewer.scene.objects[\"greenLeg\"];\n * const blueLeg = viewer.scene.objects[\"blueLeg\"];\n * ````\n *\n * ## Classifying with Metadata\n *\n * In the previous examples, we used ````SceneModel```` to build\n * two versions of the same table model, to demonstrate geometry batching and geometry instancing.\n *\n * We'll now classify our {@link Entity}s with metadata. This metadata\n * will work the same for both our examples, since they create the exact same structure of {@link Entity}s\n * to represent their models and objects. The abstract Entity type is, after all, intended to provide an abstract interface through which differently-implemented scene content can be accessed uniformly.\n *\n * To create the metadata, we'll create a {@link MetaModel} for our model,\n * with a {@link MetaObject} for each of it's objects. The MetaModel and MetaObjects\n * get the same IDs as the {@link Entity}s that represent their model and objects within our scene.\n *\n * ```` javascript\n * const furnitureMetaModel = viewer.metaScene.createMetaModel(\"furniture\", { // Creates a MetaModel in the MetaScene\n *\n * \"projectId\": \"myTableProject\",\n * \"revisionId\": \"V1.0\",\n *\n * \"metaObjects\": [\n * { // Creates a MetaObject in the MetaModel\n * \"id\": \"table\",\n * \"name\": \"Table\", // Same ID as an object Entity\n * \"type\": \"furniture\", // Arbitrary type, could be IFC type\n * \"properties\": { // Arbitrary properties, could be IfcPropertySet\n * \"cost\": \"200\"\n * }\n * },\n * {\n * \"id\": \"redLeg\",\n * \"name\": \"Red table Leg\",\n * \"type\": \"leg\",\n * \"parent\": \"table\", // References first MetaObject as parent\n * \"properties\": {\n * \"material\": \"wood\"\n * }\n * },\n * {\n * \"id\": \"greenLeg\", // Node with corresponding id does not need to exist\n * \"name\": \"Green table leg\", // and MetaObject does not need to exist for Node with an id\n * \"type\": \"leg\",\n * \"parent\": \"table\",\n * \"properties\": {\n * \"material\": \"wood\"\n * }\n * },\n * {\n * \"id\": \"blueLeg\",\n * \"name\": \"Blue table leg\",\n * \"type\": \"leg\",\n * \"parent\": \"table\",\n * \"properties\": {\n * \"material\": \"wood\"\n * }\n * },\n * {\n * \"id\": \"yellowLeg\",\n * \"name\": \"Yellow table leg\",\n * \"type\": \"leg\",\n * \"parent\": \"table\",\n * \"properties\": {\n * \"material\": \"wood\"\n * }\n * },\n * {\n * \"id\": \"tableTop\",\n * \"name\": \"Purple table top\",\n * \"type\": \"surface\",\n * \"parent\": \"table\",\n * \"properties\": {\n * \"material\": \"formica\",\n * \"width\": \"60\",\n * \"depth\": \"60\",\n * \"thickness\": \"5\"\n * }\n * }\n * ]\n * });\n * ````\n *\n * ## Querying Metadata\n *\n * Having created and classified our model (either the instancing or batching example), we can now find the {@link MetaModel}\n * and {@link MetaObject}s using the IDs of their\n * corresponding {@link Entity}s.\n *\n * ````JavaScript\n * const furnitureMetaModel = scene.metaScene.metaModels[\"furniture\"];\n *\n * const redLegMetaObject = scene.metaScene.metaObjects[\"redLeg\"];\n * ````\n *\n * In the snippet below, we'll log metadata on each {@link Entity} we click on:\n *\n * ````JavaScript\n * viewer.scene.input.on(\"mouseclicked\", function (coords) {\n *\n * const hit = viewer.scene.pick({\n * canvasPos: coords\n * });\n *\n * if (hit) {\n * const entity = hit.entity;\n * const metaObject = viewer.metaScene.metaObjects[entity.id];\n * if (metaObject) {\n * console.log(JSON.stringify(metaObject.getJSON(), null, \"\\t\"));\n * }\n * }\n * });\n * ````\n *\n * ## Metadata Structure\n *\n * The {@link MetaModel}\n * organizes its {@link MetaObject}s in\n * a tree that describes their structural composition:\n *\n * ````JavaScript\n * // Get metadata on the root object\n * const tableMetaObject = furnitureMetaModel.rootMetaObject;\n *\n * // Get metadata on the leg objects\n * const redLegMetaObject = tableMetaObject.children[0];\n * const greenLegMetaObject = tableMetaObject.children[1];\n * const blueLegMetaObject = tableMetaObject.children[2];\n * const yellowLegMetaObject = tableMetaObject.children[3];\n * ````\n *\n * Given an {@link Entity}, we can find the object or model of which it is a part, or the objects that comprise it. We can also generate UI\n * components from the metadata, such as the tree view demonstrated in [this demo](https://xeokit.github.io/xeokit-sdk/examples/index.html#BIMOffline_glTF_OTCConferenceCenter).\n *\n * This hierarchy allows us to express the hierarchical structure of a model while representing it in\n * various ways in the 3D scene (such as with ````SceneModel````, which\n * has a non-hierarchical scene representation).\n *\n * Note also that a {@link MetaObject} does not need to have a corresponding\n * {@link Entity} and vice-versa.\n *\n * # RTC Coordinates for Double Precision\n *\n * ````SceneModel```` can emulate 64-bit precision on GPUs using relative-to-center (RTC) coordinates.\n *\n * Consider a model that contains many small objects, but with such large spatial extents that 32 bits of GPU precision (accurate to ~7 digits) will not be sufficient to render all of the the objects without jittering.\n *\n * To prevent jittering, we could spatially subdivide the objects into \"tiles\". Each tile would have a center position, and the positions of the objects within the tile would be relative to that center (\"RTC coordinates\").\n *\n * While the center positions of the tiles would be 64-bit values, the object positions only need to be 32-bit.\n *\n * Internally, when rendering an object with RTC coordinates, xeokit first temporarily translates the camera viewing matrix by the object's tile's RTC center, on the CPU, using 64-bit math.\n *\n * Then xeokit loads the viewing matrix into its WebGL shaders, where math happens at 32-bit precision. Within the shaders, the matrix is effectively down-cast to 32-bit precision, and the object's 32-bit vertex positions are transformed by the matrix.\n *\n * We see no jittering, because with RTC a detectable loss of GPU accuracy only starts happening to objects as they become very distant from the camera viewpoint, at which point they are too small to be discernible anyway.\n *\n * ## RTC Coordinates with Geometry Instancing\n *\n * To use RTC with ````SceneModel```` geometry instancing, we specify an RTC center for the geometry via its ````origin```` parameter. Then ````SceneModel```` assumes that all meshes that instance that geometry are within the same RTC coordinate system, ie. the meshes ````position```` and ````rotation```` properties are assumed to be relative to the geometry's ````origin````.\n *\n * For simplicity, our example's meshes all instance the same geometry. Therefore, our example model has only one RTC center.\n *\n * Note that the axis-aligned World-space boundary (AABB) of our model is ````[ -6, -9, -6, 1000000006, -2.5, 1000000006]````.\n *\n * [![](http://xeokit.io/img/docs/sceneGraph.png)](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_batching)\n *\n * ````javascript\n * const origin = [100000000, 0, 100000000];\n *\n * sceneModel.createGeometry({\n * id: \"box\",\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg1\",\n * geometryId: \"box\",\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1, 0.3, 0.3],\n * origin: origin\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg1\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg2\",\n * geometryId: \"box\",\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 1.0, 0.3],\n * origin: origin\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg2\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg3\",\n * geometryId: \"box\",\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 0.3, 1.0],\n * origin: origin\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg3\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg4\",\n * geometryId: \"box\",\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1.0, 1.0, 0.0],\n * origin: origin\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg4\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"top\",\n * geometryId: \"box\",\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * color: [1.0, 0.3, 1.0],\n * origin: origin\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"top\"],\n * isObject: true\n * });\n * ````\n *\n * ## RTC Coordinates with Geometry Batching\n *\n * To use RTC with ````SceneModel```` geometry batching, we specify an RTC center (````origin````) for each mesh. For performance, we try to have as many meshes share the same value for ````origin```` as possible. Each mesh's ````positions````, ````position```` and ````rotation```` properties are assumed to be relative to ````origin````.\n *\n * For simplicity, the meshes in our example all share the same RTC center.\n *\n * The axis-aligned World-space boundary (AABB) of our model is ````[ -6, -9, -6, 1000000006, -2.5, 1000000006]````.\n *\n * [![](http://xeokit.io/img/docs/sceneGraph.png)](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_batching)\n *\n * ````javascript\n * const origin = [100000000, 0, 100000000];\n *\n * sceneModel.createMesh({\n * id: \"leg1\",\n * origin: origin, // This mesh's positions and transforms are relative to the RTC center\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1, 0.3, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg1\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg2\",\n * origin: origin, // This mesh's positions and transforms are relative to the RTC center\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 1.0, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg2\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg3\",\n * origin: origin, // This mesh's positions and transforms are relative to the RTC center\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg3\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg4\",\n * origin: origin, // This mesh's positions and transforms are relative to the RTC center\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1.0, 1.0, 0.0]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg4\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"top\",\n * origin: origin, // This mesh's positions and transforms are relative to the RTC center\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * color: [1.0, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"top\"],\n * isObject: true\n * });\n * ````\n *\n * ## Positioning at World-space coordinates\n *\n * To position a SceneModel at given double-precision World coordinates, we can\n * configure the ````origin```` of the SceneModel itself. The ````origin```` is a double-precision\n * 3D World-space position at which the SceneModel will be located.\n *\n * Note that ````position```` is a single-precision offset relative to ````origin````.\n *\n * ````javascript\n * const origin = [100000000, 0, 100000000];\n *\n * const sceneModel = new SceneModel(viewer.scene, {\n * id: \"table\",\n * isModel: true,\n * origin: origin, // Everything in this SceneModel is relative to this RTC center\n * position: [0, 0, 0],\n * scale: [1, 1, 1],\n * rotation: [0, 0, 0]\n * });\n *\n * sceneModel.createGeometry({\n * id: \"box\",\n * primitive: \"triangles\",\n * positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],\n * normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],\n * indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg1\",\n * geometryId: \"box\",\n * position: [-4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1, 0.3, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg1\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg2\",\n * geometryId: \"box\",\n * position: [4, -6, -4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 1.0, 0.3]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg2\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg3\",\n * geometryId: \"box\",\n * position: [4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [0.3, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg3\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"leg4\",\n * geometryId: \"box\",\n * position: [-4, -6, 4],\n * scale: [1, 3, 1],\n * rotation: [0, 0, 0],\n * color: [1.0, 1.0, 0.0]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"leg4\"],\n * isObject: true\n * });\n *\n * sceneModel.createMesh({\n * id: \"top\",\n * geometryId: \"box\",\n * position: [0, -3, 0],\n * scale: [6, 0.5, 6],\n * rotation: [0, 0, 0],\n * color: [1.0, 0.3, 1.0]\n * });\n *\n * sceneModel.createEntity({\n * meshIds: [\"top\"],\n * isObject: true\n * });\n * ````\n *\n * # Textures\n *\n * ## Loading KTX2 Texture Files into a SceneModel\n *\n * A {@link SceneModel} that is configured with a {@link KTX2TextureTranscoder} will\n * allow us to load textures into it from KTX2 buffers or files.\n *\n * In the example below, we'll create a {@link Viewer}, containing a {@link SceneModel} configured with a\n * {@link KTX2TextureTranscoder}. We'll then programmatically create a simple object within the SceneModel, consisting of\n * a single mesh with a texture loaded from a KTX2 file, which our SceneModel internally transcodes, using\n * its {@link KTX2TextureTranscoder}. Note how we configure our {@link KTX2TextureTranscoder} with a path to the Basis Universal\n * transcoder WASM module.\n *\n * ````javascript\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * const textureTranscoder = new KTX2TextureTranscoder({\n * viewer,\n * transcoderPath: \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\" // <------ Path to BasisU transcoder module\n * });\n *\n * const sceneModel = new SceneModel(viewer.scene, {\n * id: \"myModel\",\n * textureTranscoder // <<-------------------- Configure model with our transcoder\n * });\n *\n * sceneModel.createTexture({\n * id: \"myColorTexture\",\n * src: \"../assets/textures/compressed/sample_uastc_zstd.ktx2\" // <<----- KTX2 texture asset\n * });\n *\n * sceneModel.createTexture({\n * id: \"myMetallicRoughnessTexture\",\n * src: \"../assets/textures/alpha/crosshatchAlphaMap.jpg\" // <<----- JPEG texture asset\n * });\n *\n * sceneModel.createTextureSet({\n * id: \"myTextureSet\",\n * colorTextureId: \"myColorTexture\",\n * metallicRoughnessTextureId: \"myMetallicRoughnessTexture\"\n * });\n *\n * sceneModel.createMesh({\n * id: \"myMesh\",\n * textureSetId: \"myTextureSet\",\n * primitive: \"triangles\",\n * positions: [1, 1, 1, ...],\n * normals: [0, 0, 1, 0, ...],\n * uv: [1, 0, 0, ...],\n * indices: [0, 1, 2, ...],\n * });\n *\n * sceneModel.createEntity({\n * id: \"myEntity\",\n * meshIds: [\"myMesh\"]\n * });\n *\n * sceneModel.finalize();\n * ````\n *\n * ## Loading KTX2 Textures from ArrayBuffers into a SceneModel\n *\n * A SceneModel that is configured with a {@link KTX2TextureTranscoder} will allow us to load textures into\n * it from KTX2 ArrayBuffers.\n *\n * In the example below, we'll create a {@link Viewer}, containing a {@link SceneModel} configured with a\n * {@link KTX2TextureTranscoder}. We'll then programmatically create a simple object within the SceneModel, consisting of\n * a single mesh with a texture loaded from a KTX2 ArrayBuffer, which our SceneModel internally transcodes, using\n * its {@link KTX2TextureTranscoder}.\n *\n * ````javascript\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-21.80, 4.01, 6.56];\n * viewer.scene.camera.look = [0, -5.75, 0];\n * viewer.scene.camera.up = [0.37, 0.91, -0.11];\n *\n * const textureTranscoder = new KTX2TextureTranscoder({\n * viewer,\n * transcoderPath: \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/\" // <------ Path to BasisU transcoder module\n * });\n *\n * const sceneModel = new SceneModel(viewer.scene, {\n * id: \"myModel\",\n * textureTranscoder // <<-------------------- Configure model with our transcoder\n * });\n *\n * utils.loadArraybuffer(\"../assets/textures/compressed/sample_uastc_zstd.ktx2\",(arrayBuffer) => {\n *\n * sceneModel.createTexture({\n * id: \"myColorTexture\",\n * buffers: [arrayBuffer] // <<----- KTX2 texture asset\n * });\n *\n * sceneModel.createTexture({\n * id: \"myMetallicRoughnessTexture\",\n * src: \"../assets/textures/alpha/crosshatchAlphaMap.jpg\" // <<----- JPEG texture asset\n * });\n *\n * sceneModel.createTextureSet({\n * id: \"myTextureSet\",\n * colorTextureId: \"myColorTexture\",\n * metallicRoughnessTextureId: \"myMetallicRoughnessTexture\"\n * });\n *\n * sceneModel.createMesh({\n * id: \"myMesh\",\n * textureSetId: \"myTextureSet\",\n * primitive: \"triangles\",\n * positions: [1, 1, 1, ...],\n * normals: [0, 0, 1, 0, ...],\n * uv: [1, 0, 0, ...],\n * indices: [0, 1, 2, ...],\n * });\n *\n * sceneModel.createEntity({\n * id: \"myEntity\",\n * meshIds: [\"myMesh\"]\n * });\n *\n * sceneModel.finalize();\n * });\n * ````\n *\n * @implements {Entity}\n */\nclass SceneModel extends Component {\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent scene, generated automatically when omitted.\n * @param {Boolean} [cfg.isModel] Specify ````true```` if this SceneModel represents a model, in which case the SceneModel will be registered by {@link SceneModel#id} in {@link Scene#models} and may also have a corresponding {@link MetaModel} with matching {@link MetaModel#id}, registered by that ID in {@link MetaScene#metaModels}.\n * @param {Number[]} [cfg.origin=[0,0,0]] World-space double-precision 3D origin.\n * @param {Number[]} [cfg.position=[0,0,0]] Local, single-precision 3D position, relative to the origin parameter.\n * @param {Number[]} [cfg.scale=[1,1,1]] Local scale.\n * @param {Number[]} [cfg.rotation=[0,0,0]] Local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] Local modelling transform matrix. Overrides the position, scale and rotation parameters.\n * @param {Boolean} [cfg.visible=true] Indicates if the SceneModel is initially visible.\n * @param {Boolean} [cfg.culled=false] Indicates if the SceneModel is initially culled from view.\n * @param {Boolean} [cfg.pickable=true] Indicates if the SceneModel is initially pickable.\n * @param {Boolean} [cfg.clippable=true] Indicates if the SceneModel is initially clippable.\n * @param {Boolean} [cfg.collidable=true] Indicates if the SceneModel is initially included in boundary calculations.\n * @param {Boolean} [cfg.xrayed=false] Indicates if the SceneModel is initially xrayed.\n * @param {Boolean} [cfg.highlighted=false] Indicates if the SceneModel is initially highlighted.\n * @param {Boolean} [cfg.selected=false] Indicates if the SceneModel is initially selected.\n * @param {Boolean} [cfg.edges=false] Indicates if the SceneModel's edges are initially emphasized.\n * @param {Number[]} [cfg.colorize=[1.0,1.0,1.0]] SceneModel's initial RGB colorize color, multiplies by the rendered fragment colors.\n * @param {Number} [cfg.opacity=1.0] SceneModel's initial opacity factor, multiplies by the rendered fragment alpha.\n * @param {Number} [cfg.backfaces=false] When we set this ````true````, then we force rendering of backfaces for this SceneModel. When\n * we leave this ````false````, then we allow the Viewer to decide when to render backfaces. In that case, the\n * Viewer will hide backfaces on watertight meshes, show backfaces on open meshes, and always show backfaces on meshes when we slice them open with {@link SectionPlane}s.\n * @param {Boolean} [cfg.saoEnabled=true] Indicates if Scalable Ambient Obscurance (SAO) will apply to this SceneModel. SAO is configured by the Scene's {@link SAO} component.\n * @param {Boolean} [cfg.pbrEnabled=true] Indicates if physically-based rendering (PBR) will apply to the SceneModel when {@link Scene#pbrEnabled} is ````true````.\n * @param {Boolean} [cfg.colorTextureEnabled=true] Indicates if base color textures will be rendered for the SceneModel when {@link Scene#colorTextureEnabled} is ````true````.\n * @param {Number} [cfg.edgeThreshold=10] When xraying, highlighting, selecting or edging, this is the threshold angle between normals of adjacent triangles, below which their shared wireframe edge is not drawn.\n * @param {Number} [cfg.maxGeometryBatchSize=50000000] Maximum geometry batch size, as number of vertices. This is optionally supplied\n * to limit the size of the batched geometry arrays that SceneModel internally creates for batched geometries.\n * A lower value means less heap allocation/de-allocation while creating/loading batched geometries, but more draw calls and\n * slower rendering speed. A high value means larger heap allocation/de-allocation while creating/loading, but less draw calls\n * and faster rendering speed. It's recommended to keep this somewhere roughly between ````50000```` and ````50000000```.\n * @param {TextureTranscoder} [cfg.textureTranscoder] Transcoder that will be used internally by {@link SceneModel#createTexture}\n * to convert transcoded texture data. Only required when we'll be providing transcoded data\n * to {@link SceneModel#createTexture}. We assume that all transcoded texture data added to a ````SceneModel````\n * will then in a format supported by this transcoder.\n * @param {Boolean} [cfg.dtxEnabled=true] When ````true```` (default) use data textures (DTX), where appropriate, to\n * represent the returned model. Set false to always use vertex buffer objects (VBOs). Note that DTX is only applicable\n * to non-textured triangle meshes, and that VBOs are always used for meshes that have textures, line segments, or point\n * primitives. Only works while {@link DTX#enabled} is also ````true````.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._dtxEnabled = this.scene.dtxEnabled && (cfg.dtxEnabled !== false);\n\n this._enableVertexWelding = false; // Not needed for most objects, and very expensive, so disabled\n this._enableIndexBucketing = false; // Until fixed: https://github.com/xeokit/xeokit-sdk/issues/1204\n\n this._vboBatchingLayerScratchMemory = getScratchMemory();\n this._textureTranscoder = cfg.textureTranscoder || getKTX2TextureTranscoder(this.scene.viewer);\n\n this._maxGeometryBatchSize = cfg.maxGeometryBatchSize;\n\n this._aabb = math.collapseAABB3();\n this._aabbDirty = true;\n\n this._quantizationRanges = {};\n\n this._vboInstancingLayers = {};\n this._vboBatchingLayers = {};\n this._dtxLayers = {};\n\n this._meshList = [];\n\n this.layerList = []; // For GL state efficiency when drawing, InstancingLayers are in first part, BatchingLayers are in second\n this._entityList = [];\n\n this._geometries = {};\n this._dtxBuckets = {}; // Geometries with optimizations used for data texture representation\n this._textures = {};\n this._textureSets = {};\n this._transforms = {};\n this._meshes = {};\n this._unusedMeshes = {};\n this._entities = {};\n\n /** @private **/\n this.renderFlags = new RenderFlags();\n\n /**\n * @private\n */\n this.numGeometries = 0; // Number of geometries created with createGeometry()\n\n // These counts are used to avoid unnecessary render passes\n // They are incremented or decremented exclusively by BatchingLayer and InstancingLayer\n\n /**\n * @private\n */\n this.numPortions = 0;\n\n /**\n * @private\n */\n this.numVisibleLayerPortions = 0;\n\n /**\n * @private\n */\n this.numTransparentLayerPortions = 0;\n\n /**\n * @private\n */\n this.numXRayedLayerPortions = 0;\n\n /**\n * @private\n */\n this.numHighlightedLayerPortions = 0;\n\n /**\n * @private\n */\n this.numSelectedLayerPortions = 0;\n\n /**\n * @private\n */\n this.numEdgesLayerPortions = 0;\n\n /**\n * @private\n */\n this.numPickableLayerPortions = 0;\n\n /**\n * @private\n */\n this.numClippableLayerPortions = 0;\n\n /**\n * @private\n */\n this.numCulledLayerPortions = 0;\n\n this.numEntities = 0;\n this._numTriangles = 0;\n this._numLines = 0;\n this._numPoints = 0;\n\n this._edgeThreshold = cfg.edgeThreshold || 10;\n\n // Build static matrix\n\n this._origin = math.vec3(cfg.origin || [0, 0, 0]);\n this._position = math.vec3(cfg.position || [0, 0, 0]);\n this._rotation = math.vec3(cfg.rotation || [0, 0, 0]);\n this._quaternion = math.vec4(cfg.quaternion || [0, 0, 0, 1]);\n this._conjugateQuaternion = math.vec4(cfg.quaternion || [0, 0, 0, 1]);\n\n if (cfg.rotation) {\n math.eulerToQuaternion(this._rotation, \"XYZ\", this._quaternion);\n }\n this._scale = math.vec3(cfg.scale || [1, 1, 1]);\n\n this._worldRotationMatrix = math.mat4();\n this._worldRotationMatrixConjugate = math.mat4();\n this._matrix = math.mat4();\n this._matrixDirty = true;\n\n this._rebuildMatrices();\n\n this._worldNormalMatrix = math.mat4();\n math.inverseMat4(this._matrix, this._worldNormalMatrix);\n math.transposeMat4(this._worldNormalMatrix);\n\n if (cfg.matrix || cfg.position || cfg.rotation || cfg.scale || cfg.quaternion) {\n this._viewMatrix = math.mat4();\n this._viewNormalMatrix = math.mat4();\n this._viewMatrixDirty = true;\n this._matrixNonIdentity = true;\n }\n\n this._opacity = 1.0;\n this._colorize = [1, 1, 1];\n\n this._saoEnabled = (cfg.saoEnabled !== false);\n this._pbrEnabled = (cfg.pbrEnabled !== false);\n this._colorTextureEnabled = (cfg.colorTextureEnabled !== false);\n\n this._isModel = cfg.isModel;\n if (this._isModel) {\n this.scene._registerModel(this);\n }\n\n this._onCameraViewMatrix = this.scene.camera.on(\"matrix\", () => {\n this._viewMatrixDirty = true;\n });\n\n this._meshesWithDirtyMatrices = [];\n this._numMeshesWithDirtyMatrices = 0;\n\n this._onTick = this.scene.on(\"tick\", () => {\n while (this._numMeshesWithDirtyMatrices > 0) {\n this._meshesWithDirtyMatrices[--this._numMeshesWithDirtyMatrices]._updateMatrix();\n }\n });\n\n this._createDefaultTextureSet();\n\n this.visible = cfg.visible;\n this.culled = cfg.culled;\n this.pickable = cfg.pickable;\n this.clippable = cfg.clippable;\n this.collidable = cfg.collidable;\n this.castsShadow = cfg.castsShadow;\n this.receivesShadow = cfg.receivesShadow;\n this.xrayed = cfg.xrayed;\n this.highlighted = cfg.highlighted;\n this.selected = cfg.selected;\n this.edges = cfg.edges;\n this.colorize = cfg.colorize;\n this.opacity = cfg.opacity;\n this.backfaces = cfg.backfaces;\n }\n\n _meshMatrixDirty(mesh) {\n this._meshesWithDirtyMatrices[this._numMeshesWithDirtyMatrices++] = mesh;\n }\n\n _createDefaultTextureSet() {\n // Every SceneModelMesh gets at least the default TextureSet,\n // which contains empty default textures filled with color\n const defaultColorTexture = new SceneModelTexture({\n id: DEFAULT_COLOR_TEXTURE_ID,\n texture: new Texture2D({\n gl: this.scene.canvas.gl,\n preloadColor: [1, 1, 1, 1] // [r, g, b, a]})\n })\n });\n const defaultMetalRoughTexture = new SceneModelTexture({\n id: DEFAULT_METAL_ROUGH_TEXTURE_ID,\n texture: new Texture2D({\n gl: this.scene.canvas.gl,\n preloadColor: [0, 1, 1, 1] // [unused, roughness, metalness, unused]\n })\n });\n const defaultNormalsTexture = new SceneModelTexture({\n id: DEFAULT_NORMALS_TEXTURE_ID,\n texture: new Texture2D({\n gl: this.scene.canvas.gl,\n preloadColor: [0, 0, 0, 0] // [x, y, z, unused] - these must be zeros\n })\n });\n const defaultEmissiveTexture = new SceneModelTexture({\n id: DEFAULT_EMISSIVE_TEXTURE_ID,\n texture: new Texture2D({\n gl: this.scene.canvas.gl,\n preloadColor: [0, 0, 0, 1] // [x, y, z, unused]\n })\n });\n const defaultOcclusionTexture = new SceneModelTexture({\n id: DEFAULT_OCCLUSION_TEXTURE_ID,\n texture: new Texture2D({\n gl: this.scene.canvas.gl,\n preloadColor: [1, 1, 1, 1] // [x, y, z, unused]\n })\n });\n this._textures[DEFAULT_COLOR_TEXTURE_ID] = defaultColorTexture;\n this._textures[DEFAULT_METAL_ROUGH_TEXTURE_ID] = defaultMetalRoughTexture;\n this._textures[DEFAULT_NORMALS_TEXTURE_ID] = defaultNormalsTexture;\n this._textures[DEFAULT_EMISSIVE_TEXTURE_ID] = defaultEmissiveTexture;\n this._textures[DEFAULT_OCCLUSION_TEXTURE_ID] = defaultOcclusionTexture;\n this._textureSets[DEFAULT_TEXTURE_SET_ID] = new SceneModelTextureSet({\n id: DEFAULT_TEXTURE_SET_ID,\n model: this,\n colorTexture: defaultColorTexture,\n metallicRoughnessTexture: defaultMetalRoughTexture,\n normalsTexture: defaultNormalsTexture,\n emissiveTexture: defaultEmissiveTexture,\n occlusionTexture: defaultOcclusionTexture\n });\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // SceneModel members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Returns true to indicate that this Component is a SceneModel.\n * @type {Boolean}\n */\n get isPerformanceModel() {\n return true;\n }\n\n /**\n * The {@link SceneModelTransform}s in this SceneModel.\n *\n * Each {#link SceneModelTransform} is stored here against its {@link SceneModelTransform.id}.\n *\n * @returns {*|{}}\n */\n get transforms() {\n return this._transforms;\n }\n\n /**\n * The {@link SceneModelTexture}s in this SceneModel.\n *\n * * Each {@link SceneModelTexture} is created with {@link SceneModel.createTexture}.\n * * Each {@link SceneModelTexture} is stored here against its {@link SceneModelTexture.id}.\n *\n * @returns {*|{}}\n */\n get textures() {\n return this._textures;\n }\n\n /**\n * The {@link SceneModelTextureSet}s in this SceneModel.\n *\n * Each {@link SceneModelTextureSet} is stored here against its {@link SceneModelTextureSet.id}.\n *\n * @returns {*|{}}\n */\n get textureSets() {\n return this._textureSets;\n }\n\n /**\n * The {@link SceneModelMesh}es in this SceneModel.\n *\n * Each {@SceneModelMesh} is stored here against its {@link SceneModelMesh.id}.\n *\n * @returns {*|{}}\n */\n get meshes() {\n return this._meshes;\n }\n\n /**\n * The {@link SceneModelEntity}s in this SceneModel.\n *\n * Each {#link SceneModelEntity} in this SceneModel that represents an object is\n * stored here against its {@link SceneModelTransform.id}.\n *\n * @returns {*|{}}\n */\n get objects() {\n return this._entities;\n }\n\n /**\n * Gets the 3D World-space origin for this SceneModel.\n *\n * Each {@link SceneModelMesh.origin}, if supplied, is relative to this origin.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Float64Array}\n */\n get origin() {\n return this._origin;\n }\n\n /**\n * Sets the SceneModel's local translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set position(value) {\n this._position.set(value || [0, 0, 0]);\n this._setWorldMatrixDirty();\n this._sceneModelDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the SceneModel's local translation.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get position() {\n return this._position;\n }\n\n /**\n * Sets the SceneModel's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n set rotation(value) {\n this._rotation.set(value || [0, 0, 0]);\n math.eulerToQuaternion(this._rotation, \"XYZ\", this._quaternion);\n this._setWorldMatrixDirty();\n this._sceneModelDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the SceneModel's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.\n *\n * Default value is ````[0,0,0]````.\n *\n * @type {Number[]}\n */\n get rotation() {\n return this._rotation;\n }\n\n /**\n * Sets the SceneModel's local rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n set quaternion(value) {\n this._quaternion.set(value || [0, 0, 0, 1]);\n math.quaternionToEuler(this._quaternion, \"XYZ\", this._rotation);\n this._setWorldMatrixDirty();\n this._sceneModelDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the SceneModel's local rotation quaternion.\n *\n * Default value is ````[0,0,0,1]````.\n *\n * @type {Number[]}\n */\n get quaternion() {\n return this._quaternion;\n }\n\n /**\n * Sets the SceneModel's local scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n * @deprecated\n */\n set scale(value) {\n // NOP - deprecated\n }\n\n /**\n * Gets the SceneModel's local scale.\n *\n * Default value is ````[1,1,1]````.\n *\n * @type {Number[]}\n * @deprecated\n */\n get scale() {\n return this._scale;\n }\n\n /**\n * Sets the SceneModel's local modeling transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n set matrix(value) {\n this._matrix.set(value || DEFAULT_MATRIX);\n\n math.quaternionToRotationMat4(this._quaternion, this._worldRotationMatrix);\n math.conjugateQuaternion(this._quaternion, this._conjugateQuaternion);\n math.quaternionToRotationMat4(this._quaternion, this._worldRotationMatrixConjugate);\n this._matrix.set(this._worldRotationMatrix);\n math.translateMat4v(this._position, this._matrix);\n\n this._matrixDirty = false;\n this._setWorldMatrixDirty();\n this._sceneModelDirty();\n this.glRedraw();\n }\n\n /**\n * Gets the SceneModel's local modeling transform matrix.\n *\n * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.\n *\n * @type {Number[]}\n */\n get matrix() {\n if (this._matrixDirty) {\n this._rebuildMatrices();\n }\n return this._matrix;\n }\n\n /**\n * Gets the SceneModel's local modeling rotation transform matrix.\n *\n * @type {Number[]}\n */\n get rotationMatrix() {\n if (this._matrixDirty) {\n this._rebuildMatrices();\n }\n return this._worldRotationMatrix;\n }\n\n _rebuildMatrices() {\n if (this._matrixDirty) {\n math.quaternionToRotationMat4(this._quaternion, this._worldRotationMatrix);\n math.conjugateQuaternion(this._quaternion, this._conjugateQuaternion);\n math.quaternionToRotationMat4(this._quaternion, this._worldRotationMatrixConjugate);\n this._matrix.set(this._worldRotationMatrix);\n math.translateMat4v(this._position, this._matrix);\n this._matrixDirty = false;\n }\n }\n\n /**\n * Gets the conjugate of the SceneModel's local modeling rotation transform matrix.\n *\n * This is used for RTC view matrix management in renderers.\n *\n * @type {Number[]}\n */\n get rotationMatrixConjugate() {\n if (this._matrixDirty) {\n this._rebuildMatrices();\n }\n return this._worldRotationMatrixConjugate;\n }\n\n _setWorldMatrixDirty() {\n this._matrixDirty = true;\n this._aabbDirty = true;\n }\n\n _transformDirty() {\n this._matrixDirty = true;\n this._aabbDirty = true;\n this.scene._aabbDirty = true;\n }\n\n _sceneModelDirty() {\n this.scene._aabbDirty = true;\n this._aabbDirty = true;\n this.scene._aabbDirty = true;\n this._matrixDirty = true;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i]._sceneModelDirty(); // Entities need to retransform their World AABBs by SceneModel's worldMatrix\n }\n }\n\n /**\n * Gets the SceneModel's World matrix.\n *\n * @property worldMatrix\n * @type {Number[]}\n */\n get worldMatrix() {\n return this.matrix;\n }\n\n /**\n * Gets the SceneModel's World normal matrix.\n *\n * @type {Number[]}\n */\n get worldNormalMatrix() {\n return this._worldNormalMatrix;\n }\n\n /**\n * Called by private renderers in ./lib, returns the view matrix with which to\n * render this SceneModel. The view matrix is the concatenation of the\n * Camera view matrix with the Performance model's world (modeling) matrix.\n *\n * @private\n */\n get viewMatrix() {\n if (!this._viewMatrix) {\n return this.scene.camera.viewMatrix;\n }\n if (this._matrixDirty) {\n this._rebuildMatrices();\n this._viewMatrixDirty = true;\n }\n if (this._viewMatrixDirty) {\n math.mulMat4(this.scene.camera.viewMatrix, this._matrix, this._viewMatrix);\n math.inverseMat4(this._viewMatrix, this._viewNormalMatrix);\n math.transposeMat4(this._viewNormalMatrix);\n this._viewMatrixDirty = false;\n }\n return this._viewMatrix;\n }\n\n /**\n * Called by private renderers in ./lib, returns the view normal matrix with which to render this SceneModel.\n *\n * @private\n */\n get viewNormalMatrix() {\n if (!this._viewNormalMatrix) {\n return this.scene.camera.viewNormalMatrix;\n }\n if (this._matrixDirty) {\n this._rebuildMatrices();\n this._viewMatrixDirty = true;\n }\n if (this._viewMatrixDirty) {\n math.mulMat4(this.scene.camera.viewMatrix, this._matrix, this._viewMatrix);\n math.inverseMat4(this._viewMatrix, this._viewNormalMatrix);\n math.transposeMat4(this._viewNormalMatrix);\n this._viewMatrixDirty = false;\n }\n math.inverseMat4(this._viewMatrix, this._viewNormalMatrix);\n math.transposeMat4(this._viewNormalMatrix);\n return this._viewNormalMatrix;\n }\n\n /**\n * Sets if backfaces are rendered for this SceneModel.\n *\n * Default is ````false````.\n *\n * @type {Boolean}\n */\n get backfaces() {\n return this._backfaces;\n }\n\n /**\n * Sets if backfaces are rendered for this SceneModel.\n *\n * Default is ````false````.\n *\n * When we set this ````true````, then backfaces are always rendered for this SceneModel.\n *\n * When we set this ````false````, then we allow the Viewer to decide whether to render backfaces. In this case,\n * the Viewer will:\n *\n * * hide backfaces on watertight meshes,\n * * show backfaces on open meshes, and\n * * always show backfaces on meshes when we slice them open with {@link SectionPlane}s.\n *\n * @type {Boolean}\n */\n set backfaces(backfaces) {\n backfaces = !!backfaces;\n this._backfaces = backfaces;\n this.glRedraw();\n }\n\n /**\n * Gets the list of {@link SceneModelEntity}s within this SceneModel.\n *\n * @returns {SceneModelEntity[]}\n */\n get entityList() {\n return this._entityList;\n }\n\n /**\n * Returns true to indicate that SceneModel is an {@link Entity}.\n * @type {Boolean}\n */\n get isEntity() {\n return true;\n }\n\n /**\n * Returns ````true```` if this SceneModel represents a model.\n *\n * When ````true```` the SceneModel will be registered by {@link SceneModel#id} in\n * {@link Scene#models} and may also have a {@link MetaObject} with matching {@link MetaObject#id}.\n *\n * @type {Boolean}\n */\n get isModel() {\n return this._isModel;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // SceneModel members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Returns ````false```` to indicate that SceneModel never represents an object.\n *\n * @type {Boolean}\n */\n get isObject() {\n return false;\n }\n\n /**\n * Gets the SceneModel's World-space 3D axis-aligned bounding box.\n *\n * Represented by a six-element Float64Array containing the min/max extents of the\n * axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````.\n *\n * @type {Number[]}\n */\n get aabb() {\n if (this._aabbDirty) {\n math.collapseAABB3(this._aabb);\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n math.expandAABB3(this._aabb, this._entityList[i].aabb);\n }\n this._aabbDirty = false;\n }\n return this._aabb;\n }\n\n /**\n * The approximate number of triangle primitives in this SceneModel.\n *\n * @type {Number}\n */\n get numTriangles() {\n return this._numTriangles;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Entity members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * The approximate number of line primitives in this SceneModel.\n *\n * @type {Number}\n */\n get numLines() {\n return this._numLines;\n }\n\n /**\n * The approximate number of point primitives in this SceneModel.\n *\n * @type {Number}\n */\n get numPoints() {\n return this._numPoints;\n }\n\n /**\n * Gets if any {@link SceneModelEntity}s in this SceneModel are visible.\n *\n * The SceneModel is only rendered when {@link SceneModel#visible} is ````true```` and {@link SceneModel#culled} is ````false````.\n *\n * @type {Boolean}\n */\n get visible() {\n return (this.numVisibleLayerPortions > 0);\n }\n\n /**\n * Sets if this SceneModel is visible.\n *\n * The SceneModel is only rendered when {@link SceneModel#visible} is ````true```` and {@link SceneModel#culled} is ````false````.\n **\n * @type {Boolean}\n */\n set visible(visible) {\n visible = visible !== false;\n this._visible = visible;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].visible = visible;\n }\n this.glRedraw();\n }\n\n /**\n * Gets if any {@link SceneModelEntity}s in this SceneModel are xrayed.\n *\n * @type {Boolean}\n */\n get xrayed() {\n return (this.numXRayedLayerPortions > 0);\n }\n\n /**\n * Sets if all {@link SceneModelEntity}s in this SceneModel are xrayed.\n *\n * @type {Boolean}\n */\n set xrayed(xrayed) {\n xrayed = !!xrayed;\n this._xrayed = xrayed;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].xrayed = xrayed;\n }\n this.glRedraw();\n }\n\n /**\n * Gets if any {@link SceneModelEntity}s in this SceneModel are highlighted.\n *\n * @type {Boolean}\n */\n get highlighted() {\n return (this.numHighlightedLayerPortions > 0);\n }\n\n /**\n * Sets if all {@link SceneModelEntity}s in this SceneModel are highlighted.\n *\n * @type {Boolean}\n */\n set highlighted(highlighted) {\n highlighted = !!highlighted;\n this._highlighted = highlighted;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].highlighted = highlighted;\n }\n this.glRedraw();\n }\n\n /**\n * Gets if any {@link SceneModelEntity}s in this SceneModel are selected.\n *\n * @type {Boolean}\n */\n get selected() {\n return (this.numSelectedLayerPortions > 0);\n }\n\n /**\n * Sets if all {@link SceneModelEntity}s in this SceneModel are selected.\n *\n * @type {Boolean}\n */\n set selected(selected) {\n selected = !!selected;\n this._selected = selected;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].selected = selected;\n }\n this.glRedraw();\n }\n\n /**\n * Gets if any {@link SceneModelEntity}s in this SceneModel have edges emphasised.\n *\n * @type {Boolean}\n */\n get edges() {\n return (this.numEdgesLayerPortions > 0);\n }\n\n /**\n * Sets if all {@link SceneModelEntity}s in this SceneModel have edges emphasised.\n *\n * @type {Boolean}\n */\n set edges(edges) {\n edges = !!edges;\n this._edges = edges;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].edges = edges;\n }\n this.glRedraw();\n }\n\n /**\n * Gets if this SceneModel is culled from view.\n *\n * The SceneModel is only rendered when {@link SceneModel#visible} is true and {@link SceneModel#culled} is false.\n *\n * @type {Boolean}\n */\n get culled() {\n return this._culled;\n }\n\n /**\n * Sets if this SceneModel is culled from view.\n *\n * The SceneModel is only rendered when {@link SceneModel#visible} is true and {@link SceneModel#culled} is false.\n *\n * @type {Boolean}\n */\n set culled(culled) {\n culled = !!culled;\n this._culled = culled;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].culled = culled;\n }\n this.glRedraw();\n }\n\n /**\n * Gets if {@link SceneModelEntity}s in this SceneModel are clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * @type {Boolean}\n */\n get clippable() {\n return this._clippable;\n }\n\n /**\n * Sets if {@link SceneModelEntity}s in this SceneModel are clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * @type {Boolean}\n */\n set clippable(clippable) {\n clippable = clippable !== false;\n this._clippable = clippable;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].clippable = clippable;\n }\n this.glRedraw();\n }\n\n /**\n * Gets if this SceneModel is collidable.\n *\n * @type {Boolean}\n */\n get collidable() {\n return this._collidable;\n }\n\n /**\n * Sets if {@link SceneModelEntity}s in this SceneModel are collidable.\n *\n * @type {Boolean}\n */\n set collidable(collidable) {\n collidable = collidable !== false;\n this._collidable = collidable;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].collidable = collidable;\n }\n }\n\n /**\n * Gets if this SceneModel is pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * @type {Boolean}\n */\n get pickable() {\n return (this.numPickableLayerPortions > 0);\n }\n\n /**\n * Sets if {@link SceneModelEntity}s in this SceneModel are pickable.\n *\n * Picking is done via calls to {@link Scene#pick}.\n *\n * @type {Boolean}\n */\n set pickable(pickable) {\n pickable = pickable !== false;\n this._pickable = pickable;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].pickable = pickable;\n }\n }\n\n /**\n * Gets the RGB colorize color for this SceneModel.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * @type {Number[]}\n */\n get colorize() {\n return this._colorize;\n }\n\n /**\n * Sets the RGB colorize color for this SceneModel.\n *\n * Multiplies by rendered fragment colors.\n *\n * Each element of the color is in range ````[0..1]````.\n *\n * @type {Number[]}\n */\n set colorize(colorize) {\n this._colorize = colorize;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].colorize = colorize;\n }\n }\n\n /**\n * Gets this SceneModel's opacity factor.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n get opacity() {\n return this._opacity;\n }\n\n /**\n * Sets the opacity factor for this SceneModel.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n set opacity(opacity) {\n this._opacity = opacity;\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i].opacity = opacity;\n }\n }\n\n /**\n * Gets if this SceneModel casts a shadow.\n *\n * @type {Boolean}\n */\n get castsShadow() {\n return this._castsShadow;\n }\n\n /**\n * Sets if this SceneModel casts a shadow.\n *\n * @type {Boolean}\n */\n set castsShadow(castsShadow) {\n castsShadow = (castsShadow !== false);\n if (castsShadow !== this._castsShadow) {\n this._castsShadow = castsShadow;\n this.glRedraw();\n }\n }\n\n /**\n * Sets if this SceneModel can have shadow cast upon it.\n *\n * @type {Boolean}\n */\n get receivesShadow() {\n return this._receivesShadow;\n }\n\n /**\n * Sets if this SceneModel can have shadow cast upon it.\n *\n * @type {Boolean}\n */\n set receivesShadow(receivesShadow) {\n receivesShadow = (receivesShadow !== false);\n if (receivesShadow !== this._receivesShadow) {\n this._receivesShadow = receivesShadow;\n this.glRedraw();\n }\n }\n\n /**\n * Gets if Scalable Ambient Obscurance (SAO) will apply to this SceneModel.\n *\n * SAO is configured by the Scene's {@link SAO} component.\n *\n * Only works when {@link SAO#enabled} is also true.\n *\n * @type {Boolean}\n */\n get saoEnabled() {\n return this._saoEnabled;\n }\n\n /**\n * Gets if physically-based rendering (PBR) is enabled for this SceneModel.\n *\n * Only works when {@link Scene#pbrEnabled} is also true.\n *\n * @type {Boolean}\n */\n get pbrEnabled() {\n return this._pbrEnabled;\n }\n\n /**\n * Gets if color textures are enabled for this SceneModel.\n *\n * Only works when {@link Scene#colorTextureEnabled} is also true.\n *\n * @type {Boolean}\n */\n get colorTextureEnabled() {\n return this._colorTextureEnabled;\n }\n\n /**\n * Returns true to indicate that SceneModel is implements {@link Drawable}.\n *\n * @type {Boolean}\n */\n get isDrawable() {\n return true;\n }\n\n /** @private */\n get isStateSortable() {\n return false\n }\n\n /**\n * Configures the appearance of xrayed {@link SceneModelEntity}s within this SceneModel.\n *\n * This is the {@link Scene#xrayMaterial}.\n *\n * @type {EmphasisMaterial}\n */\n get xrayMaterial() {\n return this.scene.xrayMaterial;\n }\n\n /**\n * Configures the appearance of highlighted {@link SceneModelEntity}s within this SceneModel.\n *\n * This is the {@link Scene#highlightMaterial}.\n *\n * @type {EmphasisMaterial}\n */\n get highlightMaterial() {\n return this.scene.highlightMaterial;\n }\n\n /**\n * Configures the appearance of selected {@link SceneModelEntity}s within this SceneModel.\n *\n * This is the {@link Scene#selectedMaterial}.\n *\n * @type {EmphasisMaterial}\n */\n get selectedMaterial() {\n return this.scene.selectedMaterial;\n }\n\n /**\n * Configures the appearance of edges of {@link SceneModelEntity}s within this SceneModel.\n *\n * This is the {@link Scene#edgeMaterial}.\n *\n * @type {EdgeMaterial}\n */\n get edgeMaterial() {\n return this.scene.edgeMaterial;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Drawable members\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Called by private renderers in ./lib, returns the picking view matrix with which to\n * ray-pick on this SceneModel.\n *\n * @private\n */\n getPickViewMatrix(pickViewMatrix) {\n if (!this._viewMatrix) {\n return pickViewMatrix;\n }\n return this._viewMatrix;\n }\n\n /**\n *\n * @param cfg\n */\n createQuantizationRange(cfg) {\n if (cfg.id === undefined || cfg.id === null) {\n this.error(\"[createQuantizationRange] Config missing: id\");\n return;\n }\n if (cfg.aabb) {\n this.error(\"[createQuantizationRange] Config missing: aabb\");\n return;\n }\n if (this._quantizationRanges[cfg.id]) {\n this.error(\"[createQuantizationRange] QuantizationRange already created: \" + cfg.id);\n return;\n }\n this._quantizationRanges[cfg.id] = {\n id: cfg.id,\n aabb: cfg.aabb,\n matrix: createPositionsDecodeMatrix(cfg.aabb, math.mat4())\n };\n }\n\n /**\n * Creates a reusable geometry within this SceneModel.\n *\n * We can then supply the geometry ID to {@link SceneModel#createMesh} when we want to create meshes that\n * instance the geometry.\n *\n * @param {*} cfg Geometry properties.\n * @param {String|Number} cfg.id Mandatory ID for the geometry, to refer to with {@link SceneModel#createMesh}.\n * @param {String} cfg.primitive The primitive type. Accepted values are 'points', 'lines', 'triangles', 'solid' and 'surface'.\n * @param {Number[]} [cfg.positions] Flat array of uncompressed 3D vertex positions positions. Required for all primitive types. Overridden by ````positionsCompressed````.\n * @param {Number[]} [cfg.positionsCompressed] Flat array of quantized 3D vertex positions. Overrides ````positions````, and must be accompanied by ````positionsDecodeMatrix````.\n * @param {Number[]} [cfg.positionsDecodeMatrix] A 4x4 matrix for decompressing ````positionsCompressed````. Must be accompanied by ````positionsCompressed````.\n * @param {Number[]} [cfg.normals] Flat array of normal vectors. Only used with \"triangles\", \"solid\" and \"surface\" primitives. When no normals are given, the geometry will be flat shaded using auto-generated face-aligned normals.\n * @param {Number[]} [cfg.normalsCompressed] Flat array of oct-encoded normal vectors. Overrides ````normals````. Only used with \"triangles\", \"solid\" and \"surface\" primitives. When no normals are given, the geometry will be flat shaded using auto-generated face-aligned normals.\n * @param {Number[]} [cfg.colors] Flat array of uncompressed RGBA vertex colors, as float values in range ````[0..1]````. Ignored when ````geometryId```` is given. Overridden by ````color```` and ````colorsCompressed````.\n * @param {Number[]} [cfg.colorsCompressed] Flat array of compressed RGBA vertex colors, as unsigned short integers in range ````[0..255]````. Ignored when ````geometryId```` is given. Overrides ````colors```` and is overridden by ````color````.\n * @param {Number[]} [cfg.uv] Flat array of uncompressed vertex UV coordinates. Only used with \"triangles\", \"solid\" and \"surface\" primitives. Required for textured rendering.\n * @param {Number[]} [cfg.uvCompressed] Flat array of compressed vertex UV coordinates. Only used with \"triangles\", \"solid\" and \"surface\" primitives. Overrides ````uv````. Must be accompanied by ````uvDecodeMatrix````. Only used with \"triangles\", \"solid\" and \"surface\" primitives. Required for textured rendering.\n * @param {Number[]} [cfg.uvDecodeMatrix] A 3x3 matrix for decompressing ````uvCompressed````.\n * @param {Number[]} [cfg.indices] Array of primitive connectivity indices. Not required for `points` primitives.\n * @param {Number[]} [cfg.edgeIndices] Array of edge line indices. Used only with 'triangles', 'solid' and 'surface' primitives. Automatically generated internally if not supplied, using the optional ````edgeThreshold```` given to the ````SceneModel```` constructor.\n */\n createGeometry(cfg) {\n if (cfg.id === undefined || cfg.id === null) {\n this.error(\"[createGeometry] Config missing: id\");\n return;\n }\n if (this._geometries[cfg.id]) {\n this.error(\"[createGeometry] Geometry already created: \" + cfg.id);\n return;\n }\n if (cfg.primitive === undefined || cfg.primitive === null) {\n cfg.primitive = \"triangles\";\n }\n if (cfg.primitive !== \"points\" && cfg.primitive !== \"lines\" && cfg.primitive !== \"triangles\" && cfg.primitive !== \"solid\" && cfg.primitive !== \"surface\") {\n this.error(`[createGeometry] Unsupported value for 'primitive': '${cfg.primitive}' - supported values are 'points', 'lines', 'triangles', 'solid' and 'surface'. Defaulting to 'triangles'.`);\n return;\n }\n if (!cfg.positions && !cfg.positionsCompressed && !cfg.buckets) {\n this.error(\"[createGeometry] Param expected: `positions`, `positionsCompressed' or 'buckets\");\n return null;\n }\n if (cfg.positionsCompressed && !cfg.positionsDecodeMatrix && !cfg.positionsDecodeBoundary) {\n this.error(\"[createGeometry] Param expected: `positionsDecodeMatrix` or 'positionsDecodeBoundary' (required for `positionsCompressed')\");\n return null;\n }\n if (cfg.positionsDecodeMatrix && cfg.positionsDecodeBoundary) {\n this.error(\"[createGeometry] Only one of these params expected: `positionsDecodeMatrix` or 'positionsDecodeBoundary' (required for `positionsCompressed')\");\n return null;\n }\n if (cfg.uvCompressed && !cfg.uvDecodeMatrix) {\n this.error(\"[createGeometry] Param expected: `uvDecodeMatrix` (required for `uvCompressed')\");\n return null;\n }\n if (!cfg.buckets && !cfg.indices && (cfg.primitive === \"triangles\" || cfg.primitive === \"solid\" || cfg.primitive === \"surface\")) {\n const numPositions = (cfg.positions || cfg.positionsCompressed).length / 3;\n cfg.indices = this._createDefaultIndices(numPositions);\n }\n if (!cfg.buckets && !cfg.indices && cfg.primitive !== \"points\") {\n this.error(`[createGeometry] Param expected: indices (required for '${cfg.primitive}' primitive type)`);\n return null;\n }\n if (cfg.positionsDecodeBoundary) {\n cfg.positionsDecodeMatrix = createPositionsDecodeMatrix(cfg.positionsDecodeBoundary, math.mat4());\n }\n if (cfg.positions) {\n const aabb = math.collapseAABB3();\n cfg.positionsDecodeMatrix = math.mat4();\n math.expandAABB3Points3(aabb, cfg.positions);\n cfg.positionsCompressed = quantizePositions(cfg.positions, aabb, cfg.positionsDecodeMatrix);\n cfg.aabb = aabb;\n } else if (cfg.positionsCompressed) {\n const aabb = math.collapseAABB3();\n cfg.positionsDecodeMatrix = new Float64Array(cfg.positionsDecodeMatrix);\n cfg.positionsCompressed = new Uint16Array(cfg.positionsCompressed);\n math.expandAABB3Points3(aabb, cfg.positionsCompressed);\n geometryCompressionUtils.decompressAABB(aabb, cfg.positionsDecodeMatrix);\n cfg.aabb = aabb;\n } else if (cfg.buckets) {\n const aabb = math.collapseAABB3();\n this._dtxBuckets[cfg.id] = cfg.buckets;\n for (let i = 0, len = cfg.buckets.length; i < len; i++) {\n const bucket = cfg.buckets[i];\n if (bucket.positions) {\n math.expandAABB3Points3(aabb, bucket.positions);\n } else if (bucket.positionsCompressed) {\n math.expandAABB3Points3(aabb, bucket.positionsCompressed);\n }\n }\n if (cfg.positionsDecodeMatrix) {\n geometryCompressionUtils.decompressAABB(aabb, cfg.positionsDecodeMatrix);\n }\n cfg.aabb = aabb;\n }\n if (cfg.colorsCompressed && cfg.colorsCompressed.length > 0) {\n cfg.colorsCompressed = new Uint8Array(cfg.colorsCompressed);\n } else if (cfg.colors && cfg.colors.length > 0) {\n const colors = cfg.colors;\n const colorsCompressed = new Uint8Array(colors.length);\n for (let i = 0, len = colors.length; i < len; i++) {\n colorsCompressed[i] = colors[i] * 255;\n }\n cfg.colorsCompressed = colorsCompressed;\n }\n if (!cfg.buckets && !cfg.edgeIndices && (cfg.primitive === \"triangles\" || cfg.primitive === \"solid\" || cfg.primitive === \"surface\")) {\n if (cfg.positions) {\n cfg.edgeIndices = buildEdgeIndices(cfg.positions, cfg.indices, null, 5.0);\n } else {\n cfg.edgeIndices = buildEdgeIndices(cfg.positionsCompressed, cfg.indices, cfg.positionsDecodeMatrix, 2.0);\n }\n }\n if (cfg.uv) {\n const bounds = geometryCompressionUtils.getUVBounds(cfg.uv);\n const result = geometryCompressionUtils.compressUVs(cfg.uv, bounds.min, bounds.max);\n cfg.uvCompressed = result.quantized;\n cfg.uvDecodeMatrix = result.decodeMatrix;\n } else if (cfg.uvCompressed) {\n cfg.uvCompressed = new Uint16Array(cfg.uvCompressed);\n cfg.uvDecodeMatrix = new Float64Array(cfg.uvDecodeMatrix);\n }\n if (cfg.normals) { // HACK\n cfg.normals = null;\n }\n this._geometries [cfg.id] = cfg;\n this._numTriangles += (cfg.indices ? Math.round(cfg.indices.length / 3) : 0);\n this.numGeometries++;\n }\n\n /**\n * Creates a texture within this SceneModel.\n *\n * We can then supply the texture ID to {@link SceneModel#createTextureSet} when we want to create texture sets that use the texture.\n *\n * @param {*} cfg Texture properties.\n * @param {String|Number} cfg.id Mandatory ID for the texture, to refer to with {@link SceneModel#createTextureSet}.\n * @param {String} [cfg.src] Image file for the texture. Assumed to be transcoded if not having a recognized image file\n * extension (jpg, jpeg, png etc.). If transcoded, then assumes ````SceneModel```` is configured with a {@link TextureTranscoder}.\n * @param {ArrayBuffer[]} [cfg.buffers] Transcoded texture data. Assumes ````SceneModel```` is\n * configured with a {@link TextureTranscoder}. This parameter is given as an array of buffers so we can potentially support multi-image textures, such as cube maps.\n * @param {HTMLImageElement} [cfg.image] HTML Image object to load into this texture. Overrides ````src```` and ````buffers````. Never transcoded.\n * @param {Number} [cfg.minFilter=LinearMipmapLinearFilter] How the texture is sampled when a texel covers less than one pixel.\n * Supported values are {@link LinearMipmapLinearFilter}, {@link LinearMipMapNearestFilter}, {@link NearestMipMapNearestFilter}, {@link NearestMipMapLinearFilter} and {@link LinearMipMapLinearFilter}.\n * @param {Number} [cfg.magFilter=LinearFilter] How the texture is sampled when a texel covers more than one pixel. Supported values are {@link LinearFilter} and {@link NearestFilter}.\n * @param {Number} [cfg.wrapS=RepeatWrapping] Wrap parameter for texture coordinate *S*. Supported values are {@link ClampToEdgeWrapping}, {@link MirroredRepeatWrapping} and {@link RepeatWrapping}.\n * @param {Number} [cfg.wrapT=RepeatWrapping] Wrap parameter for texture coordinate *T*. Supported values are {@link ClampToEdgeWrapping}, {@link MirroredRepeatWrapping} and {@link RepeatWrapping}..\n * @param {Number} [cfg.wrapR=RepeatWrapping] Wrap parameter for texture coordinate *R*. Supported values are {@link ClampToEdgeWrapping}, {@link MirroredRepeatWrapping} and {@link RepeatWrapping}.\n * @param {Boolean} [cfg.flipY=false] Flips this Texture's source data along its vertical axis when ````true````.\n * @param {Number} [cfg.encoding=LinearEncoding] Encoding format. Supported values are {@link LinearEncoding} and {@link sRGBEncoding}.\n */\n createTexture(cfg) {\n const textureId = cfg.id;\n if (textureId === undefined || textureId === null) {\n this.error(\"[createTexture] Config missing: id\");\n return;\n }\n if (this._textures[textureId]) {\n this.error(\"[createTexture] Texture already created: \" + textureId);\n return;\n }\n if (!cfg.src && !cfg.image && !cfg.buffers) {\n this.error(\"[createTexture] Param expected: `src`, `image' or 'buffers'\");\n return null;\n }\n let minFilter = cfg.minFilter || LinearMipmapLinearFilter;\n if (minFilter !== LinearFilter &&\n minFilter !== LinearMipMapNearestFilter &&\n minFilter !== LinearMipmapLinearFilter &&\n minFilter !== NearestMipMapLinearFilter &&\n minFilter !== NearestMipMapNearestFilter) {\n this.error(`[createTexture] Unsupported value for 'minFilter' - \n supported values are LinearFilter, LinearMipMapNearestFilter, NearestMipMapNearestFilter, \n NearestMipMapLinearFilter and LinearMipmapLinearFilter. Defaulting to LinearMipmapLinearFilter.`);\n minFilter = LinearMipmapLinearFilter;\n }\n let magFilter = cfg.magFilter || LinearFilter;\n if (magFilter !== LinearFilter && magFilter !== NearestFilter) {\n this.error(`[createTexture] Unsupported value for 'magFilter' - supported values are LinearFilter and NearestFilter. Defaulting to LinearFilter.`);\n magFilter = LinearFilter;\n }\n let wrapS = cfg.wrapS || RepeatWrapping;\n if (wrapS !== ClampToEdgeWrapping && wrapS !== MirroredRepeatWrapping && wrapS !== RepeatWrapping) {\n this.error(`[createTexture] Unsupported value for 'wrapS' - supported values are ClampToEdgeWrapping, MirroredRepeatWrapping and RepeatWrapping. Defaulting to RepeatWrapping.`);\n wrapS = RepeatWrapping;\n }\n let wrapT = cfg.wrapT || RepeatWrapping;\n if (wrapT !== ClampToEdgeWrapping && wrapT !== MirroredRepeatWrapping && wrapT !== RepeatWrapping) {\n this.error(`[createTexture] Unsupported value for 'wrapT' - supported values are ClampToEdgeWrapping, MirroredRepeatWrapping and RepeatWrapping. Defaulting to RepeatWrapping.`);\n wrapT = RepeatWrapping;\n }\n let wrapR = cfg.wrapR || RepeatWrapping;\n if (wrapR !== ClampToEdgeWrapping && wrapR !== MirroredRepeatWrapping && wrapR !== RepeatWrapping) {\n this.error(`[createTexture] Unsupported value for 'wrapR' - supported values are ClampToEdgeWrapping, MirroredRepeatWrapping and RepeatWrapping. Defaulting to RepeatWrapping.`);\n wrapR = RepeatWrapping;\n }\n let encoding = cfg.encoding || LinearEncoding;\n if (encoding !== LinearEncoding && encoding !== sRGBEncoding) {\n this.error(\"[createTexture] Unsupported value for 'encoding' - supported values are LinearEncoding and sRGBEncoding. Defaulting to LinearEncoding.\");\n encoding = LinearEncoding;\n }\n const texture = new Texture2D({\n gl: this.scene.canvas.gl,\n minFilter,\n magFilter,\n wrapS,\n wrapT,\n wrapR,\n // flipY: cfg.flipY,\n encoding\n });\n if (cfg.preloadColor) {\n texture.setPreloadColor(cfg.preloadColor);\n }\n if (cfg.image) { // Ignore transcoder for Images\n const image = cfg.image;\n image.crossOrigin = \"Anonymous\";\n texture.setImage(image, {minFilter, magFilter, wrapS, wrapT, wrapR, flipY: cfg.flipY, encoding});\n } else if (cfg.src) {\n const ext = cfg.src.split('.').pop();\n switch (ext) { // Don't transcode recognized image file types\n case \"jpeg\":\n case \"jpg\":\n case \"png\":\n case \"gif\":\n const image = new Image();\n image.onload = () => {\n texture.setImage(image, {\n minFilter,\n magFilter,\n wrapS,\n wrapT,\n wrapR,\n flipY: cfg.flipY,\n encoding\n });\n this.glRedraw();\n };\n image.src = cfg.src; // URL or Base64 string\n break;\n default: // Assume other file types need transcoding\n if (!this._textureTranscoder) {\n this.error(`[createTexture] Can't create texture from 'src' - SceneModel needs to be configured with a TextureTranscoder for this file type ('${ext}')`);\n } else {\n utils.loadArraybuffer(cfg.src, (arrayBuffer) => {\n if (!arrayBuffer.byteLength) {\n this.error(`[createTexture] Can't create texture from 'src': file data is zero length`);\n return;\n }\n this._textureTranscoder.transcode([arrayBuffer], texture).then(() => {\n this.glRedraw();\n });\n },\n function (errMsg) {\n this.error(`[createTexture] Can't create texture from 'src': ${errMsg}`);\n });\n }\n break;\n }\n } else if (cfg.buffers) { // Buffers implicitly require transcoding\n if (!this._textureTranscoder) {\n this.error(`[createTexture] Can't create texture from 'buffers' - SceneModel needs to be configured with a TextureTranscoder for this option`);\n } else {\n this._textureTranscoder.transcode(cfg.buffers, texture).then(() => {\n this.glRedraw();\n });\n }\n }\n this._textures[textureId] = new SceneModelTexture({id: textureId, texture});\n }\n\n /**\n * Creates a texture set within this SceneModel.\n *\n * * Stores the new {@link SceneModelTextureSet} in {@link SceneModel#textureSets}.\n *\n * A texture set is a collection of textures that can be shared among meshes. We can then supply the texture set\n * ID to {@link SceneModel#createMesh} when we want to create meshes that use the texture set.\n *\n * The textures can work as a texture atlas, where each mesh can have geometry UVs that index\n * a different part of the textures. This allows us to minimize the number of textures in our models, which\n * means faster rendering.\n *\n * @param {*} cfg Texture set properties.\n * @param {String|Number} cfg.id Mandatory ID for the texture set, to refer to with {@link SceneModel#createMesh}.\n * @param {*} [cfg.colorTextureId] ID of *RGBA* base color texture, with color in *RGB* and alpha in *A*.\n * @param {*} [cfg.metallicRoughnessTextureId] ID of *RGBA* metal-roughness texture, with the metallic factor in *R*, and roughness factor in *G*.\n * @param {*} [cfg.normalsTextureId] ID of *RGBA* normal map texture, with normal map vectors in *RGB*.\n * @param {*} [cfg.emissiveTextureId] ID of *RGBA* emissive map texture, with emissive color in *RGB*.\n * @param {*} [cfg.occlusionTextureId] ID of *RGBA* occlusion map texture, with occlusion factor in *R*.\n * @returns {SceneModelTransform} The new texture set.\n */\n createTextureSet(cfg) {\n const textureSetId = cfg.id;\n if (textureSetId === undefined || textureSetId === null) {\n this.error(\"[createTextureSet] Config missing: id\");\n return;\n }\n if (this._textureSets[textureSetId]) {\n this.error(`[createTextureSet] Texture set already created: ${textureSetId}`);\n return;\n }\n let colorTexture;\n if (cfg.colorTextureId !== undefined && cfg.colorTextureId !== null) {\n colorTexture = this._textures[cfg.colorTextureId];\n if (!colorTexture) {\n this.error(`[createTextureSet] Texture not found: ${cfg.colorTextureId} - ensure that you create it first with createTexture()`);\n return;\n }\n } else {\n colorTexture = this._textures[DEFAULT_COLOR_TEXTURE_ID];\n }\n let metallicRoughnessTexture;\n if (cfg.metallicRoughnessTextureId !== undefined && cfg.metallicRoughnessTextureId !== null) {\n metallicRoughnessTexture = this._textures[cfg.metallicRoughnessTextureId];\n if (!metallicRoughnessTexture) {\n this.error(`[createTextureSet] Texture not found: ${cfg.metallicRoughnessTextureId} - ensure that you create it first with createTexture()`);\n return;\n }\n } else {\n metallicRoughnessTexture = this._textures[DEFAULT_METAL_ROUGH_TEXTURE_ID];\n }\n let normalsTexture;\n if (cfg.normalsTextureId !== undefined && cfg.normalsTextureId !== null) {\n normalsTexture = this._textures[cfg.normalsTextureId];\n if (!normalsTexture) {\n this.error(`[createTextureSet] Texture not found: ${cfg.normalsTextureId} - ensure that you create it first with createTexture()`);\n return;\n }\n } else {\n normalsTexture = this._textures[DEFAULT_NORMALS_TEXTURE_ID];\n }\n let emissiveTexture;\n if (cfg.emissiveTextureId !== undefined && cfg.emissiveTextureId !== null) {\n emissiveTexture = this._textures[cfg.emissiveTextureId];\n if (!emissiveTexture) {\n this.error(`[createTextureSet] Texture not found: ${cfg.emissiveTextureId} - ensure that you create it first with createTexture()`);\n return;\n }\n } else {\n emissiveTexture = this._textures[DEFAULT_EMISSIVE_TEXTURE_ID];\n }\n let occlusionTexture;\n if (cfg.occlusionTextureId !== undefined && cfg.occlusionTextureId !== null) {\n occlusionTexture = this._textures[cfg.occlusionTextureId];\n if (!occlusionTexture) {\n this.error(`[createTextureSet] Texture not found: ${cfg.occlusionTextureId} - ensure that you create it first with createTexture()`);\n return;\n }\n } else {\n occlusionTexture = this._textures[DEFAULT_OCCLUSION_TEXTURE_ID];\n }\n const textureSet = new SceneModelTextureSet({\n id: textureSetId,\n model: this,\n colorTexture,\n metallicRoughnessTexture,\n normalsTexture,\n emissiveTexture,\n occlusionTexture\n });\n this._textureSets[textureSetId] = textureSet;\n return textureSet;\n }\n\n /**\n * Creates a new {@link SceneModelTransform} within this SceneModel.\n *\n * * Stores the new {@link SceneModelTransform} in {@link SceneModel#transforms}.\n * * Can be connected into hierarchies\n * * Each {@link SceneModelTransform} can be used by unlimited {@link SceneModelMesh}es\n *\n * @param {*} cfg Transform creation parameters.\n * @param {String} cfg.id Mandatory ID for the new transform. Must not clash with any existing components within the {@link Scene}.\n * @param {String} [cfg.parentTransformId] ID of a parent transform, previously created with {@link SceneModel#createTextureSet}.\n * @param {Number[]} [cfg.position=[0,0,0]] Local 3D position of the mesh. Overridden by ````transformId````.\n * @param {Number[]} [cfg.scale=[1,1,1]] Scale of the transform.\n * @param {Number[]} [cfg.rotation=[0,0,0]] Rotation of the transform as Euler angles given in degrees, for each of the X, Y and Z axis.\n * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] Modelling transform matrix. Overrides the ````position````, ````scale```` and ````rotation```` parameters.\n * @returns {SceneModelTransform} The new transform.\n */\n createTransform(cfg) {\n if (cfg.id === undefined || cfg.id === null) {\n this.error(\"[createTransform] SceneModel.createTransform() config missing: id\");\n return;\n }\n if (this._transforms[cfg.id]) {\n this.error(`[createTransform] SceneModel already has a transform with this ID: ${cfg.id}`);\n return;\n }\n let parentTransform;\n if (cfg.parentTransformId) {\n parentTransform = this._transforms[cfg.parentTransformId];\n if (!parentTransform) {\n this.error(\"[createTransform] SceneModel.createTransform() config missing: id\");\n return;\n }\n }\n const transform = new SceneModelTransform({\n id: cfg.id,\n model: this,\n parent: parentTransform,\n matrix: cfg.matrix,\n position: cfg.position,\n scale: cfg.scale,\n rotation: cfg.rotation,\n quaternion: cfg.quaternion\n });\n this._transforms[transform.id] = transform;\n return transform;\n }\n\n /**\n * Creates a new {@link SceneModelMesh} within this SceneModel.\n *\n * * It prepares and saves data for a SceneModelMesh {@link SceneModel#meshes} creation. SceneModelMesh will be created only once the SceneModelEntity (which references this particular SceneModelMesh) will be created.\n * * The SceneModelMesh can either define its own geometry or share it with other SceneModelMeshes. To define own geometry, provide the\n * various geometry arrays to this method. To share a geometry, provide the ID of a geometry created earlier\n * with {@link SceneModel#createGeometry}.\n * * If you accompany the arrays with an ````origin````, then ````createMesh()```` will assume\n * that the geometry ````positions```` are in relative-to-center (RTC) coordinates, with ````origin```` being the\n * origin of their RTC coordinate system.\n *\n * @param {object} cfg Object properties.\n * @param {String} cfg.id Mandatory ID for the new mesh. Must not clash with any existing components within the {@link Scene}.\n * @param {String|Number} [cfg.textureSetId] ID of a {@link SceneModelTextureSet} previously created with {@link SceneModel#createTextureSet}.\n * @param {String|Number} [cfg.transformId] ID of a {@link SceneModelTransform} to instance, previously created with {@link SceneModel#createTransform}. Overrides all other transform parameters given to this method.\n * @param {String|Number} [cfg.geometryId] ID of a geometry to instance, previously created with {@link SceneModel#createGeometry}. Overrides all other geometry parameters given to this method.\n * @param {String} cfg.primitive The primitive type. Accepted values are 'points', 'lines', 'triangles', 'solid' and 'surface'.\n * @param {Number[]} [cfg.positions] Flat array of uncompressed 3D vertex positions positions. Required for all primitive types. Overridden by ````positionsCompressed````.\n * @param {Number[]} [cfg.positionsCompressed] Flat array of quantized 3D vertex positions. Overrides ````positions````, and must be accompanied by ````positionsDecodeMatrix````.\n * @param {Number[]} [cfg.positionsDecodeMatrix] A 4x4 matrix for decompressing ````positionsCompressed````. Must be accompanied by ````positionsCompressed````.\n * @param {Number[]} [cfg.normals] Flat array of normal vectors. Only used with \"triangles\", \"solid\" and \"surface\" primitives. When no normals are given, the geometry will be flat shaded using auto-generated face-aligned normals.\n * @param {Number[]} [cfg.normalsCompressed] Flat array of oct-encoded normal vectors. Overrides ````normals````. Only used with \"triangles\", \"solid\" and \"surface\" primitives. When no normals are given, the geometry will be flat shaded using auto-generated face-aligned normals.\n * @param {Number[]} [cfg.colors] Flat array of uncompressed RGBA vertex colors, as float values in range ````[0..1]````. Ignored when ````geometryId```` is given. Overridden by ````color```` and ````colorsCompressed````.\n * @param {Number[]} [cfg.colorsCompressed] Flat array of compressed RGBA vertex colors, as unsigned short integers in range ````[0..255]````. Ignored when ````geometryId```` is given. Overrides ````colors```` and is overridden by ````color````.\n * @param {Number[]} [cfg.uv] Flat array of uncompressed vertex UV coordinates. Only used with \"triangles\", \"solid\" and \"surface\" primitives. Required for textured rendering.\n * @param {Number[]} [cfg.uvCompressed] Flat array of compressed vertex UV coordinates. Only used with \"triangles\", \"solid\" and \"surface\" primitives. Overrides ````uv````. Must be accompanied by ````uvDecodeMatrix````. Only used with \"triangles\", \"solid\" and \"surface\" primitives. Required for textured rendering.\n * @param {Number[]} [cfg.uvDecodeMatrix] A 3x3 matrix for decompressing ````uvCompressed````.\n * @param {Number[]} [cfg.indices] Array of primitive connectivity indices. Not required for `points` primitives.\n * @param {Number[]} [cfg.edgeIndices] Array of edge line indices. Used only with 'triangles', 'solid' and 'surface' primitives. Automatically generated internally if not supplied, using the optional ````edgeThreshold```` given to the ````SceneModel```` constructor.\n * @param {Number[]} [cfg.origin] Optional geometry origin, relative to {@link SceneModel#origin}. When this is given, then ````positions```` are assumed to be relative to this.\n * @param {Number[]} [cfg.position=[0,0,0]] Local 3D position of the mesh. Overridden by ````transformId````.\n * @param {Number[]} [cfg.scale=[1,1,1]] Scale of the mesh. Overridden by ````transformId````.\n * @param {Number[]} [cfg.rotation=[0,0,0]] Rotation of the mesh as Euler angles given in degrees, for each of the X, Y and Z axis. Overridden by ````transformId````.\n * @param {Number[]} [cfg.quaternion] Rotation of the mesh as a quaternion. Overridden by ````rotation````.\n * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] Mesh modelling transform matrix. Overrides the ````position````, ````scale```` and ````rotation```` parameters. Also overridden by ````transformId````.\n * @param {Number[]} [cfg.color=[1,1,1]] RGB color in range ````[0..1, 0..1, 0..1]````. Overridden by texture set ````colorTexture````. Overrides ````colors```` and ````colorsCompressed````.\n * @param {Number} [cfg.opacity=1] Opacity in range ````[0..1]````. Overridden by texture set ````colorTexture````.\n * @param {Number} [cfg.metallic=0] Metallic factor in range ````[0..1]````. Overridden by texture set ````metallicRoughnessTexture````.\n * @param {Number} [cfg.roughness=1] Roughness factor in range ````[0..1]````. Overridden by texture set ````metallicRoughnessTexture````.\n * @returns {SceneModelMesh} The new mesh.\n */\n createMesh(cfg) {\n\n if (cfg.id === undefined || cfg.id === null) {\n this.error(\"[createMesh] SceneModel.createMesh() config missing: id\");\n return false;\n }\n\n if (this._meshes[cfg.id]) {\n this.error(`[createMesh] SceneModel already has a mesh with this ID: ${cfg.id}`);\n return false;\n }\n\n const instancing = (cfg.geometryId !== undefined);\n const batching = !instancing;\n\n if (batching) {\n\n // Batched geometry\n\n if (cfg.primitive === undefined || cfg.primitive === null) {\n cfg.primitive = \"triangles\";\n }\n if (cfg.primitive !== \"points\" && cfg.primitive !== \"lines\" && cfg.primitive !== \"triangles\" && cfg.primitive !== \"solid\" && cfg.primitive !== \"surface\") {\n this.error(`Unsupported value for 'primitive': '${primitive}' ('geometryId' is absent) - supported values are 'points', 'lines', 'triangles', 'solid' and 'surface'.`);\n return false;\n }\n if (!cfg.positions && !cfg.positionsCompressed && !cfg.buckets) {\n this.error(\"Param expected: 'positions', 'positionsCompressed' or `buckets` ('geometryId' is absent)\");\n return false;\n }\n if (cfg.positions && (cfg.positionsDecodeMatrix || cfg.positionsDecodeBoundary)) {\n this.error(\"Illegal params: 'positions' not expected with 'positionsDecodeMatrix'/'positionsDecodeBoundary' ('geometryId' is absent)\");\n return false;\n }\n if (cfg.positionsCompressed && !cfg.positionsDecodeMatrix && !cfg.positionsDecodeBoundary) {\n this.error(\"Param expected: 'positionsCompressed' should be accompanied by 'positionsDecodeMatrix'/'positionsDecodeBoundary' ('geometryId' is absent)\");\n return false;\n }\n if (cfg.uvCompressed && !cfg.uvDecodeMatrix) {\n this.error(\"Param expected: 'uvCompressed' should be accompanied by `uvDecodeMatrix` ('geometryId' is absent)\");\n return false;\n }\n if (!cfg.buckets && !cfg.indices && (cfg.primitive === \"triangles\" || cfg.primitive === \"solid\" || cfg.primitive === \"surface\")) {\n const numPositions = (cfg.positions || cfg.positionsCompressed).length / 3;\n cfg.indices = this._createDefaultIndices(numPositions);\n }\n if (!cfg.buckets && !cfg.indices && cfg.primitive !== \"points\") {\n cfg.indices = this._createDefaultIndices(numIndices);\n this.error(`Param expected: indices (required for '${cfg.primitive}' primitive type)`);\n return false;\n }\n if ((cfg.matrix || cfg.position || cfg.rotation || cfg.scale) && (cfg.positionsCompressed || cfg.positionsDecodeBoundary)) {\n this.error(\"Unexpected params: 'matrix', 'rotation', 'scale', 'position' not allowed with 'positionsCompressed'\");\n return false;\n }\n\n const useDTX = (!!this._dtxEnabled && (cfg.primitive === \"triangles\"\n || cfg.primitive === \"solid\"\n || cfg.primitive === \"surface\"))\n && (!cfg.textureSetId);\n\n cfg.origin = cfg.origin ? math.addVec3(this._origin, cfg.origin, math.vec3()) : this._origin;\n\n // MATRIX - optional for batching\n\n if (cfg.matrix) {\n cfg.meshMatrix = cfg.matrix;\n } else if (cfg.scale || cfg.rotation || cfg.position || cfg.quaternion) {\n const scale = cfg.scale || DEFAULT_SCALE;\n const position = cfg.position || DEFAULT_POSITION;\n if (cfg.rotation) {\n math.eulerToQuaternion(cfg.rotation, \"XYZ\", tempQuaternion);\n cfg.meshMatrix = math.composeMat4(position, tempQuaternion, scale, math.mat4());\n } else {\n cfg.meshMatrix = math.composeMat4(position, cfg.quaternion || DEFAULT_QUATERNION, scale, math.mat4());\n }\n }\n\n if (cfg.positionsDecodeBoundary) {\n cfg.positionsDecodeMatrix = createPositionsDecodeMatrix(cfg.positionsDecodeBoundary, math.mat4());\n }\n\n if (useDTX) {\n\n // DTX\n\n cfg.type = DTX;\n\n // NPR\n\n cfg.color = (cfg.color) ? new Uint8Array([Math.floor(cfg.color[0] * 255), Math.floor(cfg.color[1] * 255), Math.floor(cfg.color[2] * 255)]) : defaultCompressedColor;\n cfg.opacity = (cfg.opacity !== undefined && cfg.opacity !== null) ? Math.floor(cfg.opacity * 255) : 255;\n\n // RTC\n\n if (cfg.positions) {\n const rtcCenter = math.vec3();\n const rtcPositions = [];\n const rtcNeeded = worldToRTCPositions(cfg.positions, rtcPositions, rtcCenter);\n if (rtcNeeded) {\n cfg.positions = rtcPositions;\n cfg.origin = math.addVec3(cfg.origin, rtcCenter, rtcCenter);\n }\n }\n\n // COMPRESSION\n\n if (cfg.positions) {\n const aabb = math.collapseAABB3();\n cfg.positionsDecodeMatrix = math.mat4();\n math.expandAABB3Points3(aabb, cfg.positions);\n cfg.positionsCompressed = quantizePositions(cfg.positions, aabb, cfg.positionsDecodeMatrix);\n cfg.aabb = aabb;\n\n } else if (cfg.positionsCompressed) {\n const aabb = math.collapseAABB3();\n math.expandAABB3Points3(aabb, cfg.positionsCompressed);\n geometryCompressionUtils.decompressAABB(aabb, cfg.positionsDecodeMatrix);\n cfg.aabb = aabb;\n\n }\n if (cfg.buckets) {\n const aabb = math.collapseAABB3();\n for (let i = 0, len = cfg.buckets.length; i < len; i++) {\n const bucket = cfg.buckets[i];\n if (bucket.positions) {\n math.expandAABB3Points3(aabb, bucket.positions);\n } else if (bucket.positionsCompressed) {\n math.expandAABB3Points3(aabb, bucket.positionsCompressed);\n }\n }\n if (cfg.positionsDecodeMatrix) {\n geometryCompressionUtils.decompressAABB(aabb, cfg.positionsDecodeMatrix);\n }\n cfg.aabb = aabb;\n }\n\n if (cfg.meshMatrix) {\n math.AABB3ToOBB3(cfg.aabb, tempOBB3);\n math.transformOBB3(cfg.meshMatrix, tempOBB3);\n math.OBB3ToAABB3(tempOBB3, cfg.aabb);\n }\n\n // EDGES\n\n if (!cfg.buckets && !cfg.edgeIndices && (cfg.primitive === \"triangles\" || cfg.primitive === \"solid\" || cfg.primitive === \"surface\")) {\n if (cfg.positions) { // Faster\n cfg.edgeIndices = buildEdgeIndices(cfg.positions, cfg.indices, null, 2.0);\n } else {\n cfg.edgeIndices = buildEdgeIndices(cfg.positionsCompressed, cfg.indices, cfg.positionsDecodeMatrix, 2.0);\n }\n }\n\n // BUCKETING\n\n if (!cfg.buckets) {\n cfg.buckets = createDTXBuckets(cfg, this._enableVertexWelding && this._enableIndexBucketing);\n }\n\n } else {\n\n // VBO\n\n cfg.type = VBO_BATCHED;\n\n // PBR\n\n cfg.color = (cfg.color) ? new Uint8Array([Math.floor(cfg.color[0] * 255), Math.floor(cfg.color[1] * 255), Math.floor(cfg.color[2] * 255)]) : [255, 255, 255];\n cfg.opacity = (cfg.opacity !== undefined && cfg.opacity !== null) ? Math.floor(cfg.opacity * 255) : 255;\n cfg.metallic = (cfg.metallic !== undefined && cfg.metallic !== null) ? Math.floor(cfg.metallic * 255) : 0;\n cfg.roughness = (cfg.roughness !== undefined && cfg.roughness !== null) ? Math.floor(cfg.roughness * 255) : 255;\n\n // RTC\n\n if (cfg.positions) {\n const rtcPositions = [];\n const rtcNeeded = worldToRTCPositions(cfg.positions, rtcPositions, tempVec3a$a);\n if (rtcNeeded) {\n cfg.positions = rtcPositions;\n cfg.origin = math.addVec3(cfg.origin, tempVec3a$a, math.vec3());\n }\n }\n\n if (cfg.positions) {\n const aabb = math.collapseAABB3();\n if (cfg.meshMatrix) {\n math.transformPositions3(cfg.meshMatrix, cfg.positions, cfg.positions);\n cfg.meshMatrix = null; // Positions now baked, don't need any more\n }\n math.expandAABB3Points3(aabb, cfg.positions);\n cfg.aabb = aabb;\n\n } else {\n const aabb = math.collapseAABB3();\n math.expandAABB3Points3(aabb, cfg.positionsCompressed);\n geometryCompressionUtils.decompressAABB(aabb, cfg.positionsDecodeMatrix);\n cfg.aabb = aabb;\n }\n\n if (cfg.meshMatrix) {\n math.AABB3ToOBB3(cfg.aabb, tempOBB3);\n math.transformOBB3(cfg.meshMatrix, tempOBB3);\n math.OBB3ToAABB3(tempOBB3, cfg.aabb);\n }\n\n // EDGES\n\n if (!cfg.buckets && !cfg.edgeIndices && (cfg.primitive === \"triangles\" || cfg.primitive === \"solid\" || cfg.primitive === \"surface\")) {\n if (cfg.positions) {\n cfg.edgeIndices = buildEdgeIndices(cfg.positions, cfg.indices, null, 2.0);\n } else {\n cfg.edgeIndices = buildEdgeIndices(cfg.positionsCompressed, cfg.indices, cfg.positionsDecodeMatrix, 2.0);\n }\n }\n\n // TEXTURE\n\n // cfg.textureSetId = cfg.textureSetId || DEFAULT_TEXTURE_SET_ID;\n if (cfg.textureSetId) {\n cfg.textureSet = this._textureSets[cfg.textureSetId];\n if (!cfg.textureSet) {\n this.error(`[createMesh] Texture set not found: ${cfg.textureSetId} - ensure that you create it first with createTextureSet()`);\n return false;\n }\n }\n }\n\n } else {\n\n // INSTANCING\n\n if (cfg.positions || cfg.positionsCompressed || cfg.indices || cfg.edgeIndices || cfg.normals || cfg.normalsCompressed || cfg.uv || cfg.uvCompressed || cfg.positionsDecodeMatrix) {\n this.error(`Mesh geometry parameters not expected when instancing a geometry (not expected: positions, positionsCompressed, indices, edgeIndices, normals, normalsCompressed, uv, uvCompressed, positionsDecodeMatrix)`);\n return false;\n }\n\n cfg.geometry = this._geometries[cfg.geometryId];\n if (!cfg.geometry) {\n this.error(`[createMesh] Geometry not found: ${cfg.geometryId} - ensure that you create it first with createGeometry()`);\n return false;\n }\n\n cfg.origin = cfg.origin ? math.addVec3(this._origin, cfg.origin, math.vec3()) : this._origin;\n cfg.positionsDecodeMatrix = cfg.geometry.positionsDecodeMatrix;\n\n if (cfg.transformId) {\n\n // TRANSFORM\n\n cfg.transform = this._transforms[cfg.transformId];\n\n if (!cfg.transform) {\n this.error(`[createMesh] Transform not found: ${cfg.transformId} - ensure that you create it first with createTransform()`);\n return false;\n }\n\n cfg.aabb = cfg.geometry.aabb;\n\n } else {\n\n // MATRIX\n\n if (cfg.matrix) {\n cfg.meshMatrix = cfg.matrix;\n } else if (cfg.scale || cfg.rotation || cfg.position || cfg.quaternion) {\n const scale = cfg.scale || DEFAULT_SCALE;\n const position = cfg.position || DEFAULT_POSITION;\n if (cfg.rotation) {\n math.eulerToQuaternion(cfg.rotation, \"XYZ\", tempQuaternion);\n cfg.meshMatrix = math.composeMat4(position, tempQuaternion, scale, math.mat4());\n } else {\n cfg.meshMatrix = math.composeMat4(position, cfg.quaternion || DEFAULT_QUATERNION, scale, math.mat4());\n }\n }\n\n math.AABB3ToOBB3(cfg.geometry.aabb, tempOBB3);\n math.transformOBB3(cfg.meshMatrix, tempOBB3);\n cfg.aabb = math.OBB3ToAABB3(tempOBB3, math.AABB3());\n }\n\n const useDTX = (!!this._dtxEnabled\n && (cfg.geometry.primitive === \"triangles\"\n || cfg.geometry.primitive === \"solid\"\n || cfg.geometry.primitive === \"surface\"))\n && (!cfg.textureSetId);\n\n if (useDTX) {\n\n // DTX\n\n cfg.type = DTX;\n\n // NPR\n\n cfg.color = (cfg.color) ? new Uint8Array([Math.floor(cfg.color[0] * 255), Math.floor(cfg.color[1] * 255), Math.floor(cfg.color[2] * 255)]) : defaultCompressedColor;\n cfg.opacity = (cfg.opacity !== undefined && cfg.opacity !== null) ? Math.floor(cfg.opacity * 255) : 255;\n\n // BUCKETING - lazy generated, reused\n\n let buckets = this._dtxBuckets[cfg.geometryId];\n if (!buckets) {\n buckets = createDTXBuckets(cfg.geometry, this._enableVertexWelding, this._enableIndexBucketing);\n this._dtxBuckets[cfg.geometryId] = buckets;\n }\n cfg.buckets = buckets;\n\n } else {\n\n // VBO\n\n cfg.type = VBO_INSTANCED;\n\n // PBR\n\n cfg.color = (cfg.color) ? new Uint8Array([Math.floor(cfg.color[0] * 255), Math.floor(cfg.color[1] * 255), Math.floor(cfg.color[2] * 255)]) : defaultCompressedColor;\n cfg.opacity = (cfg.opacity !== undefined && cfg.opacity !== null) ? Math.floor(cfg.opacity * 255) : 255;\n cfg.metallic = (cfg.metallic !== undefined && cfg.metallic !== null) ? Math.floor(cfg.metallic * 255) : 0;\n cfg.roughness = (cfg.roughness !== undefined && cfg.roughness !== null) ? Math.floor(cfg.roughness * 255) : 255;\n\n // TEXTURE\n\n if (cfg.textureSetId) {\n cfg.textureSet = this._textureSets[cfg.textureSetId];\n // if (!cfg.textureSet) {\n // this.error(`[createMesh] Texture set not found: ${cfg.textureSetId} - ensure that you create it first with createTextureSet()`);\n // return false;\n // }\n }\n }\n }\n\n cfg.numPrimitives = this._getNumPrimitives(cfg);\n\n return this._createMesh(cfg);\n }\n\n _createMesh(cfg) {\n const mesh = new SceneModelMesh(this, cfg.id, cfg.color, cfg.opacity, cfg.transform, cfg.textureSet);\n mesh.pickId = this.scene._renderer.getPickID(mesh);\n const pickId = mesh.pickId;\n const a = pickId >> 24 & 0xFF;\n const b = pickId >> 16 & 0xFF;\n const g = pickId >> 8 & 0xFF;\n const r = pickId & 0xFF;\n cfg.pickColor = new Uint8Array([r, g, b, a]); // Quantized pick color\n cfg.solid = (cfg.primitive === \"solid\");\n mesh.origin = math.vec3(cfg.origin);\n switch (cfg.type) {\n case DTX:\n mesh.layer = this._getDTXLayer(cfg);\n mesh.aabb = cfg.aabb;\n break;\n case VBO_BATCHED:\n mesh.layer = this._getVBOBatchingLayer(cfg);\n mesh.aabb = cfg.aabb;\n break;\n case VBO_INSTANCED:\n mesh.layer = this._getVBOInstancingLayer(cfg);\n mesh.aabb = cfg.aabb;\n break;\n }\n if (cfg.transform) {\n cfg.meshMatrix = cfg.transform.worldMatrix;\n }\n mesh.portionId = mesh.layer.createPortion(mesh, cfg);\n this._meshes[cfg.id] = mesh;\n this._unusedMeshes[cfg.id] = mesh;\n this._meshList.push(mesh);\n return mesh;\n }\n\n _getNumPrimitives(cfg) {\n let countIndices = 0;\n const primitive = cfg.geometry ? cfg.geometry.primitive : cfg.primitive;\n switch (primitive) {\n case \"triangles\":\n case \"solid\":\n case \"surface\":\n switch (cfg.type) {\n case DTX:\n for (let i = 0, len = cfg.buckets.length; i < len; i++) {\n countIndices += cfg.buckets[i].indices.length;\n }\n break;\n case VBO_BATCHED:\n countIndices += cfg.indices.length;\n break;\n case VBO_INSTANCED:\n countIndices += cfg.geometry.indices.length;\n break;\n }\n return Math.round(countIndices / 3);\n case \"points\":\n switch (cfg.type) {\n case DTX:\n for (let i = 0, len = cfg.buckets.length; i < len; i++) {\n countIndices += cfg.buckets[i].positionsCompressed.length;\n }\n break;\n case VBO_BATCHED:\n countIndices += cfg.positions ? cfg.positions.length : cfg.positionsCompressed.length;\n break;\n case VBO_INSTANCED:\n const geometry = cfg.geometry;\n countIndices += geometry.positions ? geometry.positions.length : geometry.positionsCompressed.length;\n break;\n }\n return Math.round(countIndices);\n case \"lines\":\n case \"line-strip\":\n switch (cfg.type) {\n case DTX:\n for (let i = 0, len = cfg.buckets.length; i < len; i++) {\n countIndices += cfg.buckets[i].indices.length;\n }\n break;\n case VBO_BATCHED:\n countIndices += cfg.indices.length;\n break;\n case VBO_INSTANCED:\n countIndices += cfg.geometry.indices.length;\n break;\n }\n return Math.round(countIndices / 2);\n }\n return 0;\n }\n\n _getDTXLayer(cfg) {\n const origin = cfg.origin;\n const primitive = cfg.geometry ? cfg.geometry.primitive : cfg.primitive;\n const layerId = `.${primitive}.${Math.round(origin[0])}.${Math.round(origin[1])}.${Math.round(origin[2])}`;\n let dtxLayer = this._dtxLayers[layerId];\n if (dtxLayer) {\n if (!dtxLayer.canCreatePortion(cfg)) {\n dtxLayer.finalize();\n delete this._dtxLayers[layerId];\n dtxLayer = null;\n } else {\n return dtxLayer;\n }\n }\n switch (primitive) {\n case \"triangles\":\n case \"solid\":\n case \"surface\":\n dtxLayer = new DTXTrianglesLayer(this, {layerIndex: 0, origin}); // layerIndex is set in #finalize()\n break;\n case \"lines\":\n dtxLayer = new DTXLinesLayer(this, {layerIndex: 0, origin}); // layerIndex is set in #finalize()\n break;\n default:\n return;\n }\n this._dtxLayers[layerId] = dtxLayer;\n this.layerList.push(dtxLayer);\n return dtxLayer;\n }\n\n _getVBOBatchingLayer(cfg) {\n const model = this;\n const origin = cfg.origin;\n const positionsDecodeHash = cfg.positionsDecodeMatrix || cfg.positionsDecodeBoundary ?\n this._createHashStringFromMatrix(cfg.positionsDecodeMatrix || cfg.positionsDecodeBoundary)\n : \"-\";\n const textureSetId = cfg.textureSetId || \"-\";\n const layerId = `${Math.round(origin[0])}.${Math.round(origin[1])}.${Math.round(origin[2])}.${cfg.primitive}.${positionsDecodeHash}.${textureSetId}`;\n let vboBatchingLayer = this._vboBatchingLayers[layerId];\n if (vboBatchingLayer) {\n return vboBatchingLayer;\n }\n let textureSet = cfg.textureSet;\n while (!vboBatchingLayer) {\n switch (cfg.primitive) {\n case \"triangles\":\n // console.info(`[SceneModel ${this.id}]: creating TrianglesBatchingLayer`);\n vboBatchingLayer = new VBOBatchingTrianglesLayer({\n model,\n textureSet,\n layerIndex: 0, // This is set in #finalize()\n scratchMemory: this._vboBatchingLayerScratchMemory,\n positionsDecodeMatrix: cfg.positionsDecodeMatrix, // Can be undefined\n uvDecodeMatrix: cfg.uvDecodeMatrix, // Can be undefined\n origin,\n maxGeometryBatchSize: this._maxGeometryBatchSize,\n solid: (cfg.primitive === \"solid\"),\n autoNormals: true\n });\n break;\n case \"solid\":\n // console.info(`[SceneModel ${this.id}]: creating TrianglesBatchingLayer`);\n vboBatchingLayer = new VBOBatchingTrianglesLayer({\n model,\n textureSet,\n layerIndex: 0, // This is set in #finalize()\n scratchMemory: this._vboBatchingLayerScratchMemory,\n positionsDecodeMatrix: cfg.positionsDecodeMatrix, // Can be undefined\n uvDecodeMatrix: cfg.uvDecodeMatrix, // Can be undefined\n origin,\n maxGeometryBatchSize: this._maxGeometryBatchSize,\n solid: (cfg.primitive === \"solid\"),\n autoNormals: true\n });\n break;\n case \"surface\":\n // console.info(`[SceneModel ${this.id}]: creating TrianglesBatchingLayer`);\n vboBatchingLayer = new VBOBatchingTrianglesLayer({\n model,\n textureSet,\n layerIndex: 0, // This is set in #finalize()\n scratchMemory: this._vboBatchingLayerScratchMemory,\n positionsDecodeMatrix: cfg.positionsDecodeMatrix, // Can be undefined\n uvDecodeMatrix: cfg.uvDecodeMatrix, // Can be undefined\n origin,\n maxGeometryBatchSize: this._maxGeometryBatchSize,\n solid: (cfg.primitive === \"solid\"),\n autoNormals: true\n });\n break;\n case \"lines\":\n // console.info(`[SceneModel ${this.id}]: creating VBOBatchingLinesLayer`);\n vboBatchingLayer = new VBOBatchingLinesLayer({\n model,\n layerIndex: 0, // This is set in #finalize()\n scratchMemory: this._vboBatchingLayerScratchMemory,\n positionsDecodeMatrix: cfg.positionsDecodeMatrix, // Can be undefined\n uvDecodeMatrix: cfg.uvDecodeMatrix, // Can be undefined\n origin,\n maxGeometryBatchSize: this._maxGeometryBatchSize\n });\n break;\n case \"points\":\n // console.info(`[SceneModel ${this.id}]: creating VBOBatchingPointsLayer`);\n vboBatchingLayer = new VBOBatchingPointsLayer({\n model,\n layerIndex: 0, // This is set in #finalize()\n scratchMemory: this._vboBatchingLayerScratchMemory,\n positionsDecodeMatrix: cfg.positionsDecodeMatrix, // Can be undefined\n uvDecodeMatrix: cfg.uvDecodeMatrix, // Can be undefined\n origin,\n maxGeometryBatchSize: this._maxGeometryBatchSize\n });\n break;\n }\n const lenPositions = cfg.positionsCompressed ? cfg.positionsCompressed.length : cfg.positions.length;\n const canCreatePortion = (cfg.primitive === \"points\")\n ? vboBatchingLayer.canCreatePortion(lenPositions)\n : vboBatchingLayer.canCreatePortion(lenPositions, cfg.indices.length);\n if (!canCreatePortion) {\n vboBatchingLayer.finalize();\n delete this._vboBatchingLayers[layerId];\n vboBatchingLayer = null;\n }\n }\n this._vboBatchingLayers[layerId] = vboBatchingLayer;\n this.layerList.push(vboBatchingLayer);\n return vboBatchingLayer;\n }\n\n _createHashStringFromMatrix(matrix) {\n const matrixString = matrix.join('');\n let hash = 0;\n for (let i = 0; i < matrixString.length; i++) {\n const char = matrixString.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash |= 0; // Convert to 32-bit integer\n }\n const hashString = (hash >>> 0).toString(16);\n return hashString;\n }\n\n _getVBOInstancingLayer(cfg) {\n const model = this;\n const origin = cfg.origin;\n const textureSetId = cfg.textureSetId || \"-\";\n const geometryId = cfg.geometryId;\n const layerId = `${Math.round(origin[0])}.${Math.round(origin[1])}.${Math.round(origin[2])}.${textureSetId}.${geometryId}`;\n let vboInstancingLayer = this._vboInstancingLayers[layerId];\n if (vboInstancingLayer) {\n return vboInstancingLayer;\n }\n let textureSet = cfg.textureSet;\n const geometry = cfg.geometry;\n while (!vboInstancingLayer) {\n switch (geometry.primitive) {\n case \"triangles\":\n // console.info(`[SceneModel ${this.id}]: creating TrianglesInstancingLayer`);\n vboInstancingLayer = new VBOInstancingTrianglesLayer({\n model,\n textureSet,\n geometry,\n origin,\n layerIndex: 0,\n solid: false\n });\n break;\n case \"solid\":\n // console.info(`[SceneModel ${this.id}]: creating TrianglesInstancingLayer`);\n vboInstancingLayer = new VBOInstancingTrianglesLayer({\n model,\n textureSet,\n geometry,\n origin,\n layerIndex: 0,\n solid: true\n });\n break;\n case \"surface\":\n // console.info(`[SceneModel ${this.id}]: creating TrianglesInstancingLayer`);\n vboInstancingLayer = new VBOInstancingTrianglesLayer({\n model,\n textureSet,\n geometry,\n origin,\n layerIndex: 0,\n solid: false\n });\n break;\n case \"lines\":\n // console.info(`[SceneModel ${this.id}]: creating VBOInstancingLinesLayer`);\n vboInstancingLayer = new VBOInstancingLinesLayer({\n model,\n textureSet,\n geometry,\n origin,\n layerIndex: 0\n });\n break;\n case \"points\":\n // console.info(`[SceneModel ${this.id}]: creating PointsInstancingLayer`);\n vboInstancingLayer = new VBOInstancingPointsLayer({\n model,\n textureSet,\n geometry,\n origin,\n layerIndex: 0\n });\n break;\n }\n // const lenPositions = geometry.positionsCompressed.length;\n // if (!vboInstancingLayer.canCreatePortion(lenPositions, geometry.indices.length)) { // FIXME: indices should be optional\n // vboInstancingLayer.finalize();\n // delete this._vboInstancingLayers[layerId];\n // vboInstancingLayer = null;\n // }\n }\n this._vboInstancingLayers[layerId] = vboInstancingLayer;\n this.layerList.push(vboInstancingLayer);\n return vboInstancingLayer;\n }\n\n /**\n * Creates a {@link SceneModelEntity} within this SceneModel.\n *\n * * Gives the SceneModelEntity one or more {@link SceneModelMesh}es previously created with\n * {@link SceneModel#createMesh}. A SceneModelMesh can only belong to one SceneModelEntity, so you'll get an\n * error if you try to reuse a mesh among multiple SceneModelEntitys.\n * * The SceneModelEntity can have a {@link SceneModelTextureSet}, previously created with\n * {@link SceneModel#createTextureSet}. A SceneModelTextureSet can belong to multiple SceneModelEntitys.\n * * The SceneModelEntity can have a geometry, previously created with\n * {@link SceneModel#createTextureSet}. A geometry is a \"virtual component\" and can belong to multiple SceneModelEntitys.\n *\n * @param {Object} cfg SceneModelEntity configuration.\n * @param {String} cfg.id Optional ID for the new SceneModelEntity. Must not clash with any existing components within the {@link Scene}.\n * @param {String[]} cfg.meshIds IDs of one or more meshes created previously with {@link SceneModel@createMesh}.\n * @param {Boolean} [cfg.isObject] Set ````true```` if the {@link SceneModelEntity} represents an object, in which case it will be registered by {@link SceneModelEntity#id} in {@link Scene#objects} and can also have a corresponding {@link MetaObject} with matching {@link MetaObject#id}, registered by that ID in {@link MetaScene#metaObjects}.\n * @param {Boolean} [cfg.visible=true] Indicates if the SceneModelEntity is initially visible.\n * @param {Boolean} [cfg.culled=false] Indicates if the SceneModelEntity is initially culled from view.\n * @param {Boolean} [cfg.pickable=true] Indicates if the SceneModelEntity is initially pickable.\n * @param {Boolean} [cfg.clippable=true] Indicates if the SceneModelEntity is initially clippable.\n * @param {Boolean} [cfg.collidable=true] Indicates if the SceneModelEntity is initially included in boundary calculations.\n * @param {Boolean} [cfg.castsShadow=true] Indicates if the SceneModelEntity initially casts shadows.\n * @param {Boolean} [cfg.receivesShadow=true] Indicates if the SceneModelEntity initially receives shadows.\n * @param {Boolean} [cfg.xrayed=false] Indicates if the SceneModelEntity is initially xrayed. XRayed appearance is configured by {@link SceneModel#xrayMaterial}.\n * @param {Boolean} [cfg.highlighted=false] Indicates if the SceneModelEntity is initially highlighted. Highlighted appearance is configured by {@link SceneModel#highlightMaterial}.\n * @param {Boolean} [cfg.selected=false] Indicates if the SceneModelEntity is initially selected. Selected appearance is configured by {@link SceneModel#selectedMaterial}.\n * @param {Boolean} [cfg.edges=false] Indicates if the SceneModelEntity's edges are initially emphasized. Edges appearance is configured by {@link SceneModel#edgeMaterial}.\n * @returns {SceneModelEntity} The new SceneModelEntity.\n */\n createEntity(cfg) {\n if (cfg.id === undefined) {\n cfg.id = math.createUUID();\n } else if (this.scene.components[cfg.id]) {\n this.error(`Scene already has a Component with this ID: ${cfg.id} - will assign random ID`);\n cfg.id = math.createUUID();\n }\n if (cfg.meshIds === undefined) {\n this.error(\"Config missing: meshIds\");\n return;\n }\n let flags = 0;\n if (this._visible && cfg.visible !== false) {\n flags = flags | ENTITY_FLAGS.VISIBLE;\n }\n if (this._pickable && cfg.pickable !== false) {\n flags = flags | ENTITY_FLAGS.PICKABLE;\n }\n if (this._culled && cfg.culled !== false) {\n flags = flags | ENTITY_FLAGS.CULLED;\n }\n if (this._clippable && cfg.clippable !== false) {\n flags = flags | ENTITY_FLAGS.CLIPPABLE;\n }\n if (this._collidable && cfg.collidable !== false) {\n flags = flags | ENTITY_FLAGS.COLLIDABLE;\n }\n if (this._edges && cfg.edges !== false) {\n flags = flags | ENTITY_FLAGS.EDGES;\n }\n if (this._xrayed && cfg.xrayed !== false) {\n flags = flags | ENTITY_FLAGS.XRAYED;\n }\n if (this._highlighted && cfg.highlighted !== false) {\n flags = flags | ENTITY_FLAGS.HIGHLIGHTED;\n }\n if (this._selected && cfg.selected !== false) {\n flags = flags | ENTITY_FLAGS.SELECTED;\n }\n cfg.flags = flags;\n this._createEntity(cfg);\n }\n\n _createEntity(cfg) {\n let meshes = [];\n for (let i = 0, len = cfg.meshIds.length; i < len; i++) {\n const meshId = cfg.meshIds[i];\n let mesh = this._meshes[meshId]; // Trying to get already created mesh\n if (!mesh) { // Checks if there is already created mesh for this meshId\n this.error(`Mesh with this ID not found: \"${meshId}\" - ignoring this mesh`); // There is no such cfg\n continue;\n }\n if (mesh.parent) {\n this.error(`Mesh with ID \"${meshId}\" already belongs to object with ID \"${mesh.parent.id}\" - ignoring this mesh`);\n continue;\n }\n meshes.push(mesh);\n delete this._unusedMeshes[meshId];\n }\n const lodCullable = true;\n const entity = new SceneModelEntity(\n this,\n cfg.isObject,\n cfg.id,\n meshes,\n cfg.flags,\n lodCullable); // Internally sets SceneModelEntity#parent to this SceneModel\n this._entityList.push(entity);\n this._entities[cfg.id] = entity;\n this.numEntities++;\n }\n\n /**\n * Finalizes this SceneModel.\n *\n * Once finalized, you can't add anything more to this SceneModel.\n */\n finalize() {\n if (this.destroyed) {\n return;\n }\n this._createDummyEntityForUnusedMeshes();\n for (let i = 0, len = this.layerList.length; i < len; i++) {\n const layer = this.layerList[i];\n layer.finalize();\n }\n this._geometries = {};\n this._dtxBuckets = {};\n this._textures = {};\n this._textureSets = {};\n this._dtxLayers = {};\n this._vboInstancingLayers = {};\n this._vboBatchingLayers = {};\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n const entity = this._entityList[i];\n entity._finalize();\n }\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n const entity = this._entityList[i];\n entity._finalize2();\n }\n // Sort layers to reduce WebGL shader switching when rendering them\n this.layerList.sort((a, b) => {\n if (a.sortId < b.sortId) {\n return -1;\n }\n if (a.sortId > b.sortId) {\n return 1;\n }\n return 0;\n });\n for (let i = 0, len = this.layerList.length; i < len; i++) {\n const layer = this.layerList[i];\n layer.layerIndex = i;\n }\n this.glRedraw();\n this.scene._aabbDirty = true;\n this._viewMatrixDirty = true;\n this._matrixDirty = true;\n this._aabbDirty = true;\n\n this._setWorldMatrixDirty();\n this._sceneModelDirty();\n\n this.position = this._position;\n }\n\n /** @private */\n stateSortCompare(drawable1, drawable2) {\n }\n\n /** @private */\n rebuildRenderFlags() {\n this.renderFlags.reset();\n this._updateRenderFlagsVisibleLayers();\n if (this.renderFlags.numLayers > 0 && this.renderFlags.numVisibleLayers === 0) {\n this.renderFlags.culled = true;\n return;\n }\n this._updateRenderFlags();\n }\n\n /**\n * @private\n */\n _updateRenderFlagsVisibleLayers() {\n const renderFlags = this.renderFlags;\n renderFlags.numLayers = this.layerList.length;\n renderFlags.numVisibleLayers = 0;\n for (let layerIndex = 0, len = this.layerList.length; layerIndex < len; layerIndex++) {\n const layer = this.layerList[layerIndex];\n const layerVisible = this._getActiveSectionPlanesForLayer(layer);\n if (layerVisible) {\n renderFlags.visibleLayers[renderFlags.numVisibleLayers++] = layerIndex;\n }\n }\n }\n\n /** @private */\n _createDummyEntityForUnusedMeshes() {\n const unusedMeshIds = Object.keys(this._unusedMeshes);\n if (unusedMeshIds.length > 0) {\n const entityId = `${this.id}-dummyEntityForUnusedMeshes`;\n this.warn(`Creating dummy SceneModelEntity \"${entityId}\" for unused SceneMeshes: [${unusedMeshIds.join(\",\")}]`);\n this.createEntity({\n id: entityId,\n meshIds: unusedMeshIds,\n isObject: true\n });\n }\n this._unusedMeshes = {};\n }\n\n _getActiveSectionPlanesForLayer(layer) {\n const renderFlags = this.renderFlags;\n const sectionPlanes = this.scene._sectionPlanesState.sectionPlanes;\n const numSectionPlanes = sectionPlanes.length;\n const baseIndex = layer.layerIndex * numSectionPlanes;\n if (numSectionPlanes > 0) {\n for (let i = 0; i < numSectionPlanes; i++) {\n const sectionPlane = sectionPlanes[i];\n if (!sectionPlane.active) {\n renderFlags.sectionPlanesActivePerLayer[baseIndex + i] = false;\n } else {\n renderFlags.sectionPlanesActivePerLayer[baseIndex + i] = true;\n renderFlags.sectioned = true;\n }\n }\n }\n return true;\n }\n\n _updateRenderFlags() {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n if (this.numCulledLayerPortions === this.numPortions) {\n return;\n }\n const renderFlags = this.renderFlags;\n renderFlags.colorOpaque = (this.numTransparentLayerPortions < this.numPortions);\n if (this.numTransparentLayerPortions > 0) {\n renderFlags.colorTransparent = true;\n }\n if (this.numXRayedLayerPortions > 0) {\n const xrayMaterial = this.scene.xrayMaterial._state;\n if (xrayMaterial.fill) {\n if (xrayMaterial.fillAlpha < 1.0) {\n renderFlags.xrayedSilhouetteTransparent = true;\n } else {\n renderFlags.xrayedSilhouetteOpaque = true;\n }\n }\n if (xrayMaterial.edges) {\n if (xrayMaterial.edgeAlpha < 1.0) {\n renderFlags.xrayedEdgesTransparent = true;\n } else {\n renderFlags.xrayedEdgesOpaque = true;\n }\n }\n }\n if (this.numEdgesLayerPortions > 0) {\n const edgeMaterial = this.scene.edgeMaterial._state;\n if (edgeMaterial.edges) {\n renderFlags.edgesOpaque = (this.numTransparentLayerPortions < this.numPortions);\n if (this.numTransparentLayerPortions > 0) {\n renderFlags.edgesTransparent = true;\n }\n }\n }\n if (this.numSelectedLayerPortions > 0) {\n const selectedMaterial = this.scene.selectedMaterial._state;\n if (selectedMaterial.fill) {\n if (selectedMaterial.fillAlpha < 1.0) {\n renderFlags.selectedSilhouetteTransparent = true;\n } else {\n renderFlags.selectedSilhouetteOpaque = true;\n }\n }\n if (selectedMaterial.edges) {\n if (selectedMaterial.edgeAlpha < 1.0) {\n renderFlags.selectedEdgesTransparent = true;\n } else {\n renderFlags.selectedEdgesOpaque = true;\n }\n }\n }\n if (this.numHighlightedLayerPortions > 0) {\n const highlightMaterial = this.scene.highlightMaterial._state;\n if (highlightMaterial.fill) {\n if (highlightMaterial.fillAlpha < 1.0) {\n renderFlags.highlightedSilhouetteTransparent = true;\n } else {\n renderFlags.highlightedSilhouetteOpaque = true;\n }\n }\n if (highlightMaterial.edges) {\n if (highlightMaterial.edgeAlpha < 1.0) {\n renderFlags.highlightedEdgesTransparent = true;\n } else {\n renderFlags.highlightedEdgesOpaque = true;\n }\n }\n }\n }\n\n // -------------- RENDERING ---------------------------------------------------------------------------------------\n\n /** @private */\n drawColorOpaque(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawColorOpaque(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawColorTransparent(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawColorTransparent(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawDepth(frameCtx) { // Dedicated to SAO because it skips transparent objects\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawDepth(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawNormals(frameCtx) { // Dedicated to SAO because it skips transparent objects\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawNormals(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawSilhouetteXRayed(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawSilhouetteXRayed(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawSilhouetteHighlighted(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawSilhouetteHighlighted(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawSilhouetteSelected(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawSilhouetteSelected(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawEdgesColorOpaque(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawEdgesColorOpaque(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawEdgesColorTransparent(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawEdgesColorTransparent(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawEdgesXRayed(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawEdgesXRayed(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawEdgesHighlighted(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawEdgesHighlighted(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n drawEdgesSelected(frameCtx) {\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawEdgesSelected(renderFlags, frameCtx);\n }\n }\n\n /**\n * @private\n */\n drawOcclusion(frameCtx) {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawOcclusion(renderFlags, frameCtx);\n }\n }\n\n /**\n * @private\n */\n drawShadow(frameCtx) {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawShadow(renderFlags, frameCtx);\n }\n }\n\n /** @private */\n setPickMatrices(pickViewMatrix, pickProjMatrix) {\n if (this._numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n const layer = this.layerList[layerIndex];\n if (layer.setPickMatrices) {\n layer.setPickMatrices(pickViewMatrix, pickProjMatrix);\n }\n }\n }\n\n /** @private */\n drawPickMesh(frameCtx) {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawPickMesh(renderFlags, frameCtx);\n }\n }\n\n /**\n * Called by SceneModelMesh.drawPickDepths()\n * @private\n */\n drawPickDepths(frameCtx) {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawPickDepths(renderFlags, frameCtx);\n }\n }\n\n /**\n * Called by SceneModelMesh.drawPickNormals()\n * @private\n */\n drawPickNormals(frameCtx) {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n this.layerList[layerIndex].drawPickNormals(renderFlags, frameCtx);\n }\n }\n\n /**\n * @private\n */\n drawSnapInit(frameCtx) {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n const layer = this.layerList[layerIndex];\n if (layer.drawSnapInit) {\n frameCtx.snapPickOrigin = [0, 0, 0];\n frameCtx.snapPickCoordinateScale = [1, 1, 1];\n frameCtx.snapPickLayerNumber++;\n layer.drawSnapInit(renderFlags, frameCtx);\n frameCtx.snapPickLayerParams[frameCtx.snapPickLayerNumber] = {\n origin: frameCtx.snapPickOrigin.slice(),\n coordinateScale: frameCtx.snapPickCoordinateScale.slice(),\n };\n }\n }\n }\n\n /**\n * @private\n */\n drawSnap(frameCtx) {\n if (this.numVisibleLayerPortions === 0) {\n return;\n }\n const renderFlags = this.renderFlags;\n for (let i = 0, len = renderFlags.visibleLayers.length; i < len; i++) {\n const layerIndex = renderFlags.visibleLayers[i];\n const layer = this.layerList[layerIndex];\n if (layer.drawSnap) {\n frameCtx.snapPickOrigin = [0, 0, 0];\n frameCtx.snapPickCoordinateScale = [1, 1, 1];\n frameCtx.snapPickLayerNumber++;\n layer.drawSnap(renderFlags, frameCtx);\n frameCtx.snapPickLayerParams[frameCtx.snapPickLayerNumber] = {\n origin: frameCtx.snapPickOrigin.slice(),\n coordinateScale: frameCtx.snapPickCoordinateScale.slice(),\n };\n }\n }\n }\n\n /**\n * Destroys this SceneModel.\n */\n destroy() {\n for (let layerId in this._vboBatchingLayers) {\n if (this._vboBatchingLayers.hasOwnProperty(layerId)) {\n this._vboBatchingLayers[layerId].destroy();\n }\n }\n this._vboBatchingLayers = {};\n for (let layerId in this._vboInstancingLayers) {\n if (this._vboInstancingLayers.hasOwnProperty(layerId)) {\n this._vboInstancingLayers[layerId].destroy();\n }\n }\n this._vboInstancingLayers = {};\n this.scene.camera.off(this._onCameraViewMatrix);\n this.scene.off(this._onTick);\n for (let i = 0, len = this.layerList.length; i < len; i++) {\n this.layerList[i].destroy();\n }\n this.layerList = [];\n for (let i = 0, len = this._entityList.length; i < len; i++) {\n this._entityList[i]._destroy();\n }\n // Object.entries(this._geometries).forEach(([id, geometry]) => {\n // geometry.destroy();\n // });\n this._geometries = {};\n this._dtxBuckets = {};\n this._textures = {};\n this._textureSets = {};\n this._meshes = {};\n this._entities = {};\n this.scene._aabbDirty = true;\n if (this._isModel) {\n this.scene._deregisterModel(this);\n }\n putScratchMemory();\n super.destroy();\n }\n}\n\n\n/**\n * This function applies two steps to the provided mesh geometry data:\n *\n * - 1st, it reduces its `.positions` to unique positions, thus removing duplicate vertices. It will adjust the `.indices` and `.edgeIndices` array accordingly to the unique `.positions`.\n *\n * - 2nd, it tries to do an optimization called `index rebucketting`\n *\n * _Rebucketting minimizes the amount of RAM usage for a given mesh geometry by trying do demote its needed index bitness._\n *\n * - _for 32 bit indices, will try to demote them to 16 bit indices_\n * - _for 16 bit indices, will try to demote them to 8 bits indices_\n * - _8 bits indices are kept as-is_\n *\n * The fact that 32/16/8 bits are needed for indices, depends on the number of maximumm indexable vertices within the mesh geometry: this is, the number of vertices in the mesh geometry.\n *\n * The function returns the same provided input `geometry`, enrichened with the additional key `.preparedBukets`.\n *\n * @param {object} geometry The mesh information containing `.positions`, `.indices`, `.edgeIndices` arrays.\n *\n * @param enableVertexWelding\n * @param enableIndexBucketing\n * @returns {object} The mesh information enrichened with `.buckets` key.\n */\nfunction createDTXBuckets(geometry, enableVertexWelding, enableIndexBucketing) {\n let uniquePositionsCompressed, uniqueIndices, uniqueEdgeIndices;\n if (enableVertexWelding || enableIndexBucketing) { // Expensive - careful!\n [\n uniquePositionsCompressed,\n uniqueIndices,\n uniqueEdgeIndices,\n ] = uniquifyPositions({\n positionsCompressed: geometry.positionsCompressed,\n indices: geometry.indices,\n edgeIndices: geometry.edgeIndices\n });\n } else {\n uniquePositionsCompressed = geometry.positionsCompressed;\n uniqueIndices = geometry.indices;\n uniqueEdgeIndices = geometry.edgeIndices;\n }\n let buckets;\n if (enableIndexBucketing) {\n let numUniquePositions = uniquePositionsCompressed.length / 3;\n buckets = rebucketPositions({\n positionsCompressed: uniquePositionsCompressed,\n indices: uniqueIndices,\n edgeIndices: uniqueEdgeIndices,\n },\n (numUniquePositions > (1 << 16)) ? 16 : 8,\n // true\n );\n } else {\n buckets = [{\n positionsCompressed: uniquePositionsCompressed,\n indices: uniqueIndices,\n edgeIndices: uniqueEdgeIndices,\n }];\n }\n return buckets;\n}\n\n/**\n * A set of 3D line segments.\n *\n * * Creates a set of 3D line segments.\n * * Registered by {@link LineSet#id} in {@link Scene#lineSets}.\n * * Configure color using the {@link LinesMaterial} located at {@link Scene#linesMaterial}.\n * * {@link BCFViewpointsPlugin} will save and load Linesets in BCF viewpoints.\n *\n * ## Usage\n *\n * In the example below, we'll load the Schependomlaan model, then use\n * a ````LineSet```` to show a grid underneath the model.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#LineSet_grid)\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#LineSet_grid)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, LineSet, buildGridGeometry} from \"https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/xeokit-sdk.es.min.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * const camera = viewer.camera;\n *\n * viewer.camera.eye = [-2.56, 8.38, 8.27];\n * viewer.camera.look = [13.44, 3.31, -14.83];\n * viewer.camera.up = [0.10, 0.98, -0.14];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"../assets/models/xkt/v8/ifc/Schependomlaan.ifc.xkt\",\n * position: [0,1,0],\n * edges: true,\n * saoEnabled: true\n * });\n *\n * const geometryArrays = buildGridGeometry({\n * size: 100,\n * divisions: 30\n * });\n *\n * new LineSet(viewer.scene, {\n * positions: geometryArrays.positions,\n * indices: geometryArrays.indices\n * });\n * ````\n */\nclass LineSet extends Component {\n\n /**\n * Creates a new LineSet.\n *\n * Registers the LineSet in {@link Scene#lineSets}; causes Scene to fire a \"lineSetCreated\" event.\n *\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this ````LineSet```` as well.\n * @param {*} [cfg] ````LineSet```` configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} cfg.positions World-space 3D vertex positions.\n * @param {Number[]} [cfg.indices] Indices to connect ````positions```` into line segments. Note that these are separate line segments, not a polyline.\n * @param {Number[]} [cfg.color=[0,0,0]] The color of this ````LineSet````. This is both emissive and diffuse.\n * @param {Boolean} [cfg.visible=true] Indicates whether or not this ````LineSet```` is visible.\n * @param {Number} [cfg.opacity=1.0] ````LineSet````'s initial opacity factor.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._positions = cfg.positions || [];\n\n if (cfg.indices) {\n this._indices = cfg.indices;\n } else {\n this._indices = [];\n for (let i = 0, len = (this._positions.length / 3) - 1; i < len; i += 2) {\n this._indices.push(i);\n this._indices.push(i + 1);\n }\n }\n\n this._sceneModel = new SceneModel(this, {\n isModel: false // Don't register in Scene.models\n });\n\n this._sceneModel.createMesh({\n id: \"linesMesh\",\n primitive: \"lines\",\n positions: this._positions,\n indices: this._indices\n });\n\n this._sceneModel.createEntity({\n meshIds: [\"linesMesh\"],\n visible: cfg.visible,\n clippable: cfg.clippable,\n collidable: cfg.collidable\n });\n\n this._sceneModel.finalize();\n\n this.scene._lineSetCreated(this);\n }\n\n /**\n * Sets if this ````LineSet```` is visible.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} visible Set ````true```` to make this ````LineSet```` visible.\n */\n set visible(visible) {\n this._sceneModel.visible = visible;\n }\n\n /**\n * Gets if this ````LineSet```` is visible.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} Returns ````true```` if visible.\n */\n get visible() {\n return this._sceneModel.visible;\n }\n\n /**\n * Gets the 3D World-space vertex positions of the lines in this ````LineSet````.\n *\n * @returns {Number[]}\n */\n get positions() {\n return this._positions;\n }\n\n /**\n * Gets the vertex indices of the lines in this ````LineSet````.\n *\n * @returns {Number[]}\n */\n get indices() {\n return this._indices;\n }\n\n /**\n * Destroys this ````LineSet````.\n *\n * Removes the ```LineSet```` from {@link Scene#lineSets}; causes Scene to fire a \"lineSetDestroyed\" event.\n */\n destroy() {\n super.destroy(); // destroyes _sceneModel\n this.scene._lineSetDestroyed(this);\n }\n}\n\nconst tempVec3$5 = math.vec3();\nconst tempVec3a$9 = math.vec3();\nconst tempVec3b$6 = math.vec3();\nconst tempVec3c$4 = math.vec3();\n\n/**\n * {@link Viewer} plugin that saves and loads BCF viewpoints as JSON objects.\n *\n * [](/examples/index.html#BCF_SaveViewpoint)\n *\n * * [[Example 1: Saving viewer state to a BCF viewpoint](https://xeokit.github.io/xeokit-sdk/examples/index.html#BCF_SaveViewpoint)]\n * * [[Example 2: Loading viewer state from a BCF viewpoint](https://xeokit.github.io/xeokit-sdk/examples/index.html#BCF_LoadViewpoint)]\n *\n * ## Overview\n *\n * BCF is an open standard that enables workflow communications between BIM software tools. An XML schema, called\n * Building Collaboration Format (BCF), encodes messages that inform one BIM tool of issues found by another.\n *\n * A BCF viewpoint captures a viewpoint of a model that highlights an issue. The viewpoint can then be loaded by another\n * viewer to examine the issue.\n *\n * Using this plugin, a xeokit {@link Viewer} can exchange BCF-encoded viewpoints with other BIM software,\n * allowing us to use the Viewer to report and view issues in BIM models.\n *\n * This plugin's viewpoints conform to the BCF Version 2.1 specification.\n *\n * ## Supported BCF Elements\n *\n * BCFViewpointsPlugin saves and loads the following state in BCF viewpoints:\n *\n * * {@link Camera} position, orientation and projection\n * * {@link Entity} visibilities and selection states\n * * {@link SectionPlane}s to slice the model\n * * {@link LineSet}s to show 3D lines\n * * {@link Bitmap}s to show images\n *\n * ## Saving a BCF Viewpoint\n *\n * In the example below we'll create a {@link Viewer}, load an ````.XKT```` model into it using an {@link XKTLoaderPlugin},\n * slice the model in half using a {@link SectionPlanesPlugin}, create a grid ground plane using a {@link LineSet} and a 2D\n * plan view using a {@link Bitmap}, then use a {@link BCFViewpointsPlugin#getViewpoint}\n * to save a viewpoint to JSON, which we'll log to the JavaScript developer console.\n *\n * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#BCF_SaveViewpoint)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, SectionPlanesPlugin,\n * LineSet, Bitmap, buildGridGeometry, BCFViewpointsPlugin} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * // Set camera position and orientation\n * viewer.scene.camera.eye = [-48.93, 54.54, 50.41];\n * viewer.scene.camera.look = [0.55, -0.61, -0.55];\n * viewer.scene.camera.up = [0, -1, 0];\n * viewer.scene.camera.perspective.fov = 60;\n *\n * // Add a XKTLoaderPlugin\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * // Add a SectionPlanesPlugin\n * const sectionPlanes = new SectionPlanesPlugin(viewer);\n *\n * // Add a BCFViewpointsPlugin\n * const bcfViewpoints = new BCFViewpointsPlugin(viewer);\n *\n * // Load an .XKT model\n * const modelNode = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/Schependomlaan.xkt\",\n * edges: true // Emphasise edges\n * });\n *\n * // Slice it in half\n * sectionPlanes.createSectionPlane({\n * id: \"myClip\",\n * pos: [0, 0, 0],\n * dir: [0.5, 0.0, 0.5]\n * });\n *\n * // Create a bitmap\n * const bitmap = new Bitmap(viewer.scene, {\n * src: \"../assets/images/schependomlaanPlanView.png\",\n * visible: true,\n * height: 24.0,\n * pos: [-15, 0, -10],\n * normal: [0, -1, 0],\n * up: [0, 0, 1],\n * collidable: false,\n * opacity: 1.0,\n * clippable: false,\n * pickable: true\n * });\n *\n * // Create a grid ground plane\n * const geometryArrays = buildGridGeometry({\n * size: 60,\n * divisions: 10\n * });\n *\n * new LineSet(viewer.scene, {\n * positions: geometryArrays.positions,\n * indices: geometryArrays.indices,\n * position: [10,0,10],\n * clippable: false\n * });\n *\n * // When model is loaded, select some objects and capture a BCF viewpoint to the console\n * modelNode.on(\"loaded\", () => {\n *\n * const scene = viewer.scene;\n *\n * scene.setObjectsSelected([\n * \"3b2U496P5Ebhz5FROhTwFH\",\n * \"2MGtJUm9nD$Re1_MDIv0g2\",\n * \"3IbuwYOm5EV9Q6cXmwVWqd\",\n * \"3lhisrBxL8xgLCRdxNG$2v\",\n * \"1uDn0xT8LBkP15zQc9MVDW\"\n * ], true);\n *\n * const viewpoint = bcfViewpoints.getViewpoint();\n * const viewpointStr = JSON.stringify(viewpoint, null, 4);\n *\n * console.log(viewpointStr);\n * });\n * ````\n *\n * The saved BCF viewpoint would look something like below. Note that some elements are truncated for brevity.\n *\n * ````json\n * {\n * \"perspective_camera\": {\n * \"camera_view_point\": { \"x\": -48.93, \"y\": 54.54, \"z\": 50.41 },\n * \"camera_direction\": { \"x\": 0.55, \"y\": -0.61, \"z\": -0.55},\n * \"camera_up_vector\": { \"x\": 0.37, \"y\": -0.41, \"z\": 0.83 },\n * \"field_of_view\": 60.0\n * },\n * \"lines\": [{\n * \"start_point\": { \"x\": 1.0, \"y\": 1.0, \"z\": 1.0 },\n * \"end_point\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.0 },\n * //...(truncated)\n * }],\n * \"bitmaps\": [{\n * \"bitmap_type\": \"png\",\n * \"bitmap_data\": \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB9AAAAdp...\", //...(truncated)\n * \"location\": { \"x\": -15, \"y\": 10, \"z\": 0 },\n * \"normal\": { \"x\": 0, \"y\": 0, \"z\": -1 },\n * \"up\": { \"x\": 0, \"y\": -1, \"z\": 0 },\n * \"height\": 24\n * }],\n * \"clipping_planes\": [{\n * \"location\": { \"x\": 0.0, \"y\": 0.0, \"z\": 0.0 },\n * \"direction\": { \"x\": 0.5, \"y\": 0.0, \"z\": 0.5 }\n * }],\n * \"snapshot\": {\n * \"snapshot_type\": \"png\",\n * \"snapshot_data\": \"data:image/png;base64,......\"\n * },\n * \"components\": {\n * \"visibility\": {\n * \"default_visibility\": false,\n * \"exceptions\": [{\n * \"ifc_guid\": \"4$cshxZO9AJBebsni$z9Yk\",\n * \"originating_system\": \"xeokit.io\",\n * \"authoring_tool_id\": \"xeokit/v3.2\"\n * },\n * //...\n * ]\n * },\n * \"selection\": [{\n * \"ifc_guid\": \"4$cshxZO9AJBebsni$z9Yk\",\n * },\n * //...\n * ]\n * }\n * }\n * ````\n *\n * ## Saving View Setup Hints\n *\n * BCFViewpointsPlugin can optionally save hints in the viewpoint, which indicate how to set up the view when\n * loading it again.\n *\n * Here's the {@link BCFViewpointsPlugin#getViewpoint} call again, this time saving some hints:\n *\n * ````javascript\n * const viewpoint = bcfViewpoints.getViewpoint({ // Options\n * spacesVisible: true, // Force IfcSpace types visible in the viewpoint (default is false)\n * spaceBoundariesVisible: false, // Show IfcSpace boundaries in the viewpoint (default is false)\n * openingsVisible: true // Force IfcOpening types visible in the viewpoint (default is false)\n * });\n * ````\n *\n * ## Loading a BCF Viewpoint\n *\n * Assuming that we have our BCF viewpoint in a JSON object, let's now restore it with {@link BCFViewpointsPlugin#setViewpoint}:\n *\n * ````javascript\n * bcfViewpoints.setViewpoint(viewpoint);\n * ````\n *\n * ## Handling BCF Incompatibility with xeokit's Camera\n *\n * xeokit's {@link Camera#look} is the current 3D *point-of-interest* (POI).\n *\n * A BCF viewpoint, however, has a direction vector instead of a POI, and so {@link BCFViewpointsPlugin#getViewpoint} saves\n * xeokit's POI as a normalized vector from {@link Camera#eye} to {@link Camera#look}, which unfortunately loses\n * that positional information. Loading the viewpoint with {@link BCFViewpointsPlugin#setViewpoint} will restore {@link Camera#look} to\n * the viewpoint's camera position, offset by the normalized vector.\n *\n * As shown below, providing a ````rayCast```` option to ````setViewpoint```` will set {@link Camera#look} to the closest\n * surface intersection on the direction vector. Internally, ````setViewpoint```` supports this option by firing a ray\n * along the vector, and if that hits an {@link Entity}, sets {@link Camera#look} to ray's intersection point with the\n * Entity's surface.\n *\n * ````javascript\n * bcfViewpoints.setViewpoint(viewpoint, {\n * rayCast: true // <<--------------- Attempt to set Camera#look to surface intersection point (default)\n * });\n * ````\n *\n * ## Dealing With Loaded Models that are not in the Viewpoint\n *\n * If, for example, we load model \"duplex\", hide some objects, then save a BCF viewpoint with\n * ````BCFViewpointsPlugin#getViewpoint````, then load another model, \"schependomlaan\", then load the viewpoint again\n * with ````BCFViewpointsPlugin#setViewpoint````, then sometimes all of the objects in model \"schependomlaan\" become\n * visible, along with the visible objects in the viewpoint, which belong to model \"duplex\".\n *\n * The reason is that, when saving a BCF viewpoint, BCF logic works like the following pseudo code:\n *\n * ````\n * If numVisibleObjects < numInvisibleObjects\n * save IDs of visible objects in BCF\n * exceptions = \"visible objects\"\n * else\n * save IDS of invisible objects in BCF\n * exceptions = \"invisible objects\"\n * ````\n *\n * When loading the viewpoint again:\n *\n * ````\n * If exceptions = \"visible objects\"\n * hide all objects\n * show visible objects in BCF\n * else\n * show all objects\n * hide invisible objects in BCF\n * ````\n *\n * When the exception is \"visible objects\", loading the viewpoint shows all the objects in the first, which includes\n * objects in \"schependomlaan\", which can be confusing, because those were not even loaded when we first\n * saved the viewpoint..\n *\n * To solve this, we can supply a ````defaultInvisible```` option to {@link BCFViewpointsPlugin#getViewpoint}, which\n * will force the plugin to save the IDs of all visible objects while making invisible objects the exception.\n *\n * That way, when we load the viewpoint again, after loading model \"schependomlaan\", the plugin will hide all objects\n * in the scene first (which will include objects belonging to model \"schependomlaan\"), then make the objects in the\n * viewpoint visible (which will only be those of object \"duplex\").\n *\n * ````javascript\n * const viewpoint = bcfViewpoints.getViewpoint({ // Options\n * //..\n * defaultInvisible: true\n * });\n * ````\n *\n * [[Run an example](/examples/index.html#BCF_LoadViewpoint_defaultInvisible)]\n *\n * ## Behaviour with XKTLoaderPlugin globalizeObjectIds\n *\n * Whenever we use {@link XKTLoaderPlugin} to load duplicate copies of the same model, after configuring\n * {@link XKTLoaderPlugin#globalizeObjectIds} ````true```` to avoid ````Entity```` ID clashes, this has consequences\n * for BCF viewpoints created by {@link BCFViewpointsPlugin#getViewpoint}.\n *\n * When no duplicate copies of a model are loaded like this, viewpoints created by {@link BCFViewpointsPlugin#getViewpoint} will\n * continue to load as usual in other BIM viewers. Conversely, a viewpoint created for a single model in other BIM viewers\n * will continue to load as usual with ````BCFViewpointsPlugin````.\n *\n * When duplicate copies of a model are loaded, however, viewpoints created by {@link BCFViewpointsPlugin#getViewpoint}\n * will contain certain changes that will affect the viewpoint's portability, however. Such viewpoints will\n * use ````authoring_tool_id```` fields to save the globalized ````Entity#id```` values, which enables the viewpoints to\n * capture the states of the individual ````Entitys```` that represent the duplicate IFC elements. Take a look at the\n * following two examples to learn more.\n *\n * * [Example: Saving a BCF viewpoint containing duplicate models](https://xeokit.github.io/xeokit-sdk/examples/index.html#BCF_SaveViewpoint_MultipleModels)\n * * [Example: Loading a BCF viewpoint containing duplicate models](https://xeokit.github.io/xeokit-sdk/examples/index.html#BCF_LoadViewpoint_MultipleModels)\n *\n * **Caveat:** when loading a BCF viewpoint, we always assume that we have loaded in our target BIM viewer the same models that were\n * loaded in the viewpoint's original authoring application when the viewpoint was created. In the case of multi-model\n * viewpoints, the target BIM viewer, whether it be xeokit or another BIM viewer, will need to first have those exact\n * models loaded, with their objects having globalized IDs, following the same prefixing scheme we're using in\n * xeokit. Then, the viewpoint's ````authoring_tool_id```` fields will be able to resolve to their objects within the\n * target viewer.\n *\n * @class BCFViewpointsPlugin\n */\nclass BCFViewpointsPlugin extends Plugin {\n\n /**\n * @constructor\n * @param {Viewer} viewer The Viewer.\n * @param {Object} cfg Plugin configuration.\n * @param {String} [cfg.id=\"BCFViewpoints\"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.\n * @param {String} [cfg.originatingSystem] Identifies the originating system for BCF records.\n * @param {String} [cfg.authoringTool] Identifies the authoring tool for BCF records.\n */\n constructor(viewer, cfg = {}) {\n\n super(\"BCFViewpoints\", viewer, cfg);\n\n /**\n * Identifies the originating system to include in BCF viewpoints saved by this plugin.\n * @property originatingSystem\n * @type {string}\n */\n this.originatingSystem = cfg.originatingSystem || \"xeokit.io\";\n\n /**\n * Identifies the authoring tool to include in BCF viewpoints saved by this plugin.\n * @property authoringTool\n * @type {string}\n */\n this.authoringTool = cfg.authoringTool || \"xeokit.io\";\n }\n\n /**\n * Saves viewer state to a BCF viewpoint.\n *\n * See ````BCFViewpointsPlugin```` class comments for more info.\n *\n * @param {*} [options] Options for getting the viewpoint.\n * @param {Boolean} [options.spacesVisible=false] Indicates whether ````IfcSpace```` types should be forced visible in the viewpoint.\n * @param {Boolean} [options.openingsVisible=false] Indicates whether ````IfcOpening```` types should be forced visible in the viewpoint.\n * @param {Boolean} [options.spaceBoundariesVisible=false] Indicates whether the boundaries of ````IfcSpace```` types should be visible in the viewpoint.\n * @param {Boolean} [options.spacesTranslucent=false] Indicates whether ````IfcSpace```` types should be forced translucent in the viewpoint.\n * @param {Boolean} [options.spaceBoundariesTranslucent=false] Indicates whether the boundaries of ````IfcSpace```` types should be forced translucent in the viewpoint.\n * @param {Boolean} [options.openingsTranslucent=true] Indicates whether ````IfcOpening```` types should be forced translucent in the viewpoint.\n * @param {Boolean} [options.snapshot=true] Indicates whether the snapshot should be included in the viewpoint.\n * @param {Boolean} [options.defaultInvisible=false] When ````true````, will save the default visibility of all objects\n * as ````false````. This means that when we load the viewpoint again, and there are additional models loaded that\n * were not saved in the viewpoint, those models will be hidden when we load the viewpoint, and that only the\n * objects in the viewpoint will be visible.\n * @param {Boolean} [options.reverseClippingPlanes=false] When ````true````, clipping planes are reversed (https://github.com/buildingSMART/BCF-XML/issues/193)\n * @returns {*} BCF JSON viewpoint object\n */\n getViewpoint(options = {}) {\n const scene = this.viewer.scene;\n const camera = scene.camera;\n const realWorldOffset = scene.realWorldOffset;\n const reverseClippingPlanes = (options.reverseClippingPlanes === true);\n let bcfViewpoint = {};\n\n // Camera\n let lookDirection = math.normalizeVec3(math.subVec3(camera.look, camera.eye, math.vec3()));\n let eye = camera.eye;\n let up = camera.up;\n\n if (camera.yUp) {\n // BCF is Z up\n lookDirection = YToZ(lookDirection);\n eye = YToZ(eye);\n up = YToZ(up);\n }\n\n const camera_view_point = xyzArrayToObject(math.addVec3(eye, realWorldOffset));\n\n if (camera.projection === \"ortho\") {\n bcfViewpoint.orthogonal_camera = {\n camera_view_point: camera_view_point,\n camera_direction: xyzArrayToObject(lookDirection),\n camera_up_vector: xyzArrayToObject(up),\n view_to_world_scale: camera.ortho.scale,\n };\n } else {\n bcfViewpoint.perspective_camera = {\n camera_view_point: camera_view_point,\n camera_direction: xyzArrayToObject(lookDirection),\n camera_up_vector: xyzArrayToObject(up),\n field_of_view: camera.perspective.fov,\n };\n }\n\n // Section planes\n\n const sectionPlanes = scene.sectionPlanes;\n for (let id in sectionPlanes) {\n if (sectionPlanes.hasOwnProperty(id)) {\n let sectionPlane = sectionPlanes[id];\n if (!sectionPlane.active) {\n continue;\n }\n let location = sectionPlane.pos;\n\n let direction;\n if (reverseClippingPlanes) {\n direction = math.negateVec3(sectionPlane.dir, math.vec3());\n } else {\n direction = sectionPlane.dir;\n }\n\n if (camera.yUp) {\n // BCF is Z up\n location = YToZ(location);\n direction = YToZ(direction);\n }\n math.addVec3(location, realWorldOffset);\n\n location = xyzArrayToObject(location);\n direction = xyzArrayToObject(direction);\n if (!bcfViewpoint.clipping_planes) {\n bcfViewpoint.clipping_planes = [];\n }\n bcfViewpoint.clipping_planes.push({location, direction});\n }\n }\n\n // Lines\n\n const lineSets = scene.lineSets;\n for (let id in lineSets) {\n if (lineSets.hasOwnProperty(id)) {\n const lineSet = lineSets[id];\n if (!bcfViewpoint.lines) {\n bcfViewpoint.lines = [];\n }\n const positions = lineSet.positions;\n const indices = lineSet.indices;\n for (let i = 0, len = indices.length / 2; i < len; i++) {\n const a = indices[i * 2];\n const b = indices[(i * 2) + 1];\n bcfViewpoint.lines.push({\n start_point: {\n x: positions[a * 3 + 0],\n y: positions[a * 3 + 1],\n z: positions[a * 3 + 2]\n },\n end_point: {\n x: positions[b * 3 + 0],\n y: positions[b * 3 + 1],\n z: positions[b * 3 + 2]\n }\n });\n }\n\n }\n }\n\n // Bitmaps\n\n const bitmaps = scene.bitmaps;\n for (let id in bitmaps) {\n if (bitmaps.hasOwnProperty(id)) {\n let bitmap = bitmaps[id];\n let location = bitmap.pos;\n let normal = bitmap.normal;\n let up = bitmap.up;\n if (camera.yUp) {\n // BCF is Z up\n location = YToZ(location);\n normal = YToZ(normal);\n up = YToZ(up);\n }\n math.addVec3(location, realWorldOffset);\n if (!bcfViewpoint.bitmaps) {\n bcfViewpoint.bitmaps = [];\n }\n bcfViewpoint.bitmaps.push({\n bitmap_type: bitmap.type,\n bitmap_data: bitmap.imageData,\n location: xyzArrayToObject(location),\n normal: xyzArrayToObject(normal),\n up: xyzArrayToObject(up),\n height: bitmap.height\n });\n }\n }\n\n // Entity states\n\n bcfViewpoint.components = {\n visibility: {\n view_setup_hints: {\n spaces_visible: !!options.spacesVisible,\n space_boundaries_visible: !!options.spaceBoundariesVisible,\n openings_visible: !!options.openingsVisible,\n spaces_translucent: !!options.spaces_translucent,\n space_boundaries_translucent: !!options.space_boundaries_translucent,\n openings_translucent: !!options.openings_translucent\n }\n }\n };\n\n const opacityObjectIds = new Set(scene.opacityObjectIds);\n const xrayedObjectIds = new Set(scene.xrayedObjectIds);\n const colorizedObjectIds = new Set(scene.colorizedObjectIds);\n\n const coloringMap = Object.values(scene.objects)\n .filter(entity => opacityObjectIds.has(entity.id) || colorizedObjectIds.has(entity.id) || xrayedObjectIds.has(entity.id))\n .reduce((coloringMap, entity) => {\n\n let color = colorizeToRGB(entity.colorize);\n let alpha;\n\n if (entity.xrayed) {\n if (scene.xrayMaterial.fillAlpha === 0.0 && scene.xrayMaterial.edgeAlpha !== 0.0) {\n // BCF can't deal with edges. If xRay is implemented only with edges, set an arbitrary opacity\n alpha = 0.1;\n } else {\n alpha = scene.xrayMaterial.fillAlpha;\n }\n alpha = Math.round(alpha * 255).toString(16).padStart(2, \"0\");\n color = alpha + color;\n } else if (opacityObjectIds.has(entity.id)) {\n alpha = Math.round(entity.opacity * 255).toString(16).padStart(2, \"0\");\n color = alpha + color;\n }\n\n if (!coloringMap[color]) {\n coloringMap[color] = [];\n }\n\n const objectId = entity.id;\n const originalSystemId = entity.originalSystemId;\n const component = {\n ifc_guid: originalSystemId,\n originating_system: this.originatingSystem\n };\n if (originalSystemId !== objectId) {\n component.authoring_tool_id = objectId;\n }\n\n coloringMap[color].push(component);\n\n return coloringMap;\n\n }, {});\n\n const coloringArray = Object.entries(coloringMap).map(([color, components]) => {\n return {color, components};\n });\n\n bcfViewpoint.components.coloring = coloringArray;\n\n const objectIds = scene.objectIds;\n const visibleObjects = scene.visibleObjects;\n const visibleObjectIds = scene.visibleObjectIds;\n const invisibleObjectIds = objectIds.filter(id => !visibleObjects[id]);\n const selectedObjectIds = scene.selectedObjectIds;\n\n if (options.defaultInvisible || visibleObjectIds.length < invisibleObjectIds.length) {\n bcfViewpoint.components.visibility.exceptions = this._createBCFComponents(visibleObjectIds);\n bcfViewpoint.components.visibility.default_visibility = false;\n } else {\n bcfViewpoint.components.visibility.exceptions = this._createBCFComponents(invisibleObjectIds);\n bcfViewpoint.components.visibility.default_visibility = true;\n }\n\n bcfViewpoint.components.selection = this._createBCFComponents(selectedObjectIds);\n\n bcfViewpoint.components.translucency = this._createBCFComponents(scene.xrayedObjectIds);\n\n if (options.snapshot !== false) {\n bcfViewpoint.snapshot = {\n snapshot_type: \"png\",\n snapshot_data: this.viewer.getSnapshot({format: \"png\"})\n };\n }\n\n return bcfViewpoint;\n }\n\n _createBCFComponents(objectIds) {\n const scene = this.viewer.scene;\n const components = [];\n for (let i = 0, len = objectIds.length; i < len; i++) {\n const objectId = objectIds[i];\n const entity = scene.objects[objectId];\n if (entity) {\n const component = {\n ifc_guid: entity.originalSystemId,\n originating_system: this.originatingSystem\n };\n if (entity.originalSystemId !== objectId) {\n component.authoring_tool_id = objectId;\n }\n components.push(component);\n }\n }\n return components;\n }\n\n /**\n * Sets viewer state to the given BCF viewpoint.\n *\n * Note that xeokit's {@link Camera#look} is the **point-of-interest**, whereas the BCF ````camera_direction```` is a\n * direction vector. Therefore, when loading a BCF viewpoint, we set {@link Camera#look} to the absolute position\n * obtained by offsetting the BCF ````camera_view_point```` along ````camera_direction````.\n *\n * When loading a viewpoint, we also have the option to find {@link Camera#look} as the closest point of intersection\n * (on the surface of any visible and pickable {@link Entity}) with a 3D ray fired from ````camera_view_point```` in\n * the direction of ````camera_direction````.\n *\n * @param {*} bcfViewpoint BCF JSON viewpoint object,\n * shows default visible entities and restores camera to initial default position.\n * @param {*} [options] Options for setting the viewpoint.\n * @param {Boolean} [options.rayCast=true] When ````true```` (default), will attempt to set {@link Camera#look} to the closest\n * point of surface intersection with a ray fired from the BCF ````camera_view_point```` in the direction of ````camera_direction````.\n * @param {Boolean} [options.immediate=true] When ````true```` (default), immediately set camera position.\n * @param {Boolean} [options.duration] Flight duration in seconds. Overrides {@link CameraFlightAnimation#duration}. Only applies when ````immediate```` is ````false````.\n * @param {Boolean} [options.reset=true] When ````true```` (default), set {@link Entity#xrayed} and {@link Entity#highlighted} ````false```` on all scene objects.\n * @param {Boolean} [options.reverseClippingPlanes=false] When ````true````, clipping planes are reversed (https://github.com/buildingSMART/BCF-XML/issues/193)\n * @param {Boolean} [options.updateCompositeObjects=false] When ````true````, then when visibility and selection updates refer to composite objects (eg. an IfcBuildingStorey),\n * then this method will apply the updates to objects within those composites.\n */\n setViewpoint(bcfViewpoint, options = {}) {\n if (!bcfViewpoint) {\n return;\n }\n\n const viewer = this.viewer;\n const scene = viewer.scene;\n const camera = scene.camera;\n const rayCast = (options.rayCast !== false);\n const immediate = (options.immediate !== false);\n const reset = (options.reset !== false);\n const realWorldOffset = scene.realWorldOffset;\n const reverseClippingPlanes = (options.reverseClippingPlanes === true);\n\n scene.clearSectionPlanes();\n\n if (bcfViewpoint.clipping_planes && bcfViewpoint.clipping_planes.length > 0) {\n bcfViewpoint.clipping_planes.forEach(function (e) {\n let pos = xyzObjectToArray(e.location, tempVec3$5);\n let dir = xyzObjectToArray(e.direction, tempVec3$5);\n\n if (reverseClippingPlanes) {\n math.negateVec3(dir);\n }\n math.subVec3(pos, realWorldOffset);\n\n if (camera.yUp) {\n pos = ZToY(pos);\n dir = ZToY(dir);\n }\n new SectionPlane(scene, {pos, dir});\n });\n }\n\n scene.clearLines();\n\n if (bcfViewpoint.lines && bcfViewpoint.lines.length > 0) {\n const positions = [];\n const indices = [];\n let i = 0;\n bcfViewpoint.lines.forEach((e) => {\n if (!e.start_point) {\n return;\n }\n if (!e.end_point) {\n return;\n }\n positions.push(e.start_point.x);\n positions.push(e.start_point.y);\n positions.push(e.start_point.z);\n positions.push(e.end_point.x);\n positions.push(e.end_point.y);\n positions.push(e.end_point.z);\n indices.push(i++);\n indices.push(i++);\n });\n new LineSet(scene, {\n positions,\n indices,\n clippable: false,\n collidable: true\n });\n }\n\n scene.clearBitmaps();\n\n if (bcfViewpoint.bitmaps && bcfViewpoint.bitmaps.length > 0) {\n bcfViewpoint.bitmaps.forEach(function (e) {\n const bitmap_type = e.bitmap_type || \"jpg\"; // \"jpg\" | \"png\"\n const bitmap_data = e.bitmap_data; // base64\n let location = xyzObjectToArray(e.location, tempVec3a$9);\n let normal = xyzObjectToArray(e.normal, tempVec3b$6);\n let up = xyzObjectToArray(e.up, tempVec3c$4);\n let height = e.height || 1;\n if (!bitmap_type) {\n return;\n }\n if (!bitmap_data) {\n return;\n }\n if (!location) {\n return;\n }\n if (!normal) {\n return;\n }\n if (!up) {\n return;\n }\n if (camera.yUp) {\n location = ZToY(location);\n normal = ZToY(normal);\n up = ZToY(up);\n }\n new Bitmap(scene, {\n src: bitmap_data,\n type: bitmap_type,\n pos: location,\n normal: normal,\n up: up,\n clippable: false,\n collidable: true,\n height\n });\n });\n }\n\n if (reset) {\n scene.setObjectsXRayed(scene.xrayedObjectIds, false);\n scene.setObjectsHighlighted(scene.highlightedObjectIds, false);\n scene.setObjectsSelected(scene.selectedObjectIds, false);\n }\n\n if (bcfViewpoint.components) {\n\n if (bcfViewpoint.components.visibility) {\n\n if (!bcfViewpoint.components.visibility.default_visibility) {\n scene.setObjectsVisible(scene.objectIds, false);\n if (bcfViewpoint.components.visibility.exceptions) {\n bcfViewpoint.components.visibility.exceptions.forEach((component) => this._withBCFComponent(options, component, entity => entity.visible = true));\n }\n } else {\n scene.setObjectsVisible(scene.objectIds, true);\n if (bcfViewpoint.components.visibility.exceptions) {\n bcfViewpoint.components.visibility.exceptions.forEach((component) => this._withBCFComponent(options, component, entity => entity.visible = false));\n }\n }\n\n const view_setup_hints = bcfViewpoint.components.visibility.view_setup_hints;\n if (view_setup_hints) {\n if (view_setup_hints.spaces_visible === false) {\n scene.setObjectsVisible(viewer.metaScene.getObjectIDsByType(\"IfcSpace\"), false);\n }\n if (view_setup_hints.spaces_translucent !== undefined) {\n scene.setObjectsXRayed(viewer.metaScene.getObjectIDsByType(\"IfcSpace\"), true);\n }\n if (view_setup_hints.space_boundaries_visible !== undefined) ;\n if (view_setup_hints.openings_visible === false) {\n scene.setObjectsVisible(viewer.metaScene.getObjectIDsByType(\"IfcOpening\"), true);\n }\n if (view_setup_hints.space_boundaries_translucent !== undefined) ;\n if (view_setup_hints.openings_translucent !== undefined) {\n scene.setObjectsXRayed(viewer.metaScene.getObjectIDsByType(\"IfcOpening\"), true);\n }\n }\n }\n\n if (bcfViewpoint.components.selection) {\n scene.setObjectsSelected(scene.selectedObjectIds, false);\n bcfViewpoint.components.selection.forEach(component => this._withBCFComponent(options, component, entity => entity.selected = true));\n\n }\n\n if (bcfViewpoint.components.translucency) {\n scene.setObjectsXRayed(scene.xrayedObjectIds, false);\n bcfViewpoint.components.translucency.forEach(component => this._withBCFComponent(options, component, entity => entity.xrayed = true));\n }\n\n if (bcfViewpoint.components.coloring) {\n bcfViewpoint.components.coloring.forEach(coloring => {\n\n let color = coloring.color;\n let alpha = 0;\n let alphaDefined = false;\n\n if (color.length === 8) {\n alpha = parseInt(color.substring(0, 2), 16) / 256;\n if (alpha <= 1.0 && alpha >= 0.95) {\n alpha = 1.0;\n }\n color = color.substring(2);\n alphaDefined = true;\n }\n\n const colorize = [\n parseInt(color.substring(0, 2), 16) / 256,\n parseInt(color.substring(2, 4), 16) / 256,\n parseInt(color.substring(4, 6), 16) / 256\n ];\n\n coloring.components.map(component =>\n this._withBCFComponent(options, component, entity => {\n entity.colorize = colorize;\n if (alphaDefined) {\n entity.opacity = alpha;\n }\n }));\n });\n }\n }\n\n if (bcfViewpoint.perspective_camera || bcfViewpoint.orthogonal_camera) {\n let eye;\n let look;\n let up;\n let projection;\n\n if (bcfViewpoint.perspective_camera) {\n eye = xyzObjectToArray(bcfViewpoint.perspective_camera.camera_view_point, tempVec3$5);\n look = xyzObjectToArray(bcfViewpoint.perspective_camera.camera_direction, tempVec3$5);\n up = xyzObjectToArray(bcfViewpoint.perspective_camera.camera_up_vector, tempVec3$5);\n\n camera.perspective.fov = bcfViewpoint.perspective_camera.field_of_view;\n\n projection = \"perspective\";\n } else {\n eye = xyzObjectToArray(bcfViewpoint.orthogonal_camera.camera_view_point, tempVec3$5);\n look = xyzObjectToArray(bcfViewpoint.orthogonal_camera.camera_direction, tempVec3$5);\n up = xyzObjectToArray(bcfViewpoint.orthogonal_camera.camera_up_vector, tempVec3$5);\n\n camera.ortho.scale = bcfViewpoint.orthogonal_camera.view_to_world_scale;\n\n projection = \"ortho\";\n }\n\n math.subVec3(eye, realWorldOffset);\n\n if (camera.yUp) {\n eye = ZToY(eye);\n look = ZToY(look);\n up = ZToY(up);\n }\n\n if (rayCast) {\n const hit = scene.pick({\n pickSurface: true, // <<------ This causes picking to find the intersection point on the entity\n origin: eye,\n direction: look\n });\n look = (hit ? hit.worldPos : math.addVec3(eye, look, tempVec3$5));\n } else {\n look = math.addVec3(eye, look, tempVec3$5);\n }\n\n if (immediate) {\n camera.eye = eye;\n camera.look = look;\n camera.up = up;\n camera.projection = projection;\n } else {\n viewer.cameraFlight.flyTo({eye, look, up, duration: options.duration, projection});\n }\n }\n }\n\n _withBCFComponent(options, component, callback) {\n\n const viewer = this.viewer;\n const scene = viewer.scene;\n\n if (component.authoring_tool_id && component.originating_system === this.originatingSystem) {\n\n const id = component.authoring_tool_id;\n const entity = scene.objects[id];\n\n if (entity) {\n callback(entity);\n return\n }\n\n if (options.updateCompositeObjects) {\n const metaObject = viewer.metaScene.metaObjects[id];\n if (metaObject) {\n scene.withObjects(viewer.metaScene.getObjectIDsInSubtree(id), callback);\n return;\n }\n }\n }\n\n if (component.ifc_guid) {\n\n const originalSystemId = component.ifc_guid;\n const entity = scene.objects[originalSystemId];\n\n if (entity) {\n callback(entity);\n return;\n }\n\n if (options.updateCompositeObjects) {\n const metaObject = viewer.metaScene.metaObjects[originalSystemId];\n if (metaObject) {\n scene.withObjects(viewer.metaScene.getObjectIDsInSubtree(originalSystemId), callback);\n return;\n }\n }\n\n Object.keys(scene.models).forEach((modelId) => {\n\n const id = math.globalizeObjectId(modelId, originalSystemId);\n const entity = scene.objects[id];\n\n if (entity) {\n callback(entity);\n return;\n }\n\n if (options.updateCompositeObjects) {\n const metaObject = viewer.metaScene.metaObjects[id];\n if (metaObject) {\n scene.withObjects(viewer.metaScene.getObjectIDsInSubtree(id), callback);\n\n }\n }\n });\n }\n }\n\n /**\n * Destroys this BCFViewpointsPlugin.\n */\n destroy() {\n super.destroy();\n }\n}\n\nfunction xyzArrayToObject(arr) {\n return {\"x\": arr[0], \"y\": arr[1], \"z\": arr[2]};\n}\n\nfunction xyzObjectToArray(xyz, arry) {\n arry = new Float64Array(3);\n arry[0] = xyz.x;\n arry[1] = xyz.y;\n arry[2] = xyz.z;\n return arry;\n}\n\nfunction YToZ(vec) {\n return new Float64Array([vec[0], -vec[2], vec[1]]);\n}\n\nfunction ZToY(vec) {\n return new Float64Array([vec[0], vec[2], -vec[1]]);\n}\n\nfunction colorizeToRGB(color) {\n let rgb = \"\";\n rgb += Math.round(color[0] * 255).toString(16).padStart(2, \"0\");\n rgb += Math.round(color[1] * 255).toString(16).padStart(2, \"0\");\n rgb += Math.round(color[2] * 255).toString(16).padStart(2, \"0\");\n return rgb;\n}\n\nconst distVec3 = math.vec3();\n\nconst lengthWire = (x1, y1, x2, y2) => {\n var a = x1 - x2;\n var b = y1 - y2;\n return Math.sqrt(a * a + b * b);\n};\n\nfunction determineMeasurementOrientation(A, B, distance) {\n const yDiff = Math.abs(B[1] - A[1]);\n\n return yDiff > distance ? 'Vertical' : 'Horizontal';\n}\n\n// function findDistance\n\n/**\n * @desc Measures the distance between two 3D points.\n *\n * See {@link DistanceMeasurementsPlugin} for more info.\n */\nclass DistanceMeasurement extends Component {\n\n /**\n * @private\n */\n constructor(plugin, cfg = {}) {\n\n super(plugin.viewer.scene, cfg);\n\n /**\n * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurement.\n * @type {DistanceMeasurementsPlugin}\n */\n this.plugin = plugin;\n\n this._container = cfg.container;\n if (!this._container) {\n throw \"config missing: container\";\n }\n\n this._eventSubs = {};\n\n var scene = this.plugin.viewer.scene;\n this._originMarker = new Marker(scene, cfg.origin);\n this._targetMarker = new Marker(scene, cfg.target);\n\n this._originWorld = math.vec3();\n this._targetWorld = math.vec3();\n\n this._wp = new Float64Array(24); //world position\n this._vp = new Float64Array(24); //view position\n this._pp = new Float64Array(24);\n this._cp = new Float64Array(8); //canvas position\n\n this._xAxisLabelCulled = false;\n this._yAxisLabelCulled = false;\n this._zAxisLabelCulled = false;\n\n this._color = cfg.color || this.plugin.defaultColor;\n\n const onMouseOver = cfg.onMouseOver ? (event) => {\n cfg.onMouseOver(event, this);\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseover', event));\n } : null;\n\n const onMouseLeave = cfg.onMouseLeave ? (event) => {\n cfg.onMouseLeave(event, this);\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseleave', event));\n } : null;\n\n const onMouseDown = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mousedown', event));\n } ;\n\n const onMouseUp = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseup', event));\n };\n\n const onMouseMove = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mousemove', event));\n };\n\n const onContextMenu = cfg.onContextMenu ? (event) => {\n cfg.onContextMenu(event, this);\n } : null;\n\n const onMouseWheel = (event) => {\n this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new WheelEvent('wheel', event));\n };\n\n this._originDot = new Dot(this._container, {\n fillColor: this._color,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 2 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._targetDot = new Dot(this._container, {\n fillColor: this._color,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 2 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._lengthWire = new Wire(this._container, {\n color: this._color,\n thickness: 2,\n thicknessClickable: 6,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 1 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._xAxisWire = new Wire(this._container, {\n color: \"#FF0000\",\n thickness: 1,\n thicknessClickable: 6,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 1 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._yAxisWire = new Wire(this._container, {\n color: \"green\",\n thickness: 1,\n thicknessClickable: 6,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 1 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._zAxisWire = new Wire(this._container, {\n color: \"blue\",\n thickness: 1,\n thicknessClickable: 6,\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 1 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._lengthLabel = new Label(this._container, {\n fillColor: this._color,\n prefix: \"\",\n text: \"\",\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 4 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._xAxisLabel = new Label(this._container, {\n fillColor: \"red\",\n prefix: \"X\",\n text: \"\",\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 3 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._yAxisLabel = new Label(this._container, {\n fillColor: \"green\",\n prefix: \"Y\",\n text: \"\",\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 3 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._zAxisLabel = new Label(this._container, {\n fillColor: \"blue\",\n prefix: \"Z\",\n text: \"\",\n zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 3 : undefined,\n onMouseOver,\n onMouseLeave,\n onMouseWheel,\n onMouseDown,\n onMouseUp,\n onMouseMove,\n onContextMenu\n });\n\n this._measurementOrientation = 'Horizontal';\n this._wpDirty = false;\n this._vpDirty = false;\n this._cpDirty = false;\n this._sectionPlanesDirty = true;\n\n this._visible = false;\n this._originVisible = false;\n this._targetVisible = false;\n this._wireVisible = false;\n this._axisVisible = false;\n this._xAxisVisible = false;\n this._yAxisVisible = false;\n this._zAxisVisible = false;\n this._axisEnabled = true;\n this._xLabelEnabled = false;\n this._yLabelEnabled = false;\n this._zLabelEnabled = false;\n this._lengthLabelEnabled = false;\n this._labelsVisible = false;\n this._labelsOnWires = false;\n this._clickable = false;\n\n this._originMarker.on(\"worldPos\", (value) => {\n this._originWorld.set(value || [0,0,0]); \n this._wpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._targetMarker.on(\"worldPos\", (value) => {\n this._targetWorld.set(value || [0,0,0]); \n this._wpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._onViewMatrix = scene.camera.on(\"viewMatrix\", () => {\n this._vpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._onProjMatrix = scene.camera.on(\"projMatrix\", () => {\n this._cpDirty = true;\n this._needUpdate();\n });\n\n this._onCanvasBoundary = scene.canvas.on(\"boundary\", () => {\n this._cpDirty = true;\n this._needUpdate(0); // No lag\n });\n\n this._onMetricsUnits = scene.metrics.on(\"units\", () => {\n this._cpDirty = true;\n this._needUpdate();\n });\n\n this._onMetricsScale = scene.metrics.on(\"scale\", () => {\n this._cpDirty = true;\n this._needUpdate();\n });\n\n this._onMetricsOrigin = scene.metrics.on(\"origin\", () => {\n this._cpDirty = true;\n this._needUpdate();\n });\n\n this._onSectionPlaneUpdated = scene.on(\"sectionPlaneUpdated\", () =>{\n this._sectionPlanesDirty = true;\n this._needUpdate();\n });\n\n this.approximate = cfg.approximate;\n this.visible = cfg.visible;\n this.originVisible = cfg.originVisible;\n this.targetVisible = cfg.targetVisible;\n this.wireVisible = cfg.wireVisible;\n this.axisVisible = cfg.axisVisible;\n this.xAxisVisible = cfg.xAxisVisible;\n this.yAxisVisible = cfg.yAxisVisible;\n this.zAxisVisible = cfg.zAxisVisible;\n this.xLabelEnabled = cfg.xLabelEnabled;\n this.yLabelEnabled = cfg.yLabelEnabled;\n this.zLabelEnabled = cfg.zLabelEnabled;\n this.lengthLabelEnabled = cfg.lengthLabelEnabled;\n this.labelsVisible = cfg.labelsVisible;\n this.labelsOnWires = cfg.labelsOnWires;\n this.useRotationAdjustment = cfg.useRotationAdjustment;\n }\n\n _update() {\n\n if (!this._visible) {\n return;\n }\n\n const scene = this.plugin.viewer.scene;\n\n if (this._wpDirty) {\n\n this._measurementOrientation = determineMeasurementOrientation(this._originWorld, this._targetWorld, 1);\n if(this._measurementOrientation === 'Vertical' && this.useRotationAdjustment){\n this._wp[0] = this._originWorld[0];\n this._wp[1] = this._originWorld[1];\n this._wp[2] = this._originWorld[2];\n this._wp[3] = 1.0;\n\n this._wp[4] = this._originWorld[0]; //x-axis\n this._wp[5] = this._originWorld[1];\n this._wp[6] = this._originWorld[2];\n this._wp[7] = 1.0;\n\n this._wp[8] = this._originWorld[0]; //x-axis\n this._wp[9] = this._targetWorld[1]; //y-axis\n this._wp[10] = this._originWorld[2];\n this._wp[11] = 1.0;\n\n this._wp[12] = this._targetWorld[0];\n this._wp[13] = this._targetWorld[1];\n this._wp[14] = this._targetWorld[2];\n this._wp[15] = 1.0;\n }\n else {\n this._wp[0] = this._originWorld[0];\n this._wp[1] = this._originWorld[1];\n this._wp[2] = this._originWorld[2];\n this._wp[3] = 1.0;\n\n this._wp[4] = this._targetWorld[0];\n this._wp[5] = this._originWorld[1];\n this._wp[6] = this._originWorld[2];\n this._wp[7] = 1.0;\n\n this._wp[8] = this._targetWorld[0];\n this._wp[9] = this._targetWorld[1];\n this._wp[10] = this._originWorld[2];\n this._wp[11] = 1.0;\n\n this._wp[12] = this._targetWorld[0];\n this._wp[13] = this._targetWorld[1];\n this._wp[14] = this._targetWorld[2];\n this._wp[15] = 1.0;\n }\n \n\n this._wpDirty = false;\n this._vpDirty = true;\n }\n\n if (this._vpDirty) {\n\n math.transformPositions4(scene.camera.viewMatrix, this._wp, this._vp);\n\n this._vp[3] = 1.0;\n this._vp[7] = 1.0;\n this._vp[11] = 1.0;\n this._vp[15] = 1.0;\n\n this._vpDirty = false;\n this._cpDirty = true;\n }\n\n if (this._sectionPlanesDirty) {\n\n if (this._isSliced(this._originWorld) || this._isSliced(this._targetWorld)) {\n this._xAxisLabel.setCulled(true);\n this._yAxisLabel.setCulled(true);\n this._zAxisLabel.setCulled(true);\n this._lengthLabel.setCulled(true);\n this._xAxisWire.setCulled(true);\n this._yAxisWire.setCulled(true);\n this._zAxisWire.setCulled(true);\n this._lengthWire.setCulled(true);\n this._originDot.setCulled(true);\n this._targetDot.setCulled(true);\n return;\n } else {\n this._xAxisLabel.setCulled(false);\n this._yAxisLabel.setCulled(false);\n this._zAxisLabel.setCulled(false);\n this._lengthLabel.setCulled(false);\n this._xAxisWire.setCulled(false);\n this._yAxisWire.setCulled(false);\n this._zAxisWire.setCulled(false);\n this._lengthWire.setCulled(false);\n this._originDot.setCulled(false);\n this._targetDot.setCulled(false);\n }\n\n this._sectionPlanesDirty = true;\n }\n\n const near = -0.3;\n const vpz1 = this._originMarker.viewPos[2];\n const vpz2 = this._targetMarker.viewPos[2];\n\n if (vpz1 > near || vpz2 > near) {\n\n this._xAxisLabel.setCulled(true);\n this._yAxisLabel.setCulled(true);\n this._zAxisLabel.setCulled(true);\n this._lengthLabel.setCulled(true);\n\n this._xAxisWire.setVisible(false);\n this._yAxisWire.setVisible(false);\n this._zAxisWire.setVisible(false);\n this._lengthWire.setVisible(false);\n\n this._originDot.setVisible(false);\n this._targetDot.setVisible(false);\n\n return;\n }\n\n if (this._cpDirty) {\n\n math.transformPositions4(scene.camera.project.matrix, this._vp, this._pp);\n\n var pp = this._pp;\n var cp = this._cp;\n\n var canvas = scene.canvas.canvas;\n var offsets = canvas.getBoundingClientRect();\n const containerOffsets = this._container.getBoundingClientRect();\n var top = offsets.top - containerOffsets.top;\n var left = offsets.left - containerOffsets.left;\n var aabb = scene.canvas.boundary;\n var canvasWidth = aabb[2];\n var canvasHeight = aabb[3];\n var j = 0;\n\n const metrics = this.plugin.viewer.scene.metrics;\n const scale = metrics.scale;\n const units = metrics.units;\n const unitInfo = metrics.unitsInfo[units];\n const unitAbbrev = unitInfo.abbrev;\n\n for (var i = 0, len = pp.length; i < len; i += 4) {\n cp[j] = left + Math.floor((1 + pp[i + 0] / pp[i + 3]) * canvasWidth / 2);\n cp[j + 1] = top + Math.floor((1 - pp[i + 1] / pp[i + 3]) * canvasHeight / 2);\n j += 2;\n }\n\n this._originDot.setPos(cp[0], cp[1]);\n this._targetDot.setPos(cp[6], cp[7]);\n\n this._lengthWire.setStartAndEnd(cp[0], cp[1], cp[6], cp[7]);\n\n this._xAxisWire.setStartAndEnd(cp[0], cp[1], cp[2], cp[3]);\n this._yAxisWire.setStartAndEnd(cp[2], cp[3], cp[4], cp[5]);\n this._zAxisWire.setStartAndEnd(cp[4], cp[5], cp[6], cp[7]);\n\n if (!this.labelsVisible) {\n\n this._lengthLabel.setCulled(true);\n\n this._xAxisLabel.setCulled(true);\n this._yAxisLabel.setCulled(true);\n this._zAxisLabel.setCulled(true);\n\n } else {\n\n this._lengthLabel.setPosOnWire(cp[0], cp[1], cp[6], cp[7]);\n\n if (this.labelsOnWires) {\n this._xAxisLabel.setPosOnWire(cp[0], cp[1], cp[2], cp[3]);\n this._yAxisLabel.setPosOnWire(cp[2], cp[3], cp[4], cp[5]);\n this._zAxisLabel.setPosOnWire(cp[4], cp[5], cp[6], cp[7]);\n } else {\n const labelOffset = 35;\n let currentLabelOffset = labelOffset;\n this._xAxisLabel.setPosOnWire(cp[0], cp[1] + currentLabelOffset, cp[6], cp[7] + currentLabelOffset);\n currentLabelOffset += labelOffset;\n this._yAxisLabel.setPosOnWire(cp[0], cp[1] + currentLabelOffset, cp[6], cp[7] + currentLabelOffset);\n currentLabelOffset += labelOffset;\n this._zAxisLabel.setPosOnWire(cp[0], cp[1] + currentLabelOffset, cp[6], cp[7] + currentLabelOffset);\n }\n\n const tilde = this._approximate ? \" ~ \" : \" = \";\n\n this._length = Math.abs(math.lenVec3(math.subVec3(this._targetWorld, this._originWorld, distVec3)));\n this._lengthLabel.setText(tilde + (this._length * scale).toFixed(2) + unitAbbrev);\n\n const xAxisCanvasLength = Math.abs(lengthWire(cp[0], cp[1], cp[2], cp[3]));\n const yAxisCanvasLength = Math.abs(lengthWire(cp[2], cp[3], cp[4], cp[5]));\n const zAxisCanvasLength = Math.abs(lengthWire(cp[4], cp[5], cp[6], cp[7]));\n\n const labelMinAxisLength = this.plugin.labelMinAxisLength;\n\n if (this.labelsOnWires){\n this._xAxisLabelCulled = (xAxisCanvasLength < labelMinAxisLength);\n this._yAxisLabelCulled = (yAxisCanvasLength < labelMinAxisLength);\n this._zAxisLabelCulled = (zAxisCanvasLength < labelMinAxisLength);\n } else {\n this._xAxisLabelCulled = false;\n this._yAxisLabelCulled = false;\n this._zAxisLabelCulled = false;\n }\n\n if (!this._xAxisLabelCulled) {\n this._xAxisLabel.setText(tilde + Math.abs((this._targetWorld[0] - this._originWorld[0]) * scale).toFixed(2) + unitAbbrev);\n this._xAxisLabel.setCulled(!this.axisVisible);\n } else {\n this._xAxisLabel.setCulled(true);\n }\n\n if (!this._yAxisLabelCulled) {\n this._yAxisLabel.setText(tilde + Math.abs((this._targetWorld[1] - this._originWorld[1]) * scale).toFixed(2) + unitAbbrev);\n this._yAxisLabel.setCulled(!this.axisVisible);\n } else {\n this._yAxisLabel.setCulled(true);\n }\n\n if (!this._zAxisLabelCulled) {\n if(this._measurementOrientation === 'Vertical') {\n this._zAxisLabel.setPrefix(\"\");\n this._zAxisLabel.setText(tilde + Math.abs(math.lenVec3(math.subVec3(this._targetWorld, [this._originWorld[0], this._targetWorld[1], this._originWorld[2]], distVec3)) * scale).toFixed(2) + unitAbbrev);\n }\n else {\n this._zAxisLabel.setPrefix(\"Z\");\n this._zAxisLabel.setText(tilde + Math.abs((this._targetWorld[2] - this._originWorld[2]) * scale).toFixed(2) + unitAbbrev);\n }\n this._zAxisLabel.setCulled(!this.axisVisible);\n } else {\n this._zAxisLabel.setCulled(true);\n }\n }\n\n // this._xAxisLabel.setVisible(this.axisVisible && this.xAxisVisible);\n // this._yAxisLabel.setVisible(this.axisVisible && this.yAxisVisible);\n // this._zAxisLabel.setVisible(this.axisVisible && this.zAxisVisible);\n // this._lengthLabel.setVisible(false);\n\n this._originDot.setVisible(this._visible && this._originVisible);\n this._targetDot.setVisible(this._visible && this._targetVisible);\n\n this._xAxisWire.setVisible(this.axisVisible && this.xAxisVisible);\n this._yAxisWire.setVisible(this.axisVisible && this.yAxisVisible);\n this._zAxisWire.setVisible(this.axisVisible && this.zAxisVisible);\n\n this._lengthWire.setVisible(this.wireVisible);\n this._lengthLabel.setCulled(!this.wireVisible);\n\n this._cpDirty = false;\n }\n }\n\n _isSliced(positions) {\n const sectionPlanes = this.scene._sectionPlanesState.sectionPlanes;\n for (let i = 0, len = sectionPlanes.length; i < len; i++) {\n const sectionPlane = sectionPlanes[i];\n if (math.planeClipsPositions3(sectionPlane.pos, sectionPlane.dir, positions, 4)) {\n return true\n }\n }\n return false;\n }\n\n /**\n * Sets whether this DistanceMeasurement indicates that its measurement is approximate.\n *\n * This is ````true```` by default.\n *\n * @type {Boolean}\n */\n set approximate(approximate) {\n approximate = approximate !== false;\n if (this._approximate === approximate) {\n return;\n }\n this._approximate = approximate;\n this._cpDirty = true;\n this._needUpdate(0);\n }\n\n /**\n * Gets whether this DistanceMeasurement indicates that its measurement is approximate.\n *\n * This is ````true```` by default.\n *\n * @type {Boolean}\n */\n get approximate() {\n return this._approximate;\n }\n\n /**\n * Gets the origin {@link Marker}.\n *\n * @type {Marker}\n */\n get origin() {\n return this._originMarker;\n }\n\n /**\n * Gets the target {@link Marker}.\n *\n * @type {Marker}\n */\n get target() {\n return this._targetMarker;\n }\n\n /**\n * Gets the World-space direct point-to-point distance between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target}.\n *\n * @type {Number}\n */\n get length() {\n this._update();\n const scale = this.plugin.viewer.scene.metrics.scale;\n return this._length * scale;\n }\n\n get color() {\n return this._color;\n }\n\n set color(value) {\n this._color = value;\n this._originDot.setFillColor(value);\n this._targetDot.setFillColor(value);\n this._lengthWire.setColor(value);\n this._lengthLabel.setFillColor(value);\n }\n\n /**\n * Sets whether this DistanceMeasurement is visible or not.\n *\n * @type {Boolean}\n */\n set visible(value) {\n\n value = value !== undefined ? Boolean(value) : this.plugin.defaultVisible;\n\n this._visible = value;\n\n this._originDot.setVisible(this._visible && this._originVisible);\n this._targetDot.setVisible(this._visible && this._targetVisible);\n this._lengthWire.setVisible(this._visible && this._wireVisible);\n this._lengthLabel.setVisible(this._visible && this._wireVisible && this._lengthLabelEnabled);\n\n const xAxisVisible = this._visible && this._axisVisible && this._xAxisVisible;\n const yAxisVisible = this._visible && this._axisVisible && this._yAxisVisible;\n const zAxisVisible = this._visible && this._axisVisible && this._zAxisVisible;\n\n this._xAxisWire.setVisible(xAxisVisible);\n this._yAxisWire.setVisible(yAxisVisible);\n this._zAxisWire.setVisible(zAxisVisible);\n\n this._xAxisLabel.setVisible(xAxisVisible && !this._xAxisLabelCulled && this._EnabledVisible);\n this._yAxisLabel.setVisible(yAxisVisible && !this._yAxisLabelCulled && this._yLabelEnabled);\n this._zAxisLabel.setVisible(zAxisVisible && !this._zAxisLabelCulled && this._zLabelEnabled);\n\n this._cpDirty = true;\n\n this._needUpdate();\n }\n\n /**\n * Gets whether this DistanceMeasurement is visible or not.\n *\n * @type {Boolean}\n */\n get visible() {\n return this._visible;\n }\n\n /**\n * Sets if the origin {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n set originVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultOriginVisible;\n this._originVisible = value;\n this._originDot.setVisible(this._visible && this._originVisible);\n }\n\n /**\n * Gets if the origin {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n get originVisible() {\n return this._originVisible;\n }\n\n /**\n * Sets if the target {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n set targetVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultTargetVisible;\n this._targetVisible = value;\n this._targetDot.setVisible(this._visible && this._targetVisible);\n }\n\n /**\n * Gets if the target {@link Marker} is visible.\n *\n * @type {Boolean}\n */\n get targetVisible() {\n return this._targetVisible;\n }\n\n /**\n * Sets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are enabled.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n set axisEnabled(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultAxisVisible;\n this._axisEnabled = value;\n var axisVisible = this._visible && this._axisVisible && this._axisEnabled;\n this._xAxisWire.setVisible(axisVisible && this._xAxisVisible);\n this._yAxisWire.setVisible(axisVisible && this._yAxisVisible);\n this._zAxisWire.setVisible(axisVisible && this._zAxisVisible);\n this._xAxisLabel.setVisible(axisVisible && !this._xAxisLabelCulled&& this._xAxisVisible && this._xLabelEnabled);\n this._yAxisLabel.setVisible(axisVisible && !this._yAxisLabelCulled&& this._xAxisVisible && this._yLabelEnabled);\n this._zAxisLabel.setVisible(axisVisible && !this._zAxisLabelCulled&& this._xAxisVisible && this._zLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are enabled.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n get axisEnabled() {\n return this._axisEnabled;\n }\n\n /**\n * Sets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n set axisVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultAxisVisible;\n this._axisVisible = value;\n var axisVisible = this._visible && this._axisVisible && this._axisEnabled;\n this._xAxisWire.setVisible(axisVisible && this._xAxisVisible);\n this._yAxisWire.setVisible(axisVisible && this._yAxisVisible);\n this._zAxisWire.setVisible(axisVisible && this._zAxisVisible);\n this._xAxisLabel.setVisible(axisVisible && !this._xAxisLabelCulled&& this._xAxisVisible);\n this._yAxisLabel.setVisible(axisVisible && !this._yAxisLabelCulled&& this._yAxisVisible);\n this._zAxisLabel.setVisible(axisVisible && !this._zAxisLabelCulled&& this._zAxisVisible);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n get axisVisible() {\n return this._axisVisible;\n }\n\n /**\n * Sets if the X-axis-aligned wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n set xAxisVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultAxisVisible;\n this._xAxisVisible = value;\n const axisVisible = this._visible && this._axisVisible && this._xAxisVisible && this._axisEnabled;\n this._xAxisWire.setVisible(axisVisible);\n this._xAxisLabel.setVisible(axisVisible && !this._xAxisLabelCulled && this._xLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the X-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n get xAxisVisible() {\n return this._xAxisVisible;\n }\n\n /**\n * Sets if the Y-axis-aligned wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n set yAxisVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultAxisVisible;\n this._yAxisVisible = value;\n const axisVisible = this._visible && this._axisVisible && this._yAxisVisible && this._axisEnabled;\n this._yAxisWire.setVisible(axisVisible);\n this._yAxisLabel.setVisible(axisVisible && !this._yAxisLabelCulled && this._yLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the Y-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n get yAxisVisible() {\n return this._yAxisVisible;\n }\n\n /**\n * Sets if the Z-axis-aligned wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n set zAxisVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultAxisVisible;\n this._zAxisVisible = value;\n const axisVisible = this._visible && this._axisVisible && this._zAxisVisible && this._axisEnabled;\n this._zAxisWire.setVisible(axisVisible);\n this._zAxisLabel.setVisible(axisVisible && !this._zAxisLabelCulled && this._zLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the Z-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible.\n *\n * Wires are only shown if enabled and visible.\n *\n * @type {Boolean}\n */\n get zAxisVisible() {\n return this._zAxisVisible;\n }\n\n /**\n * Sets if the direct point-to-point wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible.\n *\n * @type {Boolean}\n */\n set wireVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultWireVisible;\n this._wireVisible = value;\n var wireVisible = this._visible && this._wireVisible;\n this._lengthLabel.setVisible(wireVisible && this._lengthLabelEnabled);\n this._lengthWire.setVisible(wireVisible);\n }\n\n /**\n * Gets if the direct point-to-point wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible.\n *\n * @type {Boolean}\n */\n get wireVisible() {\n return this._wireVisible;\n }\n\n /**\n * Sets if the labels are visible except the length label.\n *\n * @type {Boolean}\n */\n set labelsVisible(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultLabelsVisible;\n this._labelsVisible = value;\n var labelsVisible = this._visible && this._labelsVisible;\n this._xAxisLabel.setVisible(labelsVisible && !this._xAxisLabelCulled && this._clickable && this._axisEnabled && this._xLabelEnabled);\n this._yAxisLabel.setVisible(labelsVisible && !this._yAxisLabelCulled && this._clickable && this._axisEnabled && this._yLabelEnabled);\n this._zAxisLabel.setVisible(labelsVisible && !this._zAxisLabelCulled && this._clickable && this._axisEnabled && this._zLabelEnabled);\n this._lengthLabel.setVisible(labelsVisible && this._lengthLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the labels are visible.\n *\n * @type {Boolean}\n */\n get labelsVisible() {\n return this._labelsVisible;\n }\n\n /**\n * Sets if the x label is enabled.\n *\n * @type {Boolean}\n */\n set xLabelEnabled(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultXLabelEnabled;\n this._xLabelEnabled = value;\n var labelsVisible = this._visible && this._labelsVisible;\n this._xAxisLabel.setVisible(labelsVisible && !this._xAxisLabelCulled && this._clickable && this._axisEnabled && this._xLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the x label is enabled.\n *\n * @type {Boolean}\n */\n get xLabelEnabled(){\n return this._xLabelEnabled;\n }\n\n /**\n * Sets if the y label is enabled.\n *\n * @type {Boolean}\n */\n set yLabelEnabled(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultYLabelEnabled;\n this._yLabelEnabled = value;\n var labelsVisible = this._visible && this._labelsVisible;\n this._yAxisLabel.setVisible(labelsVisible && !this._yAxisLabelCulled && this._clickable && this._axisEnabled && this._yLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the y label is enabled.\n *\n * @type {Boolean}\n */\n get yLabelEnabled(){\n return this._yLabelEnabled;\n }\n\n /**\n * Sets if the z label is enabled.\n *\n * @type {Boolean}\n */\n set zLabelEnabled(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultZLabelEnabled;\n this._zLabelEnabled = value;\n var labelsVisible = this._visible && this._labelsVisible;\n this._zAxisLabel.setVisible(labelsVisible && !this._zAxisLabelCulled && this._clickable && this._axisEnabled && this._zLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the z label is enabled.\n *\n * @type {Boolean}\n */\n get zLabelEnabled(){\n return this._zLabelEnabled;\n }\n\n /**\n * Sets if the length label is enabled.\n *\n * @type {Boolean}\n */\n set lengthLabelEnabled(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultLengthLabelEnabled;\n this._lengthLabelEnabled = value;\n var labelsVisible = this._visible && this._labelsVisible;\n this._lengthLabel.setVisible(labelsVisible && !this._lengthAxisLabelCulled && this._clickable && this._axisEnabled && this._lengthLabelEnabled);\n this._cpDirty = true;\n this._needUpdate();\n }\n\n /**\n * Gets if the length label is enabled.\n *\n * @type {Boolean}\n */\n get lengthLabelEnabled(){\n return this._lengthLabelEnabled;\n }\n\n /**\n * Sets if labels should be positioned on the wires.\n *\n * @type {Boolean}\n */\n set labelsOnWires(value) {\n value = value !== undefined ? Boolean(value) : this.plugin.defaultLabelsOnWires;\n this._labelsOnWires = value;\n }\n\n /**\n * Gets if labels should be positioned on the wires.\n *\n * @type {Boolean}\n */\n get labelsOnWires() {\n return this._labelsOnWires;\n }\n\n /**\n * Sets if this DistanceMeasurement appears highlighted.\n * @param highlighted\n */\n setHighlighted(highlighted) {\n this._originDot.setHighlighted(highlighted);\n this._targetDot.setHighlighted(highlighted);\n this._xAxisWire.setHighlighted(highlighted);\n this._yAxisWire.setHighlighted(highlighted);\n this._zAxisWire.setHighlighted(highlighted);\n this._xAxisLabel.setHighlighted(highlighted);\n this._yAxisLabel.setHighlighted(highlighted);\n this._zAxisLabel.setHighlighted(highlighted);\n this._lengthWire.setHighlighted(highlighted);\n this._lengthLabel.setHighlighted(highlighted);\n }\n\n /**\n * Sets if the wires, dots ad labels will fire \"mouseOver\" \"mouseLeave\" and \"contextMenu\" events, or ignore mouse events altogether.\n *\n * @type {Boolean}\n */\n set clickable(value) {\n value = !!value;\n this._clickable = value;\n this._originDot.setClickable(this._clickable);\n this._targetDot.setClickable(this._clickable);\n this._xAxisWire.setClickable(this._clickable);\n this._yAxisWire.setClickable(this._clickable);\n this._zAxisWire.setClickable(this._clickable);\n this._lengthWire.setClickable(this._clickable);\n this._xAxisLabel.setClickable(this._clickable);\n this._yAxisLabel.setClickable(this._clickable);\n this._zAxisLabel.setClickable(this._clickable);\n this._lengthLabel.setClickable(this._clickable);\n }\n\n /**\n * Gets if the wires, dots ad labels will fire \"mouseOver\" \"mouseLeave\" and \"contextMenu\" events.\n *\n * @type {Boolean}\n */\n get clickable() {\n return this._clickable;\n }\n\n /**\n * @private\n */\n destroy() {\n\n const scene = this.plugin.viewer.scene;\n const metrics = scene.metrics;\n\n if (this._onViewMatrix) {\n scene.camera.off(this._onViewMatrix);\n }\n if (this._onProjMatrix) {\n scene.camera.off(this._onProjMatrix);\n }\n if (this._onCanvasBoundary) {\n scene.canvas.off(this._onCanvasBoundary);\n }\n if (this._onMetricsUnits) {\n metrics.off(this._onMetricsUnits);\n }\n if (this._onMetricsScale) {\n metrics.off(this._onMetricsScale);\n }\n if (this._onMetricsOrigin) {\n metrics.off(this._onMetricsOrigin);\n }\n if (this._onSectionPlaneUpdated) {\n scene.off(this._onSectionPlaneUpdated);\n }\n\n this._originDot.destroy();\n this._targetDot.destroy();\n this._xAxisWire.destroy();\n this._yAxisWire.destroy();\n this._zAxisWire.destroy();\n this._lengthLabel.destroy();\n this._xAxisLabel.destroy();\n this._yAxisLabel.destroy();\n this._zAxisLabel.destroy();\n this._lengthWire.destroy();\n\n super.destroy();\n }\n}\n\n/**\n * Creates {@link DistanceMeasurement}s in a {@link DistanceMeasurementsPlugin} from user input.\n *\n * @interface\n * @abstract\n */\nclass DistanceMeasurementsControl extends Component {\n\n /**\n * Gets if this DistanceMeasurementsControl is currently active, where it is responding to input.\n *\n * @returns {boolean} True if this DistanceMeasurementsControl is active.\n * @abstract\n */\n get active() {\n }\n\n /**\n * Sets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsControl.\n *\n * This is `true` by default.\n *\n * Internally, this deactivates then activates the DistanceMeasurementsControl when changed, which means that\n * it will destroy any DistanceMeasurements currently under construction, and incurs some overhead, since it unbinds\n * and rebinds various input handlers.\n *\n * @param {boolean} snapping Whether to enable snap-to-vertex and snap-edge for this DistanceMeasurementsControl.\n */\n set snapping(snapping) {\n }\n\n /**\n * Gets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsControl.\n *\n * This is `true` by default.\n *\n * @returns {boolean} Whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsControl.\n */\n get snapping() {\n return true;\n }\n\n /**\n * Activates this DistanceMeasurementsControl, ready to respond to input.\n *\n * @abstract\n */\n activate() {\n }\n\n /**\n * Deactivates this DistanceMeasurementsControl, making it unresponsive to input.\n *\n * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsControl.\n *\n * @abstract\n */\n deactivate() {\n }\n\n /**\n * Resets this DistanceMeasurementsControl.\n *\n * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsControl.\n *\n * Does nothing if the DistanceMeasurementsControl is not active.\n *\n * @abstract\n */\n reset() {\n }\n\n /**\n * Gets the {@link DistanceMeasurement} under construction by this DistanceMeasurementsControl, if any.\n *\n * @returns {null|DistanceMeasurement}\n *\n * @abstract\n */\n get currentMeasurement() {\n return null;\n }\n\n /**\n * Destroys this DistanceMeasurementsControl.\n *\n * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsControl.\n *\n * @abstract\n */\n destroy() {\n }\n}\n\nconst MOUSE_FIRST_CLICK_EXPECTED = 0;\n\n/**\n * Creates {@link DistanceMeasurement}s in a {@link DistanceMeasurementsPlugin} from mouse input.\n *\n * ## Usage\n *\n * [[Run example](/examples/measurement/#distance_createWithMouse_snapping)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, DistanceMeasurementsPlugin, DistanceMeasurementsMouseControl, PointerLens} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * });\n *\n * viewer.camera.eye = [-3.93, 2.85, 27.01];\n * viewer.camera.look = [4.40, 3.72, 8.89];\n * viewer.camera.up = [-0.01, 0.99, 0.039];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const sceneModel = xktLoader.load({\n * id: \"myModel\",\n * src: \"Duplex.xkt\"\n * });\n *\n * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);\n *\n * const distanceMeasurementsControl = new DistanceMeasurementsMouseControl(DistanceMeasurements, {\n * pointerLens: new PointerLens(viewer)\n * })\n *\n * distanceMeasurementsControl.snapping = true;\n *\n * distanceMeasurementsControl.activate();\n * ````\n */\nclass DistanceMeasurementsMouseControl extends DistanceMeasurementsControl {\n\n /**\n * Creates a DistanceMeasurementsMouseControl bound to the given DistanceMeasurementsPlugin.\n *\n * @param {DistanceMeasurementsPlugin} distanceMeasurementsPlugin The AngleMeasurementsPlugin to control.\n * @param [cfg] Configuration\n * @param {function} [cfg.canvasToPagePos] Optional function to map canvas-space coordinates to page coordinates.\n * @param {PointerLens} [cfg.pointerLens] A PointerLens to use to provide a magnified view of the cursor when snapping is enabled.\n * @param {boolean} [cfg.snapping=true] Whether to initially enable snap-to-vertex and snap-to-edge for this DistanceMeasurementsMouseControl.\n */\n constructor(distanceMeasurementsPlugin, cfg = {}) {\n\n super(distanceMeasurementsPlugin.viewer.scene);\n\n this._canvasToPagePos = cfg.canvasToPagePos;\n\n this.pointerLens = cfg.pointerLens;\n\n this._active = false;\n\n this._currentDistanceMeasurement = null;\n\n this._currentDistanceMeasurementInitState = {\n wireVisible: null,\n axisVisible: null,\n xAxisVisible: null,\n yaxisVisible: null,\n zAxisVisible: null,\n targetVisible: null,\n };\n\n this._initMarkerDiv();\n\n this._onCameraControlHoverSnapOrSurface = null;\n this._onCameraControlHoverSnapOrSurfaceOff = null;\n this._onMouseDown = null;\n this._onMouseUp = null;\n this._onCanvasTouchStart = null;\n this._onCanvasTouchEnd = null;\n this._snapping = cfg.snapping !== false;\n this._mouseState = MOUSE_FIRST_CLICK_EXPECTED;\n\n this._attachPlugin(distanceMeasurementsPlugin, cfg);\n }\n\n _initMarkerDiv() {\n const markerDiv = document.createElement('div');\n markerDiv.setAttribute('id', 'myMarkerDiv');\n const canvas = this.scene.canvas.canvas;\n canvas.parentNode.insertBefore(markerDiv, canvas);\n markerDiv.style.background = \"black\";\n markerDiv.style.border = \"2px solid blue\";\n markerDiv.style.borderRadius = \"10px\";\n markerDiv.style.width = \"5px\";\n markerDiv.style.height = \"5px\";\n markerDiv.style.top = \"-200px\";\n markerDiv.style.left = \"-200px\";\n markerDiv.style.margin = \"0 0\";\n markerDiv.style.zIndex = \"100\";\n markerDiv.style.position = \"absolute\";\n markerDiv.style.pointerEvents = \"none\";\n\n this._markerDiv = markerDiv;\n }\n\n _destroyMarkerDiv() {\n if (this._markerDiv) {\n const element = document.getElementById('myMarkerDiv');\n element.parentNode.removeChild(element);\n this._markerDiv = null;\n }\n }\n\n _attachPlugin(distanceMeasurementsPlugin, cfg = {}) {\n\n /**\n * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurementsMouseControl.\n * @type {DistanceMeasurementsPlugin}\n */\n this.distanceMeasurementsPlugin = distanceMeasurementsPlugin;\n\n /**\n * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurementsMouseControl.\n * @type {DistanceMeasurementsPlugin}\n */\n this.plugin = distanceMeasurementsPlugin;\n }\n\n /**\n * Gets if this DistanceMeasurementsMouseControl is currently active, where it is responding to input.\n *\n * @returns {boolean} True if this DistanceMeasurementsMouseControl is active.\n */\n get active() {\n return this._active;\n }\n\n /**\n * Sets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsMouseControl.\n *\n * This is `true` by default.\n *\n * Internally, this deactivates then activates the DistanceMeasurementsMouseControl when changed, which means that\n * it will destroy any DistanceMeasurements currently under construction, and incurs some overhead, since it unbinds\n * and rebinds various input handlers.\n *\n * @param snapping\n */\n set snapping(snapping) {\n if (snapping !== this._snapping) {\n this._snapping = snapping;\n this.deactivate();\n this.activate();\n } else {\n this._snapping = snapping;\n }\n }\n\n /**\n * Gets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsMouseControl.\n *\n * This is `true` by default.\n * @returns {*}\n */\n get snapping() {\n return this._snapping;\n }\n \n /**\n * Activates this DistanceMeasurementsMouseControl, ready to respond to input.\n */\n activate() {\n\n if (this._active) {\n return;\n }\n\n if (!this._markerDiv) {\n this._initMarkerDiv();\n }\n\n this.fire(\"activated\", true);\n\n const distanceMeasurementsPlugin = this.distanceMeasurementsPlugin;\n const scene = this.scene;\n const cameraControl = distanceMeasurementsPlugin.viewer.cameraControl;\n const canvas = scene.canvas.canvas;\n scene.input;\n let mouseHovering = false;\n const pointerWorldPos = math.vec3();\n const pointerCanvasPos = math.vec2();\n let pointerDownCanvasX;\n let pointerDownCanvasY;\n const clickTolerance = 20;\n let hoveredEntity = null;\n\n this._mouseState = MOUSE_FIRST_CLICK_EXPECTED;\n\n const getTop = el => el.offsetTop + (el.offsetParent && (el.offsetParent !== canvas.parentNode) && getTop(el.offsetParent));\n const getLeft = el => el.offsetLeft + (el.offsetParent && (el.offsetParent !== canvas.parentNode) && getLeft(el.offsetParent));\n\n const pagePos = math.vec2();\n \n this._onCameraControlHoverSnapOrSurface = cameraControl.on(\n this._snapping\n ? \"hoverSnapOrSurface\"\n : \"hoverSurface\", event => {\n const canvasPos = event.snappedCanvasPos || event.canvasPos;\n mouseHovering = true;\n pointerWorldPos.set(event.worldPos);\n pointerCanvasPos.set(event.canvasPos);\n if (this._mouseState === MOUSE_FIRST_CLICK_EXPECTED) {\n\n if (this._canvasToPagePos) {\n this._canvasToPagePos(canvas, canvasPos, pagePos);\n this._markerDiv.style.left = `${pagePos[0] - 5}px`;\n this._markerDiv.style.top = `${pagePos[1] - 5}px`;\n } else {\n this._markerDiv.style.left = `${getLeft(canvas) + canvasPos[0] - 5}px`;\n this._markerDiv.style.top = `${getTop(canvas) + canvasPos[1] - 5}px`;\n }\n\n this._markerDiv.style.background = \"pink\";\n if (event.snappedToVertex || event.snappedToEdge) {\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = event.canvasPos;\n this.pointerLens.snappedCanvasPos = event.snappedCanvasPos || event.canvasPos;\n this.pointerLens.snapped = true;\n }\n this._markerDiv.style.background = \"greenyellow\";\n this._markerDiv.style.border = \"2px solid green\";\n } else {\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = event.canvasPos;\n this.pointerLens.snappedCanvasPos = event.canvasPos;\n this.pointerLens.snapped = false;\n }\n this._markerDiv.style.background = \"pink\";\n this._markerDiv.style.border = \"2px solid red\";\n }\n hoveredEntity = event.entity;\n } else {\n this._markerDiv.style.left = `-10000px`;\n this._markerDiv.style.top = `-10000px`;\n }\n canvas.style.cursor = \"pointer\";\n if (this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement.wireVisible = this._currentDistanceMeasurementInitState.wireVisible;\n this._currentDistanceMeasurement.axisVisible = this._currentDistanceMeasurementInitState.axisVisible && this.distanceMeasurementsPlugin.defaultAxisVisible;\n this._currentDistanceMeasurement.xAxisVisible = this._currentDistanceMeasurementInitState.xAxisVisible && this.distanceMeasurementsPlugin.defaultXAxisVisible;\n this._currentDistanceMeasurement.yAxisVisible = this._currentDistanceMeasurementInitState.yAxisVisible && this.distanceMeasurementsPlugin.defaultYAxisVisible;\n this._currentDistanceMeasurement.zAxisVisible = this._currentDistanceMeasurementInitState.zAxisVisible && this.distanceMeasurementsPlugin.defaultZAxisVisible;\n this._currentDistanceMeasurement.targetVisible = this._currentDistanceMeasurementInitState.targetVisible;\n this._currentDistanceMeasurement.target.worldPos = pointerWorldPos.slice();\n this._markerDiv.style.left = `-10000px`;\n this._markerDiv.style.top = `-10000px`;\n }\n });\n\n canvas.addEventListener('mousedown', this._onMouseDown = (e) => {\n if (e.which !== 1) {\n return;\n }\n pointerDownCanvasX = e.clientX;\n pointerDownCanvasY = e.clientY;\n });\n\n canvas.addEventListener(\"mouseup\", this._onMouseUp = (e) => {\n if (e.which !== 1) {\n return;\n }\n if (e.clientX > pointerDownCanvasX + clickTolerance ||\n e.clientX < pointerDownCanvasX - clickTolerance ||\n e.clientY > pointerDownCanvasY + clickTolerance ||\n e.clientY < pointerDownCanvasY - clickTolerance) {\n return;\n }\n if (this._currentDistanceMeasurement) {\n if (mouseHovering) {\n this._currentDistanceMeasurement.target.entity = hoveredEntity;\n hoveredEntity = null;\n this._currentDistanceMeasurement.clickable = true;\n this.distanceMeasurementsPlugin.fire(\"measurementEnd\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement = null;\n } else {\n this._currentDistanceMeasurement.destroy();\n this.distanceMeasurementsPlugin.fire(\"measurementCancel\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement = null;\n hoveredEntity = null;\n }\n } else {\n if (mouseHovering) {\n this._currentDistanceMeasurement = distanceMeasurementsPlugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: pointerWorldPos.slice(),\n entity: hoveredEntity\n },\n target: {\n worldPos: pointerWorldPos.slice(),\n entity: hoveredEntity\n },\n approximate: true\n });\n this._currentDistanceMeasurementInitState.axisVisible = this._currentDistanceMeasurement.axisVisible && this.distanceMeasurementsPlugin.defaultAxisVisible;\n this._currentDistanceMeasurementInitState.xAxisVisible = this._currentDistanceMeasurement.xAxisVisible && this.distanceMeasurementsPlugin.defaultXAxisVisible;\n this._currentDistanceMeasurementInitState.yAxisVisible = this._currentDistanceMeasurement.yAxisVisible && this.distanceMeasurementsPlugin.defaultYAxisVisible;\n this._currentDistanceMeasurementInitState.zAxisVisible = this._currentDistanceMeasurement.zAxisVisible && this.distanceMeasurementsPlugin.defaultZAxisVisible;\n this._currentDistanceMeasurementInitState.wireVisible = this._currentDistanceMeasurement.wireVisible;\n this._currentDistanceMeasurementInitState.targetVisible = this._currentDistanceMeasurement.targetVisible;\n this._currentDistanceMeasurement.clickable = false;\n this._currentDistanceMeasurement.origin.entity = hoveredEntity;\n hoveredEntity = null;\n this.distanceMeasurementsPlugin.fire(\"measurementStart\", this._currentDistanceMeasurement);\n }\n }\n });\n\n this._onCameraControlHoverSnapOrSurfaceOff = cameraControl.on(\n this._snapping\n ? \"hoverSnapOrSurfaceOff\"\n : \"hoverOff\", event => {\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = event.canvasPos;\n this.pointerLens.snappedCanvasPos = event.snappedCanvasPos || event.canvasPos;\n }\n mouseHovering = false;\n this._markerDiv.style.left = `-100px`;\n this._markerDiv.style.top = `-100px`;\n if (this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement.wireVisible = false;\n this._currentDistanceMeasurement.targetVisible = false;\n this._currentDistanceMeasurement.axisVisible = false;\n }\n canvas.style.cursor = \"default\";\n });\n\n this._active = true;\n }\n\n /**\n * Deactivates this DistanceMeasurementsMouseControl, making it unresponsive to input.\n *\n * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl.\n */\n deactivate() {\n if (!this._active) {\n return;\n }\n\n this.fire(\"activated\", false);\n\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n if (this._markerDiv) {\n this._destroyMarkerDiv();\n }\n this.reset();\n const canvas = this.scene.canvas.canvas;\n canvas.removeEventListener(\"mousedown\", this._onMouseDown);\n canvas.removeEventListener(\"mouseup\", this._onMouseUp);\n const cameraControl = this.distanceMeasurementsPlugin.viewer.cameraControl;\n cameraControl.off(this._onCameraControlHoverSnapOrSurface);\n cameraControl.off(this._onCameraControlHoverSnapOrSurfaceOff);\n if (this._currentDistanceMeasurement) {\n this.distanceMeasurementsPlugin.fire(\"measurementCancel\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement.destroy();\n this._currentDistanceMeasurement = null;\n }\n this._active = false;\n }\n\n /**\n * Resets this DistanceMeasurementsMouseControl.\n *\n * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl.\n *\n * Does nothing if the DistanceMeasurementsMouseControl is not active.\n */\n reset() {\n if (!this._active) {\n return;\n }\n\n this._destroyMarkerDiv();\n this._initMarkerDiv();\n\n if (this._currentDistanceMeasurement) {\n this.distanceMeasurementsPlugin.fire(\"measurementCancel\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement.destroy();\n this._currentDistanceMeasurement = null;\n }\n\n this._mouseState = MOUSE_FIRST_CLICK_EXPECTED;\n }\n\n /**\n * Gets the {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl, if any.\n *\n * @returns {null|DistanceMeasurement}\n */\n get currentMeasurement() {\n return this._currentDistanceMeasurement;\n }\n\n /**\n * Destroys this DistanceMeasurementsMouseControl.\n *\n * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl.\n */\n destroy() {\n this.deactivate();\n super.destroy();\n }\n}\n\n/**\n * {@link Viewer} plugin for measuring point-to-point distances.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_distance_createWithMouse)\n *\n * * [[Example 1: Model with distance measurements](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_distance_modelWithMeasurements)]\n * * [[Example 2: Create distance measurements with mouse](https://xeokit.github.io/xeokit-sdk/examples/measurement/#distance_createWithMouse_snapping)]\n * * [[Example 3: Configuring units and scale](https://xeokit.github.io/xeokit-sdk/examples/measurement/#distance_unitsAndScale)\n *\n * ## Overview\n *\n * * A {@link DistanceMeasurement} represents a point-to-point measurement between two 3D points on one or two {@link Entity}s.\n * * As shown on the screen capture above, a DistanceMeasurement has one wire (light blue) that shows the direct point-to-point measurement,\n * and three more wires (red, green and blue) that show the distance on each of the World-space X, Y and Z axis.\n * * Create DistanceMeasurements programmatically with {@link DistanceMeasurementsPlugin#createMeasurement}.\n * * Create DistanceMeasurements interactively using a {@link DistanceMeasurementsControl}.\n * * Existing DistanceMeasurements are registered by ID in {@link DistanceMeasurementsPlugin#measurements}.\n * * Destroy DistanceMeasurements using {@link DistanceMeasurementsPlugin#destroyMeasurement}.\n * * Configure global measurement units and scale via {@link Metrics}, located at {@link Scene#metrics}.\n *\n * ## Example 1: Creating DistanceMeasurements Programmatically\n *\n * In our first example, we'll use an {@link XKTLoaderPlugin} to load a model, and then use a DistanceMeasurementsPlugin to programmatically create two {@link DistanceMeasurement}s.\n *\n * Note how each DistanceMeasurement has ````origin```` and ````target```` endpoints, which each indicate a 3D World-space\n * position on the surface of an {@link Entity}. The endpoints can be attached to the same Entity, or to different Entitys.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#distance_modelWithMeasurements)]\n *\n * ````JavaScript\n * import {Viewer, XKTLoaderPlugin, DistanceMeasurementsPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/duplex.xkt\"\n * });\n *\n * model.on(\"loaded\", () => {\n *\n * const myMeasurement1 = distanceMeasurements.createMeasurement({\n * id: \"distanceMeasurement1\",\n * origin: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"],\n * worldPos: [0.044, 5.998, 17.767]\n * },\n * target: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"],\n * worldPos: [4.738, 3.172, 17.768]\n * },\n * visible: true,\n * wireVisible: true\n * });\n *\n * const myMeasurement2 = distanceMeasurements.createMeasurement({\n * id: \"distanceMeasurement2\",\n * origin: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FNr2\"],\n * worldPos: [0.457, 2.532, 17.766]\n * },\n * target: {\n * entity: viewer.scene.objects[\"1CZILmCaHETO8tf3SgGEXu\"],\n * worldPos: [0.436, 0.001, 22.135]\n * },\n * visible: true,\n * wireVisible: true\n * });\n * });\n * ````\n *\n * ## Example 2: Creating DistanceMeasurements with Mouse Input\n *\n * In our second example, we'll use an {@link XKTLoaderPlugin} to load a model, then we'll use a\n * {@link DistanceMeasurementsMouseControl} to interactively create {@link DistanceMeasurement}s with mouse input.\n *\n * After we've activated the DistanceMeasurementsMouseControl, the first click on any {@link Entity} begins constructing a DistanceMeasurement, fixing its\n * origin to that Entity. The next click on any Entity will complete the DistanceMeasurement, fixing its target to that second Entity.\n *\n * The DistanceMeasurementsMouseControl will then wait for the next click on any Entity, to begin constructing\n * another DistanceMeasurement, and so on, until deactivated again.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#distance_createWithMouse_snapping)]\n *\n * ````JavaScript\n * import {Viewer, XKTLoaderPlugin, DistanceMeasurementsPlugin, DistanceMeasurementsMouseControl, PointerLens} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);\n *\n * const distanceMeasurementsControl = new DistanceMeasurementsMouseControl(distanceMeasurements, {\n * pointerLens : new PointerLens(viewer)\n * })\n *\n * distanceMeasurementsControl.snapToVertex = true;\n * distanceMeasurementsControl.snapToEdge = true;\n *\n * distanceMeasurementsControl.activate();\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/duplex.xkt\"\n * });\n * ````\n *\n * ## Example 3: Configuring Measurement Units and Scale\n *\n * In our third example, we'll use the {@link Scene}'s {@link Metrics} to set the global unit of measurement to ````\"meters\"````. We'll also specify that a unit within the World-space coordinate system represents ten meters.\n *\n * The wires belonging to our DistanceMeasurements show their lengths in Real-space coordinates, in the current unit of measurement. They will dynamically update as we set these configurations.\n *\n * * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_distance_unitsAndScale)]\n *\n * ````JavaScript\n * const metrics = viewer.scene.metrics;\n\n * metrics.units = \"meters\";\n * metrics.scale = 10.0;\n * ````\n *\n * ## Example 4: Attaching Mouse Handlers\n *\n * In our fourth example, we'll attach event handlers to our plugin, to catch when the user\n * hovers or right-clicks over our measurements.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_distance_modelWithMeasurements)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, DistanceMeasurementsPlugin, DistanceMeasurementsMouseControl, PointerLens} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);\n *\n * const distanceMeasurementsControl = new DistanceMeasurementsMouseControl(distanceMeasurements, {\n * pointerLens : new PointerLens(viewer)\n * })\n *\n * distanceMeasurementsControl.snapToVertex = true;\n * distanceMeasurementsControl.snapToEdge = true;\n *\n * distanceMeasurementsControl.activate();\n *\n * distanceMeasurements.on(\"mouseOver\", (e) => {\n * e.measurement.setHighlighted(true);\n * });\n *\n * distanceMeasurements.on(\"mouseLeave\", (e) => {\n * e.measurement.setHighlighted(false);\n * });\n *\n * distanceMeasurements.on(\"contextMenu\", (e) => {\n * // Show context menu\n * e.event.preventDefault();\n * });\n *\n * const model = xktLoader.load({\n * src: \"Duplex.xkt\"\n * });\n *\n * model.on(\"loaded\", () => {\n *\n * const myMeasurement1 = distanceMeasurements.createMeasurement({\n * id: \"distanceMeasurement1\",\n * origin: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"],\n * worldPos: [0.044, 5.998, 17.767]\n * },\n * target: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FLOH\"],\n * worldPos: [4.738, 3.172, 17.768]\n * },\n * visible: true,\n * wireVisible: true\n * });\n *\n * const myMeasurement2 = distanceMeasurements.createMeasurement({\n * id: \"distanceMeasurement2\",\n * origin: {\n * entity: viewer.scene.objects[\"2O2Fr$t4X7Zf8NOew3FNr2\"],\n * worldPos: [0.457, 2.532, 17.766]\n * },\n * target: {\n * entity: viewer.scene.objects[\"1CZILmCaHETO8tf3SgGEXu\"],\n * worldPos: [0.436, 0.001, 22.135]\n * },\n * visible: true,\n * wireVisible: true\n * });\n * });\n * ````\n *\n * ## Example 5: Creating DistanceMeasurements with Touch Input\n *\n * In our fifth example, we'll show how to create distance measurements with touch input, with snapping\n * to the nearest vertex or edge. While creating the measurements, a long-touch when setting the\n * start or end point will cause the point to snap to the nearest vertex or edge. A quick\n * touch-release will immediately set the point at the tapped position on the object surface.\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#distance_createWithTouch_snapping)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, DistanceMeasurementsPlugin, DistanceMeasurementsTouchControl} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];\n * viewer.scene.camera.look = [10.97, 5.82, -11.22];\n * viewer.scene.camera.up = [0.36, 0.83, 0.40];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);\n *\n * const model = xktLoader.load({\n * src: \"./models/xkt/duplex/duplex.xkt\"\n * });\n *\n * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);\n *\n * const distanceMeasurementsTouchControl = new DistanceMeasurementsTouchControl(distanceMeasurements, {\n * pointerLens : new PointerLens(viewer),\n * snapToVertex: true,\n * snapToEdge: true\n * })\n *\n * distanceMeasurementsTouchControl.activate();\n * ````\n */\nclass DistanceMeasurementsPlugin extends Plugin {\n /**\n * @constructor\n * @param {Viewer} viewer The Viewer.\n * @param {Object} [cfg] Plugin configuration.\n * @param {String} [cfg.id=\"DistanceMeasurements\"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.\n * @param {Number} [cfg.labelMinAxisLength=25] The minimum length, in pixels, of an axis wire beyond which its label is shown.\n * @param {HTMLElement} [cfg.container] Container DOM element for markers and labels. Defaults to ````document.body````.\n * @param {boolean} [cfg.defaultVisible=true] The default value of the DistanceMeasurements `visible` property.\n * @param {boolean} [cfg.defaultOriginVisible=true] The default value of the DistanceMeasurements `originVisible` property.\n * @param {boolean} [cfg.defaultTargetVisible=true] The default value of the DistanceMeasurements `targetVisible` property.\n * @param {boolean} [cfg.defaultWireVisible=true] The default value of the DistanceMeasurements `wireVisible` property.\n * @param {boolean} [cfg.defaultLabelsVisible=true] The default value of the DistanceMeasurements `labelsVisible` property.\n * @param {boolean} [cfg.defaultXLabelEnabled=true] The default value of the DistanceMeasurements `xLabelEnabled` property.\n * @param {boolean} [cfg.defaultYLabelEnabled=true] The default value of the DistanceMeasurements `yLabelEnabled` property.\n * @param {boolean} [cfg.defaultZLabelEnabled=true] The default value of the DistanceMeasurements `zLabelEnabled` property.\n * @param {boolean} [cfg.defaultLengthLabelEnabled=true] The default value of the DistanceMeasurements `lengthLabelEnabled` property.\n * @param {boolean} [cfg.defaultAxisVisible=true] The default value of the DistanceMeasurements `axisVisible` property.\n * @param {boolean} [cfg.defaultXAxisVisible=true] The default value of the DistanceMeasurements `xAxisVisible` property.\n * @param {boolean} [cfg.defaultYAxisVisible=true] The default value of the DistanceMeasurements `yAxisVisible` property.\n * @param {boolean} [cfg.defaultZAxisVisible=true] The default value of the DistanceMeasurements `zAxisVisible` property.\n * @param {boolean} [cfg.useRotationAdjustment=false] The default value of DistanceMeasurements `useRotationAdjustment` property.\n * @param {string} [cfg.defaultColor=#00BBFF] The default color of the length dots, wire and label.\n * @param {number} [cfg.zIndex] If set, the wires, dots and labels will have this zIndex (+1 for dots and +2 for labels).\n * @param {boolean} [cfg.defaultLabelsOnWires=true] The default value of the DistanceMeasurements `labelsOnWires` property.\n * @param {PointerCircle} [cfg.pointerLens] A PointerLens to help the user position the pointer. This can be shared with other plugins.\n */\n constructor(viewer, cfg = {}) {\n super(\"DistanceMeasurements\", viewer);\n\n this._pointerLens = cfg.pointerLens;\n\n this._container = cfg.container || document.body;\n\n this._defaultControl = null;\n\n this._measurements = {};\n\n this.labelMinAxisLength = cfg.labelMinAxisLength;\n this.defaultVisible = cfg.defaultVisible !== false;\n this.defaultOriginVisible = cfg.defaultOriginVisible !== false;\n this.defaultTargetVisible = cfg.defaultTargetVisible !== false;\n this.defaultWireVisible = cfg.defaultWireVisible !== false;\n this.defaultXLabelEnabled = cfg.defaultXLabelEnabled !== false;\n this.defaultYLabelEnabled = cfg.defaultYLabelEnabled !== false;\n this.defaultZLabelEnabled = cfg.defaultZLabelEnabled !== false;\n this.defaultLengthLabelEnabled = cfg.defaultLengthLabelEnabled !== false;\n this.defaultLabelsVisible = cfg.defaultLabelsVisible !== false;\n this.defaultAxisVisible = cfg.defaultAxisVisible !== false;\n this.defaultXAxisVisible = cfg.defaultXAxisVisible !== false;\n this.defaultYAxisVisible = cfg.defaultYAxisVisible !== false;\n this.defaultZAxisVisible = cfg.defaultZAxisVisible !== false;\n this.defaultColor =cfg.defaultColor !== undefined ? cfg.defaultColor : \"#00BBFF\";\n this.zIndex = cfg.zIndex || 10000;\n this.defaultLabelsOnWires = cfg.defaultLabelsOnWires !== false;\n this.useRotationAdjustment = cfg.useRotationAdjustment !== undefined ? cfg.useRotationAdjustment !== false : false;\n\n this._onMouseOver = (event, measurement) => {\n this.fire(\"mouseOver\", {\n plugin: this,\n distanceMeasurement: measurement,\n measurement,\n event,\n });\n };\n\n this._onMouseLeave = (event, measurement) => {\n this.fire(\"mouseLeave\", {\n plugin: this,\n distanceMeasurement: measurement,\n measurement,\n event,\n });\n };\n\n this._onContextMenu = (event, measurement) => {\n this.fire(\"contextMenu\", {\n plugin: this,\n distanceMeasurement: measurement,\n measurement,\n event,\n });\n };\n }\n\n /**\n * Gets the plugin's HTML container element, if any.\n * @returns {*|HTMLElement|HTMLElement}\n */\n getContainerElement() {\n return this._container;\n }\n\n /**\n * @private\n */\n send(name, value) {}\n\n /**\n * Gets the PointerLens attached to this DistanceMeasurementsPlugin.\n * @returns {PointerCircle}\n */\n get pointerLens() {\n return this._pointerLens;\n }\n\n /**\n * Gets the default {@link DistanceMeasurementsControl}.\n *\n * @type {DistanceMeasurementsControl}\n * @deprecated\n */\n get control() {\n if (!this._defaultControl) {\n this._defaultControl = new DistanceMeasurementsMouseControl(this, {});\n }\n return this._defaultControl;\n }\n\n /**\n * Gets the existing {@link DistanceMeasurement}s, each mapped to its {@link DistanceMeasurement#id}.\n *\n * @type {{String:DistanceMeasurement}}\n */\n get measurements() {\n return this._measurements;\n }\n\n /**\n * Sets the minimum length, in pixels, of an axis wire beyond which its label is shown.\n *\n * The axis wire's label is not shown when its length is less than this value.\n *\n * This is ````25```` pixels by default.\n *\n * Must not be less than ````1````.\n *\n * @type {number}\n */\n set labelMinAxisLength(labelMinAxisLength) {\n if (labelMinAxisLength < 1) {\n this.error(\"labelMinAxisLength must be >= 1; defaulting to 25\");\n labelMinAxisLength = 25;\n }\n this._labelMinAxisLength = labelMinAxisLength || 25;\n }\n\n /**\n * Gets the minimum length, in pixels, of an axis wire beyond which its label is shown.\n * @returns {number}\n */\n get labelMinAxisLength() {\n return this._labelMinAxisLength;\n }\n\n /**\n * Sets whether the measurement added is rotation adjusted or not.\n *\n * @type {boolean}\n */\n set useRotationAdjustment(_useRotationAdjustment) {\n _useRotationAdjustment = _useRotationAdjustment !== undefined ? Boolean(_useRotationAdjustment) : false;\n this._useRotationAdjustment = _useRotationAdjustment;\n }\n\n /**\n * Gets whether the measurement added is rotation adjusted or not.\n * @returns {number}\n */\n get useRotationAdjustment(){\n return this._useRotationAdjustment;\n }\n /**\n * Creates a {@link DistanceMeasurement}.\n *\n * The DistanceMeasurement is then registered by {@link DistanceMeasurement#id} in {@link DistanceMeasurementsPlugin#measurements}.\n *\n * @param {Object} params {@link DistanceMeasurement} configuration.\n * @param {String} params.id Unique ID to assign to {@link DistanceMeasurement#id}. The DistanceMeasurement will be registered by this in {@link DistanceMeasurementsPlugin#measurements} and {@link Scene.components}. Must be unique among all components in the {@link Viewer}.\n * @param {Number[]} params.origin.worldPos Origin World-space 3D position.\n * @param {Entity} params.origin.entity Origin Entity.\n * @param {Number[]} params.target.worldPos Target World-space 3D position.\n * @param {Entity} params.target.entity Target Entity.\n * @param {Boolean} [params.visible=true] Whether to initially show the {@link DistanceMeasurement}.\n * @param {Boolean} [params.originVisible=true] Whether to initially show the {@link DistanceMeasurement} origin.\n * @param {Boolean} [params.targetVisible=true] Whether to initially show the {@link DistanceMeasurement} target.\n * @param {Boolean} [params.wireVisible=true] Whether to initially show the direct point-to-point wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target}.\n * @param {Boolean} [params.axisVisible=true] Whether to initially show the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target}.\n * @param {Boolean} [params.xAxisVisible=true] Whether to initially show the X-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target}.\n * @param {Boolean} [params.yAxisVisible=true] Whether to initially show the Y-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target}.\n * @param {Boolean} [params.zAxisVisible=true] Whether to initially show the Z-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target}.\n * @param {Boolean} [params.xLabelEnabled=true] Whether to initially show the x label.\n * @param {Boolean} [params.yLabelEnabled=true] Whether to initially show the y label.\n * @param {Boolean} [params.zLabelEnabled=true] Whether to initially show the z label.\n * @param {Boolean} [params.lengthLabelEnabled=true] Whether to initially show the length label.\n * @param {Boolean} [params.labelsVisible=true] Whether to initially show the labels.\n * @param {Boolean} [params.lengthLabelVisible=true] Whether to initially show the labels.\n * @param {string} [params.color] The color of the length dot, wire and label.\n * @param {Boolean} [params.labelsOnWires=true] Determines if labels will be set on wires or one below the other.\n * @returns {DistanceMeasurement} The new {@link DistanceMeasurement}.\n */\n createMeasurement(params = {}) {\n if (this.viewer.scene.components[params.id]) {\n this.error(\n \"Viewer scene component with this ID already exists: \" + params.id\n );\n delete params.id;\n }\n const origin = params.origin;\n const target = params.target;\n const measurement = new DistanceMeasurement(this, {\n id: params.id,\n plugin: this,\n container: this._container,\n origin: {\n entity: origin.entity,\n worldPos: origin.worldPos,\n },\n target: {\n entity: target.entity,\n worldPos: target.worldPos,\n },\n visible: params.visible,\n wireVisible: params.wireVisible,\n axisVisible: params.axisVisible !== false && this.defaultAxisVisible !== false,\n xAxisVisible: params.xAxisVisible !== false && this.defaultXAxisVisible !== false,\n yAxisVisible: params.yAxisVisible !== false && this.defaultYAxisVisible !== false,\n zAxisVisibld: params.zAxisVisible !== false && this.defaultZAxisVisible !== false,\n xLabelEnabled: params.xLabelEnabled !== false && this.defaultXLabelEnabled !== false,\n yLabelEnabled: params.yLabelEnabled !== false && this.defaultYLabelEnabled !== false,\n zLabelEnabled: params.zLabelEnabled !== false && this.defaultZLabelEnabled !== false,\n lengthLabelEnabled: params.lengthLabelEnabled !== false && this.defaultLengthLabelEnabled !== false,\n labelsVisible: params.labelsVisible !== false && this.defaultLabelsVisible !== false,\n useRotationAdjustment: this.useRotationAdjustment,\n originVisible: params.originVisible,\n targetVisible: params.targetVisible,\n color: params.color,\n labelsOnWires:params.labelsOnWires !== false && this.defaultLabelsOnWires !== false,\n onMouseOver: this._onMouseOver,\n onMouseLeave: this._onMouseLeave,\n onContextMenu: this._onContextMenu,\n });\n this._measurements[measurement.id] = measurement;\n measurement.clickable = true;\n measurement.on(\"destroyed\", () => {\n delete this._measurements[measurement.id];\n });\n this.fire(\"measurementCreated\", measurement);\n return measurement;\n }\n\n /**\n * Destroys a {@link DistanceMeasurement}.\n *\n * @param {String} id ID of DistanceMeasurement to destroy.\n */\n destroyMeasurement(id) {\n const measurement = this._measurements[id];\n if (!measurement) {\n this.log(\"DistanceMeasurement not found: \" + id);\n return;\n }\n measurement.destroy();\n this.fire(\"measurementDestroyed\", measurement);\n }\n\n /**\n * Shows all or hides the distance label of each {@link DistanceMeasurement}.\n *\n * @param {Boolean} labelsShown Whether or not to show the labels.\n */\n setLabelsShown(labelsShown) {\n for (const [key, measurement] of Object.entries(this.measurements)) {\n measurement.labelShown = labelsShown;\n }\n }\n\n /**\n * Shows all or hides the axis wires of each {@link DistanceMeasurement}.\n *\n * @param {Boolean} labelsShown Whether or not to show the axis wires.\n */\n setAxisVisible(axisVisible) {\n for (const [key, measurement] of Object.entries(this.measurements)) {\n measurement.axisVisible = axisVisible;\n }\n this.defaultAxisVisible = axisVisible;\n }\n\n /**\n * Gets if the axis wires of each {@link DistanceMeasurement} are visible.\n *\n * @returns {Boolean} Whether or not the axis wires are visible.\n */\n getAxisVisible() {\n return this.defaultAxisVisible;\n }\n\n /**\n * Destroys all {@link DistanceMeasurement}s.\n */\n clear() {\n const ids = Object.keys(this._measurements);\n for (var i = 0, len = ids.length; i < len; i++) {\n this.destroyMeasurement(ids[i]);\n }\n }\n\n /**\n * Destroys this DistanceMeasurementsPlugin.\n *\n * Destroys all {@link DistanceMeasurement}s first.\n */\n destroy() {\n this.clear();\n super.destroy();\n }\n}\n\nconst WAITING_FOR_ORIGIN_TOUCH_START = 0;\nconst WAITING_FOR_ORIGIN_QUICK_TOUCH_END = 1;\nconst WAITING_FOR_ORIGIN_LONG_TOUCH_END = 2;\n\nconst WAITING_FOR_TARGET_TOUCH_START = 3;\nconst WAITING_FOR_TARGET_QUICK_TOUCH_END = 4;\nconst WAITING_FOR_TARGET_LONG_TOUCH_END = 5;\n\nconst TOUCH_CANCELING = 7;\n\n/**\n * Creates {@link DistanceMeasurement}s from touch input.\n *\n * See {@link DistanceMeasurementsPlugin} for more info.\n *\n */\nclass DistanceMeasurementsTouchControl extends DistanceMeasurementsControl {\n\n /**\n * Creates a DistanceMeasurementsTouchControl bound to the given DistanceMeasurementsPlugin.\n */\n constructor(distanceMeasurementsPlugin, cfg = {}) {\n\n super(distanceMeasurementsPlugin.viewer.scene);\n\n this.pointerLens = cfg.pointerLens;\n this.pointerCircle = new PointerCircle(distanceMeasurementsPlugin.viewer);\n\n this._active = false;\n\n const markerDiv = document.createElement('div');\n const canvas = this.scene.canvas.canvas;\n canvas.parentNode.insertBefore(markerDiv, canvas);\n\n markerDiv.style.background = \"black\";\n markerDiv.style.border = \"2px solid blue\";\n markerDiv.style.borderRadius = \"10px\";\n markerDiv.style.width = \"5px\";\n markerDiv.style.height = \"5px\";\n markerDiv.style.margin = \"-200px -200px\";\n markerDiv.style.zIndex = \"100\";\n markerDiv.style.position = \"absolute\";\n markerDiv.style.pointerEvents = \"none\";\n\n this.markerDiv = markerDiv;\n\n this._currentDistanceMeasurement = null;\n\n this._currentDistanceMeasurementInitState = {\n wireVisible: null,\n axisVisible: null,\n xAxisVisible: null,\n yaxisVisible: null,\n zAxisVisible: null,\n targetVisible: null,\n };\n\n this._onCanvasTouchStart = null;\n this._onCanvasTouchEnd = null;\n this._longTouchTimeoutMs = 300;\n this._snapping = cfg.snapping !== false;\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n\n this._attachPlugin(distanceMeasurementsPlugin, cfg);\n }\n\n _attachPlugin(distanceMeasurementsPlugin) {\n\n /**\n * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurementsTouchControl.\n * @type {DistanceMeasurementsPlugin}\n */\n this.distanceMeasurementsPlugin = distanceMeasurementsPlugin;\n\n /**\n * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurementsTouchControl.\n * @type {DistanceMeasurementsPlugin}\n */\n this.plugin = distanceMeasurementsPlugin;\n }\n\n /** Gets if this DistanceMeasurementsTouchControl is currently active, where it is responding to input.\n *\n * @returns {Boolean}\n */\n get active() {\n return this._active;\n }\n\n /**\n * Sets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsTouchControl.\n *\n * This is `true` by default.\n *\n * Internally, this deactivates then activates the DistanceMeasurementsTouchControl when changed, which means that\n * it will destroy any DistanceMeasurements currently under construction, and incurs some overhead, since it unbinds\n * and rebinds various input handlers.\n *\n * @param {boolean} snapping Whether to enable snap-to-vertex and snap-edge for this DistanceMeasurementsTouchControl.\n */\n set snapping(snapping) {\n if (snapping !== this._snapping) {\n this._snapping = snapping;\n this.deactivate();\n this.activate();\n } else {\n this._snapping = snapping;\n }\n }\n\n /**\n * Gets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsTouchControl.\n *\n * This is `true` by default.\n *\n * @returns {boolean} Whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsTouchControl.\n */\n get snapping() {\n return this._snapping;\n }\n\n /**\n * Activates this DistanceMeasurementsTouchControl, ready to respond to input.\n */\n activate() {\n\n if (this._active) {\n return;\n }\n\n const plugin = this.plugin;\n const scene = this.scene;\n const canvas = scene.canvas.canvas;\n plugin.pointerLens;\n const pointerWorldPos = math.vec3();\n\n const touchTolerance = 20;\n\n let longTouchTimeout = null;\n\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n\n const touchStartCanvasPos = math.vec2();\n const touchMoveCanvasPos = math.vec2();\n const touchEndCanvasPos = math.vec2();\n\n let touchId = null;\n\n const disableCameraNavigation = () => {\n this.plugin.viewer.cameraControl.active = false;\n };\n\n const enableCameraNavigation = () => {\n this.plugin.viewer.cameraControl.active = true;\n };\n\n const cancel = () => {\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n if (this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement.destroy();\n this._currentDistanceMeasurement = null;\n }\n enableCameraNavigation();\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n };\n\n canvas.addEventListener(\"touchstart\", this._onCanvasTouchStart = (event) => {\n\n const currentNumTouches = event.touches.length;\n\n if (currentNumTouches !== 1) {\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n return;\n }\n\n const touch = event.touches[0];\n const touchX = touch.clientX;\n const touchY = touch.clientY;\n\n touchStartCanvasPos.set([touchX, touchY]);\n touchMoveCanvasPos.set([touchX, touchY]);\n\n switch (this._touchState) {\n\n case WAITING_FOR_ORIGIN_TOUCH_START:\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n cancel();\n return;\n }\n const snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapping: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.snapped) {\n pointerWorldPos.set(snapPickResult.worldPos);\n this.pointerCircle.start(snapPickResult.snappedCanvasPos);\n } else {\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n pointerWorldPos.set(pickResult.worldPos);\n this.pointerCircle.start(pickResult.canvasPos);\n } else {\n return;\n }\n }\n longTouchTimeout = setTimeout(() => {\n if (currentNumTouches !== 1 ||\n touchMoveCanvasPos[0] > touchStartCanvasPos[0] + touchTolerance ||\n touchMoveCanvasPos[0] < touchStartCanvasPos[0] - touchTolerance ||\n touchMoveCanvasPos[1] > touchStartCanvasPos[1] + touchTolerance ||\n touchMoveCanvasPos[1] < touchStartCanvasPos[1] - touchTolerance) {\n return; // Has moved\n }\n // Long touch\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = touchStartCanvasPos;\n this.pointerLens.cursorPos = touchStartCanvasPos;\n }\n if (this.pointerLens) {\n this.pointerLens.canvasPos = touchMoveCanvasPos;\n this.pointerLens.snapped = false;\n }\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.canvasPos;\n this.pointerLens.snapped = true;\n }\n // pointerWorldPos.set(snapPickResult.snappedWorldPos);\n if (!this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement = plugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: pointerWorldPos,\n entity: snapPickResult.entity\n },\n target: {\n worldPos: pointerWorldPos,\n entity: snapPickResult.entity\n }\n });\n this._currentDistanceMeasurement.labelsVisible = false;\n this._currentDistanceMeasurement.xAxisVisible = false;\n this._currentDistanceMeasurement.yAxisVisible = false;\n this._currentDistanceMeasurement.zAxisVisible = false;\n this._currentDistanceMeasurement.wireVisible = false;\n this._currentDistanceMeasurement.originVisible = true;\n this._currentDistanceMeasurement.targetVisible = false;\n this._currentDistanceMeasurement.clickable = false;\n } else {\n this._currentDistanceMeasurement.origin.worldPos = pointerWorldPos;\n }\n this.distanceMeasurementsPlugin.fire(\"measurementStart\", this._currentDistanceMeasurement);\n // if (this.pointerLens) {\n // this.pointerLens.cursorPos = pickResult.canvasPos;\n // this.pointerLens.snapped = false;\n // }\n this._touchState = WAITING_FOR_ORIGIN_LONG_TOUCH_END;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_ORIGIN_TOUCH_START -> WAITING_FOR_ORIGIN_LONG_TOUCH_END\")\n disableCameraNavigation();\n }, this._longTouchTimeoutMs);\n this._touchState = WAITING_FOR_ORIGIN_QUICK_TOUCH_END;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_ORIGIN_TOUCH_START -> WAITING_FOR_ORIGIN_QUICK_TOUCH_END\")\n\n touchId = touch.identifier;\n\n break;\n\n\n case WAITING_FOR_TARGET_TOUCH_START:\n\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n return;\n }\n if (currentNumTouches === 1) { // One finger down\n longTouchTimeout = setTimeout(() => {\n longTouchTimeout = null;\n if (currentNumTouches !== 1 ||\n touchMoveCanvasPos[0] > touchStartCanvasPos[0] + touchTolerance ||\n touchMoveCanvasPos[0] < touchStartCanvasPos[0] - touchTolerance ||\n touchMoveCanvasPos[1] > touchStartCanvasPos[1] + touchTolerance ||\n touchMoveCanvasPos[1] < touchStartCanvasPos[1] - touchTolerance) {\n // Has moved\n return;\n }\n\n // Long touch\n if (this.pointerLens) {\n this.pointerLens.visible = true;\n this.pointerLens.canvasPos = touchStartCanvasPos;\n this.pointerLens.snapped = false;\n }\n\n const snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.snapped) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n this.pointerCircle.start(snapPickResult.snappedCanvasPos);\n pointerWorldPos.set(snapPickResult.worldPos);\n this._currentDistanceMeasurement.target.worldPos = snapPickResult.worldPos;\n this._currentDistanceMeasurement.target.entity = snapPickResult.entity;\n this._currentDistanceMeasurement.targetVisible = true;\n this._currentDistanceMeasurement.wireVisible = true;\n this._currentDistanceMeasurement.labelsVisible = true;\n this.distanceMeasurementsPlugin.fire(\"measurementStart\", this._currentDistanceMeasurement);\n } else {\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n this.pointerCircle.start(pickResult.canvasPos);\n pointerWorldPos.set(pickResult.worldPos);\n this._currentDistanceMeasurement.target.worldPos = pickResult.worldPos;\n this._currentDistanceMeasurement.target.entity = pickResult.entity;\n this._currentDistanceMeasurement.targetVisible = true;\n this._currentDistanceMeasurement.wireVisible = true;\n this._currentDistanceMeasurement.labelsVisible = true;\n this.distanceMeasurementsPlugin.fire(\"measurementStart\", this._currentDistanceMeasurement);\n } else {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = null;\n this.pointerLens.snapped = false;\n\n }\n }\n }\n this._touchState = WAITING_FOR_TARGET_LONG_TOUCH_END;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_TARGET_TOUCH_START -> WAITING_FOR_TARGET_LONG_TOUCH_END\")\n\n disableCameraNavigation();\n\n }, this._longTouchTimeoutMs);\n\n this._touchState = WAITING_FOR_TARGET_QUICK_TOUCH_END;\n // console.log(\"touchstart: this._touchState= WAITING_FOR_TARGET_TOUCH_START -> WAITING_FOR_TARGET_QUICK_TOUCH_END\")\n }\n\n touchId = touch.identifier;\n\n break;\n\n default:\n if (longTouchTimeout !== null) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n this._touchState = TOUCH_CANCELING;\n // console.log(\"touchstart: this._touchState= default -> TOUCH_CANCELING\")\n return;\n }\n\n }, {passive: true});\n\n\n canvas.addEventListener(\"touchmove\", (event) => {\n\n this.pointerCircle.stop();\n\n const currentNumTouches = event.touches.length;\n\n if (currentNumTouches !== 1 || event.changedTouches.length !== 1) {\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n return;\n }\n\n const touch = event.touches[0];\n const touchX = touch.clientX;\n const touchY = touch.clientY;\n\n if (touch.identifier !== touchId) {\n return;\n }\n\n touchMoveCanvasPos.set([touchX, touchY]);\n\n let snapPickResult;\n let pickResult;\n\n switch (this._touchState) {\n\n case WAITING_FOR_ORIGIN_LONG_TOUCH_END:\n if (this.pointerLens) {\n this.pointerLens.canvasPos = touchMoveCanvasPos;\n }\n snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && (snapPickResult.snapped)) {\n if (this.pointerLens) {\n this.pointerLens.snappedCanvasPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n pointerWorldPos.set(snapPickResult.worldPos);\n if (!this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement = plugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: snapPickResult.worldPos,\n entity: snapPickResult.entity\n },\n target: {\n worldPos: snapPickResult.worldPos,\n entity: snapPickResult.entity\n }\n });\n this._currentDistanceMeasurement.labelsVisible = false;\n this._currentDistanceMeasurement.xAxisVisible = false;\n this._currentDistanceMeasurement.yAxisVisible = false;\n this._currentDistanceMeasurement.zAxisVisible = false;\n this._currentDistanceMeasurement.wireVisible = false;\n this._currentDistanceMeasurement.originVisible = true;\n this._currentDistanceMeasurement.targetVisible = false;\n this._currentDistanceMeasurement.clickable = false;\n } else {\n this._currentDistanceMeasurement.origin.worldPos = snapPickResult.worldPos;\n }\n\n this.distanceMeasurementsPlugin.fire(\"measurementStart\", this._currentDistanceMeasurement);\n } else {\n pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n pointerWorldPos.set(pickResult.worldPos);\n if (!this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement = plugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: pickResult.worldPos,\n entity: pickResult.entity\n },\n target: {\n worldPos: pickResult.worldPos,\n entity: pickResult.entity\n }\n });\n this._currentDistanceMeasurement.labelsVisible = false;\n this._currentDistanceMeasurement.xAxisVisible = false;\n this._currentDistanceMeasurement.yAxisVisible = false;\n this._currentDistanceMeasurement.zAxisVisible = false;\n this._currentDistanceMeasurement.wireVisible = false;\n this._currentDistanceMeasurement.originVisible = true;\n this._currentDistanceMeasurement.targetVisible = false;\n this._currentDistanceMeasurement.clickable = false;\n } else {\n this._currentDistanceMeasurement.origin.worldPos = pickResult.worldPos;\n }\n\n this.distanceMeasurementsPlugin.fire(\"measurementStart\", this._currentDistanceMeasurement);\n } else {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = null;\n this.pointerLens.snapped = false;\n }\n }\n }\n this._touchState = WAITING_FOR_ORIGIN_LONG_TOUCH_END;\n // console.log(\"touchmove: this._touchState= WAITING_FOR_ORIGIN_LONG_TOUCH_END -> WAITING_FOR_ORIGIN_LONG_TOUCH_END\")\n break;\n\n // case WAITING_FOR_TARGET_TOUCH_START:\n // this._touchState = WAITING_FOR_TARGET_TOUCH_START;\n // console.log(\"touchmove: this._touchState= WAITING_FOR_TARGET_TOUCH_START -> WAITING_FOR_TARGET_TOUCH_START\")\n // break;\n\n case WAITING_FOR_TARGET_LONG_TOUCH_END:\n if (currentNumTouches !== 1 && longTouchTimeout !== null) { // Two or more fingers down\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n this._touchState = TOUCH_CANCELING;\n // console.log(\"touchmove: this._touchState= QUICK_TOUCH_FINDING_TARGET -> TOUCH_CANCELING\")\n return;\n }\n if (this.pointerLens) {\n this.pointerLens.canvasPos = touchMoveCanvasPos;\n }\n snapPickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n snapToVertex: this._snapping,\n snapToEdge: this._snapping\n });\n if (snapPickResult && snapPickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = snapPickResult.snappedCanvasPos;\n this.pointerLens.snapped = true;\n }\n this._currentDistanceMeasurement.target.worldPos = snapPickResult.worldPos;\n this._currentDistanceMeasurement.target.entity = snapPickResult.entity;\n this._currentDistanceMeasurement.targetVisible = true;\n this._currentDistanceMeasurement.wireVisible = true;\n this._currentDistanceMeasurement.labelsVisible = true;\n } else {\n pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n if (this.pointerLens) {\n this.pointerLens.cursorPos = pickResult.canvasPos;\n this.pointerLens.snapped = false;\n }\n this._currentDistanceMeasurement.target.worldPos = pickResult.worldPos;\n this._currentDistanceMeasurement.target.entity = pickResult.entity;\n this._currentDistanceMeasurement.targetVisible = true;\n this._currentDistanceMeasurement.wireVisible = true;\n this._currentDistanceMeasurement.labelsVisible = true;\n\n }\n }\n this._touchState = WAITING_FOR_TARGET_LONG_TOUCH_END;\n break;\n }\n }, {passive: true});\n\n canvas.addEventListener(\"touchend\", this._onCanvasTouchEnd = (event) => {\n\n this.pointerCircle.stop();\n\n const numChangedTouches = event.changedTouches.length;\n\n if (numChangedTouches !== 1) {\n return;\n }\n\n const touch = event.changedTouches[0];\n const touchX = touch.clientX;\n const touchY = touch.clientY;\n\n if (touch.identifier !== touchId) {\n return;\n }\n\n if (longTouchTimeout) {\n clearTimeout(longTouchTimeout);\n longTouchTimeout = null;\n }\n\n touchEndCanvasPos.set([touchX, touchY]);\n\n switch (this._touchState) {\n\n case WAITING_FOR_ORIGIN_QUICK_TOUCH_END: {\n if (numChangedTouches !== 1 ||\n touchX > touchStartCanvasPos[0] + touchTolerance ||\n touchX < touchStartCanvasPos[0] - touchTolerance ||\n touchY > touchStartCanvasPos[1] + touchTolerance ||\n touchY < touchStartCanvasPos[1] - touchTolerance) {\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n return;\n }\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n this._currentDistanceMeasurement = plugin.createMeasurement({\n id: math.createUUID(),\n origin: {\n worldPos: pickResult.worldPos,\n entity: pickResult.entity\n },\n target: {\n worldPos: pickResult.worldPos,\n entity: pickResult.entity\n }\n });\n this._currentDistanceMeasurement.labelsVisible = false;\n this._currentDistanceMeasurement.xAxisVisible = false;\n this._currentDistanceMeasurement.yAxisVisible = false;\n this._currentDistanceMeasurement.zAxisVisible = false;\n this._currentDistanceMeasurement.wireVisible = false;\n this._currentDistanceMeasurement.originVisible = true;\n this._currentDistanceMeasurement.targetVisible = false;\n this._currentDistanceMeasurement.clickable = false;\n this._touchState = WAITING_FOR_TARGET_TOUCH_START;\n this.distanceMeasurementsPlugin.fire(\"measurementStart\", this._currentDistanceMeasurement);\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_QUICK_TOUCH_END -> WAITING_FOR_ORIGIN_TOUCH_START\")\n } else {\n if (this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement.destroy();\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_QUICK_TOUCH_END -> WAITING_FOR_ORIGIN_TOUCH_START\")\n }\n }\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_ORIGIN_LONG_TOUCH_END:\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n if (!this._currentDistanceMeasurement) {\n if (this.pointerLens) {\n this.pointerLens.snapped = false;\n this.pointerLens.visible = false;\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_LONG_TOUCH_END (no measurement) -> WAITING_FOR_ORIGIN_TOUCH_START\")\n } else {\n this._touchState = WAITING_FOR_TARGET_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_ORIGIN_LONG_TOUCH_END (picked, begin measurement) -> WAITING_FOR_TARGET_TOUCH_START\")\n }\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_TARGET_QUICK_TOUCH_END: {\n if (numChangedTouches !== 1 ||\n touchX > touchStartCanvasPos[0] + touchTolerance ||\n touchX < touchStartCanvasPos[0] - touchTolerance ||\n touchY > touchStartCanvasPos[1] + touchTolerance ||\n touchY < touchStartCanvasPos[1] - touchTolerance) {\n this._touchState = WAITING_FOR_TARGET_TOUCH_START;\n return;\n }\n const pickResult = scene.pick({\n canvasPos: touchMoveCanvasPos,\n pickSurface: true\n });\n if (pickResult && pickResult.worldPos) {\n this._currentDistanceMeasurement.target.worldPos = pickResult.worldPos;\n this._currentDistanceMeasurement.target.entity = pickResult.entity;\n this._currentDistanceMeasurement.targetVisible = true;\n this._currentDistanceMeasurement.wireVisible = true;\n this._currentDistanceMeasurement.labelsVisible = true;\n this._currentDistanceMeasurement.xAxisVisible = true;\n this._currentDistanceMeasurement.yAxisVisible = true;\n this._currentDistanceMeasurement.zAxisVisible = true;\n this._currentDistanceMeasurement.clickable = true;\n this.distanceMeasurementsPlugin.fire(\"measurementEnd\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement = null;\n } else {\n if (this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement.destroy();\n this._currentDistanceMeasurement = null;\n }\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_TARGET_TOUCH_START -> WAITING_FOR_ORIGIN_TOUCH_START\")\n }\n enableCameraNavigation();\n break;\n\n case WAITING_FOR_TARGET_LONG_TOUCH_END:\n console.log('long touch');\n if (this.pointerLens) {\n this.pointerLens.visible = false;\n }\n if (!this._currentDistanceMeasurement || !this._currentDistanceMeasurement.targetVisible) {\n if (this._currentDistanceMeasurement) {\n this._currentDistanceMeasurement.destroy();\n this._currentDistanceMeasurement = null;\n }\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_TARGET_LONG_TOUCH_END (no target found) -> WAITING_FOR_ORIGIN_TOUCH_START\")\n } else {\n this._currentDistanceMeasurement.clickable = true;\n this.distanceMeasurementsPlugin.fire(\"measurementEnd\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement = null;\n this._touchState = WAITING_FOR_ORIGIN_TOUCH_START;\n // console.log(\"touchend: this._touchState= WAITING_FOR_TARGET_LONG_TOUCH_END -> WAITING_FOR_ORIGIN_TOUCH_START\")\n }\n enableCameraNavigation();\n break;\n }\n\n }, {passive: true});\n\n this._active = true;\n }\n\n /**\n * Deactivates this DistanceMeasurementsTouchControl, making it unresponsive to input.\n *\n * Destroys any {@link DistanceMeasurement} under construction.\n */\n deactivate() {\n if (!this._active) {\n return;\n }\n if (this.plugin.pointerLens) {\n this.plugin.pointerLens.visible = false;\n }\n this.reset();\n const canvas = this.plugin.viewer.scene.canvas.canvas;\n canvas.removeEventListener(\"touchstart\", this._onCanvasTouchStart);\n canvas.removeEventListener(\"touchend\", this._onCanvasTouchEnd);\n if (this._currentDistanceMeasurement) {\n this.distanceMeasurementsPlugin.fire(\"measurementCancel\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement.destroy();\n this._currentDistanceMeasurement = null;\n }\n this._active = false;\n this.plugin.viewer.cameraControl.active = true;\n }\n\n /**\n * Resets this DistanceMeasurementsTouchControl.\n *\n * Destroys any {@link DistanceMeasurement} under construction.\n *\n * Does nothing if the DistanceMeasurementsTouchControl is not active.\n */\n reset() {\n if (!this._active) {\n return;\n }\n if (this._currentDistanceMeasurement) {\n this.distanceMeasurementsPlugin.fire(\"measurementCancel\", this._currentDistanceMeasurement);\n this._currentDistanceMeasurement.destroy();\n this._currentDistanceMeasurement = null;\n }\n this._mouseState = WAITING_FOR_ORIGIN_TOUCH_START;\n }\n\n /**\n * Gets the {@link DistanceMeasurement} under construction by this DistanceMeasurementsTouchControl, if any.\n *\n * @returns {null|DistanceMeasurement}\n */\n get currentMeasurement() {\n return this._currentDistanceMeasurement;\n }\n\n /**\n * Destroys this DistanceMeasurementsTouchControl.\n */\n destroy() {\n this.deactivate();\n super.destroy();\n }\n}\n\n/**\n * {@link Viewer} plugin that makes interaction smoother with large models, by temporarily switching\n * the Viewer to faster, lower-quality rendering modes whenever we interact.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/index.html#performance_FastNavPlugin)\n *\n * FastNavPlugin works by hiding specified Viewer rendering features, and optionally scaling the Viewer's canvas\n * resolution, whenever we interact with the Viewer. Then, once we've finished interacting, FastNavPlugin restores those\n * rendering features and the original canvas scale, after a configured delay.\n *\n * Depending on how we configure FastNavPlugin, we essentially switch to a smooth-rendering low-quality view while\n * interacting, then return to the normal higher-quality view after we stop, following an optional delay.\n *\n * Down-scaling the canvas resolution gives particularly good results. For example, scaling by ````0.5```` means that\n * we're rendering a quarter of the pixels while interacting, which can make the Viewer noticeably smoother with big models.\n *\n * The screen capture above shows FastNavPlugin in action. In this example, whenever we move the Camera or resize the Canvas,\n * FastNavPlugin switches off enhanced edges and ambient shadows (SAO), and down-scales the canvas, making it slightly\n * blurry. When ````0.5```` seconds passes with no interaction, the plugin shows edges and SAO again, and restores the\n * original canvas scale.\n *\n * # Usage\n *\n * In the example below, we'll create a {@link Viewer}, add a {@link FastNavPlugin}, then use an {@link XKTLoaderPlugin} to load a model.\n *\n * Whenever we interact with the Viewer, our FastNavPlugin will:\n *\n * * hide edges,\n * * hide ambient shadows (SAO),\n * * hide physically-based materials (switching to non-PBR),\n * * hide transparent objects, and\n * * scale the canvas resolution by 0.5, causing the GPU to render 75% less pixels.\n *
\n *\n * We'll also configure a 0.5 second delay before we transition back to high-quality each time we stop ineracting, so that we're\n * not continually flipping between low and high quality as we interact. Since we're only rendering ambient shadows when not interacting, we'll also treat ourselves\n * to expensive, high-quality SAO settings, that we wouldn't normally configure for an interactive SAO effect.\n *\n * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#performance_FastNavPlugin)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, FastNavPlugin} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer with PBR and SAO enabled\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true,\n * pbr: true, // Enable physically-based rendering for Viewer\n * sao: true // Enable ambient shadows for Viewer\n * });\n *\n * viewer.scene.camera.eye = [-66.26, 105.84, -281.92];\n * viewer.scene.camera.look = [42.45, 49.62, -43.59];\n * viewer.scene.camera.up = [0.05, 0.95, 0.15];\n *\n * // Higher-quality SAO settings\n *\n * viewer.scene.sao.enabled = true;\n * viewer.scene.sao.numSamples = 60;\n * viewer.scene.sao.kernelRadius = 170;\n *\n * // Install a FastNavPlugin\n *\n * new FastNavPlugin(viewer, {\n * hideEdges: true, // Don't show edges while we interact (default is true)\n * hideSAO: true, // Don't show ambient shadows while we interact (default is true)\n * hideColorTexture: true, // No color textures while we interact (default is true)\n * hidePBR: true, // No physically-based rendering while we interact (default is true)\n * hideTransparentObjects: true, // Hide transparent objects while we interact (default is false)\n * scaleCanvasResolution: true, // Scale canvas resolution while we interact (default is false)\n * defaultScaleCanvasResolutionFactor: 1.0, // Factor by which we scale canvas resolution when we stop interacting (default is 1.0)\n * scaleCanvasResolutionFactor: 0.5, // Factor by which we scale canvas resolution when we interact (default is 0.6)\n * delayBeforeRestore: true, // When we stop interacting, delay before restoring normal render (default is true)\n * delayBeforeRestoreSeconds: 0.5 // The delay duration, in seconds (default is 0.5)\n * });\n *\n * // Load a BIM model from XKT\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/HolterTower.xkt\",\n * sao: true, // Enable ambient shadows for this model\n * pbr: true // Enable physically-based rendering for this model\n * });\n * ````\n *\n * @class FastNavPlugin\n */\nclass FastNavPlugin extends Plugin {\n\n /**\n * @constructor\n * @param {Viewer} viewer The Viewer.\n * @param {Object} cfg FastNavPlugin configuration.\n * @param {String} [cfg.id=\"FastNav\"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.\n * @param {Boolean} [cfg.hideColorTexture=true] Whether to temporarily hide color textures whenever we interact with the Viewer.\n * @param {Boolean} [cfg.hidePBR=true] Whether to temporarily hide physically-based rendering (PBR) whenever we interact with the Viewer.\n * @param {Boolean} [cfg.hideSAO=true] Whether to temporarily hide scalable ambient occlusion (SAO) whenever we interact with the Viewer.\n * @param {Boolean} [cfg.hideEdges=true] Whether to temporarily hide edges whenever we interact with the Viewer.\n * @param {Boolean} [cfg.hideTransparentObjects=false] Whether to temporarily hide transparent objects whenever we interact with the Viewer.\n * @param {Number} [cfg.scaleCanvasResolution=false] Whether to temporarily down-scale the canvas resolution whenever we interact with the Viewer.\n * @param {Number} [cfg.defaultScaleCanvasResolutionFactor=0.6] The factor by which we downscale the canvas resolution whenever we stop interacting with the Viewer.\n * @param {Number} [cfg.scaleCanvasResolutionFactor=0.6] The factor by which we downscale the canvas resolution whenever we interact with the Viewer.\n * @param {Boolean} [cfg.delayBeforeRestore=true] Whether to temporarily have a delay before restoring normal rendering after we stop interacting with the Viewer.\n * @param {Number} [cfg.delayBeforeRestoreSeconds=0.5] Delay in seconds before restoring normal rendering after we stop interacting with the Viewer.\n */\n constructor(viewer, cfg = {}) {\n\n super(\"FastNav\", viewer);\n\n this._hideColorTexture = cfg.hideColorTexture !== false;\n this._hidePBR = cfg.hidePBR !== false;\n this._hideSAO = cfg.hideSAO !== false;\n this._hideEdges = cfg.hideEdges !== false;\n this._hideTransparentObjects = !!cfg.hideTransparentObjects;\n this._scaleCanvasResolution = !!cfg.scaleCanvasResolution;\n this._defaultScaleCanvasResolutionFactor = cfg.defaultScaleCanvasResolutionFactor || 1.0;\n this._scaleCanvasResolutionFactor = cfg.scaleCanvasResolutionFactor || 0.6;\n this._delayBeforeRestore = (cfg.delayBeforeRestore !== false);\n this._delayBeforeRestoreSeconds = cfg.delayBeforeRestoreSeconds || 0.5;\n\n let timer = this._delayBeforeRestoreSeconds * 1000;\n let fastMode = false;\n\n const switchToLowQuality = () => {\n timer = (this._delayBeforeRestoreSeconds * 1000);\n if (!fastMode) {\n viewer.scene._renderer.setColorTextureEnabled(!this._hideColorTexture);\n viewer.scene._renderer.setPBREnabled(!this._hidePBR);\n viewer.scene._renderer.setSAOEnabled(!this._hideSAO);\n viewer.scene._renderer.setTransparentEnabled(!this._hideTransparentObjects);\n viewer.scene._renderer.setEdgesEnabled(!this._hideEdges);\n if (this._scaleCanvasResolution) {\n viewer.scene.canvas.resolutionScale = this._scaleCanvasResolutionFactor;\n } else {\n viewer.scene.canvas.resolutionScale = this._defaultScaleCanvasResolutionFactor;\n }\n fastMode = true;\n }\n };\n\n const switchToHighQuality = () => {\n viewer.scene.canvas.resolutionScale = this._defaultScaleCanvasResolutionFactor;\n viewer.scene._renderer.setEdgesEnabled(true);\n viewer.scene._renderer.setColorTextureEnabled(true);\n viewer.scene._renderer.setPBREnabled(true);\n viewer.scene._renderer.setSAOEnabled(true);\n viewer.scene._renderer.setTransparentEnabled(true);\n fastMode = false;\n };\n\n this._onCanvasBoundary = viewer.scene.canvas.on(\"boundary\", switchToLowQuality);\n this._onCameraMatrix = viewer.scene.camera.on(\"matrix\", switchToLowQuality);\n\n this._onSceneTick = viewer.scene.on(\"tick\", (tickEvent) => {\n if (!fastMode) {\n return;\n }\n timer -= tickEvent.deltaTime;\n if ((!this._delayBeforeRestore) || timer <= 0) {\n switchToHighQuality();\n }\n });\n\n let down = false;\n\n this._onSceneMouseDown = viewer.scene.input.on(\"mousedown\", () => {\n down = true;\n });\n\n this._onSceneMouseUp = viewer.scene.input.on(\"mouseup\", () => {\n down = false;\n });\n\n this._onSceneMouseMove = viewer.scene.input.on(\"mousemove\", () => {\n if (!down) {\n return;\n }\n switchToLowQuality();\n });\n }\n\n /**\n * Gets whether to temporarily hide color textures whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @return {Boolean} ````true```` if hiding color textures.\n */\n get hideColorTexture() {\n return this._hideColorTexture;\n }\n\n /**\n * Sets whether to temporarily hide color textures whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @param {Boolean} hideColorTexture ````true```` to hide color textures.\n */\n set hideColorTexture(hideColorTexture) {\n this._hideColorTexture = hideColorTexture;\n }\n \n /**\n * Gets whether to temporarily hide physically-based rendering (PBR) whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @return {Boolean} ````true```` if hiding PBR.\n */\n get hidePBR() {\n return this._hidePBR;\n }\n\n /**\n * Sets whether to temporarily hide physically-based rendering (PBR) whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @param {Boolean} hidePBR ````true```` to hide PBR.\n */\n set hidePBR(hidePBR) {\n this._hidePBR = hidePBR;\n }\n\n /**\n * Gets whether to temporarily hide scalable ambient shadows (SAO) whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @return {Boolean} ````true```` if hiding SAO.\n */\n get hideSAO() {\n return this._hideSAO;\n }\n\n /**\n * Sets whether to temporarily hide scalable ambient shadows (SAO) whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @param {Boolean} hideSAO ````true```` to hide SAO.\n */\n set hideSAO(hideSAO) {\n this._hideSAO = hideSAO;\n }\n\n /**\n * Gets whether to temporarily hide edges whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @return {Boolean} ````true```` if hiding edges.\n */\n get hideEdges() {\n return this._hideEdges;\n }\n\n /**\n * Sets whether to temporarily hide edges whenever we interact with the Viewer.\n *\n * Default is ````true````.\n *\n * @param {Boolean} hideEdges ````true```` to hide edges.\n */\n set hideEdges(hideEdges) {\n this._hideEdges = hideEdges;\n }\n\n /**\n * Gets whether to temporarily hide transparent objects whenever we interact with the Viewer.\n *\n * Does not hide X-rayed, selected, highlighted objects.\n *\n * Default is ````false````.\n *\n * @return {Boolean} ````true```` if hiding transparent objects.\n */\n get hideTransparentObjects() {\n return this._hideTransparentObjects\n }\n\n /**\n * Sets whether to temporarily hide transparent objects whenever we interact with the Viewer.\n *\n * Does not hide X-rayed, selected, highlighted objects.\n *\n * Default is ````false````.\n *\n * @param {Boolean} hideTransparentObjects ````true```` to hide transparent objects.\n */\n set hideTransparentObjects(hideTransparentObjects) {\n this._hideTransparentObjects = (hideTransparentObjects !== false);\n }\n\n /**\n * Gets whether to temporarily scale the canvas resolution whenever we interact with the Viewer.\n *\n * Default is ````false````.\n *\n * The scaling factor is configured via {@link FastNavPlugin#scaleCanvasResolutionFactor}.\n *\n * @return {Boolean} ````true```` if scaling the canvas resolution.\n */\n get scaleCanvasResolution() {\n return this._scaleCanvasResolution;\n }\n\n /**\n * Sets the factor to which we restore the canvas resolution scale when we stop interacting with the viewer.\n *\n * Default is ````false````.\n *\n * The scaling factor is configured via {@link FastNavPlugin#scaleCanvasResolutionFactor}.\n *\n * @param {Boolean} scaleCanvasResolution ````true```` to scale the canvas resolution.\n */\n set scaleCanvasResolution(scaleCanvasResolution) {\n this._scaleCanvasResolution = scaleCanvasResolution;\n }\n\n /**\n * Gets the factor to which we restore the canvas resolution scale when we stop interacting with the viewer.\n *\n * Default is ````1.0````.\n *\n * Enable canvas resolution scaling by setting {@link FastNavPlugin#scaleCanvasResolution} ````true````.\n *\n * @return {Number} Factor by scale canvas resolution when we stop interacting with the viewer.\n */\n get defaultScaleCanvasResolutionFactor() {\n return this._defaultScaleCanvasResolutionFactor;\n }\n\n /**\n * Sets the factor to which we restore the canvas resolution scale when we stop interacting with the viewer.\n *\n * Accepted range is ````[0.0 .. 1.0]````.\n *\n * Default is ````1.0````.\n *\n * Enable canvas resolution scaling by setting {@link FastNavPlugin#scaleCanvasResolution} ````true````.\n *\n * @param {Number} defaultScaleCanvasResolutionFactor Factor by scale canvas resolution when we stop interacting with the viewer.\n */\n set defaultScaleCanvasResolutionFactor(defaultScaleCanvasResolutionFactor) {\n this._defaultScaleCanvasResolutionFactor = defaultScaleCanvasResolutionFactor || 1.0;\n }\n\n /**\n * Gets the factor by which we temporarily scale the canvas resolution when we interact with the viewer.\n *\n * Default is ````0.6````.\n *\n * Enable canvas resolution scaling by setting {@link FastNavPlugin#scaleCanvasResolution} ````true````.\n *\n * @return {Number} Factor by which we scale the canvas resolution.\n */\n get scaleCanvasResolutionFactor() {\n return this._scaleCanvasResolutionFactor;\n }\n\n /**\n * Sets the factor by which we temporarily scale the canvas resolution when we interact with the viewer.\n *\n * Accepted range is ````[0.0 .. 1.0]````.\n *\n * Default is ````0.6````.\n *\n * Enable canvas resolution scaling by setting {@link FastNavPlugin#scaleCanvasResolution} ````true````.\n *\n * @param {Number} scaleCanvasResolutionFactor Factor by which we scale the canvas resolution.\n */\n set scaleCanvasResolutionFactor(scaleCanvasResolutionFactor) {\n this._scaleCanvasResolutionFactor = scaleCanvasResolutionFactor || 0.6;\n }\n\n /**\n * Gets whether to have a delay before restoring normal rendering after we stop interacting with the Viewer.\n *\n * The delay duration is configured via {@link FastNavPlugin#delayBeforeRestoreSeconds}.\n *\n * Default is ````true````.\n *\n * @return {Boolean} Whether to have a delay.\n */\n get delayBeforeRestore() {\n return this._delayBeforeRestore;\n }\n\n /**\n * Sets whether to have a delay before restoring normal rendering after we stop interacting with the Viewer.\n *\n * The delay duration is configured via {@link FastNavPlugin#delayBeforeRestoreSeconds}.\n *\n * Default is ````true````.\n *\n * @param {Boolean} delayBeforeRestore Whether to have a delay.\n */\n set delayBeforeRestore(delayBeforeRestore) {\n this._delayBeforeRestore = delayBeforeRestore;\n }\n\n /**\n * Gets the delay before restoring normal rendering after we stop interacting with the Viewer.\n *\n * The delay is enabled when {@link FastNavPlugin#delayBeforeRestore} is ````true````.\n *\n * Default is ````0.5```` seconds.\n *\n * @return {Number} Delay in seconds.\n */\n get delayBeforeRestoreSeconds() {\n return this._delayBeforeRestoreSeconds;\n }\n\n /**\n * Sets the delay before restoring normal rendering after we stop interacting with the Viewer.\n *\n * The delay is enabled when {@link FastNavPlugin#delayBeforeRestore} is ````true````.\n *\n * Default is ````0.5```` seconds.\n *\n * @param {Number} delayBeforeRestoreSeconds Delay in seconds.\n */\n set delayBeforeRestoreSeconds(delayBeforeRestoreSeconds) {\n this._delayBeforeRestoreSeconds = delayBeforeRestoreSeconds !== null && delayBeforeRestoreSeconds !== undefined ? delayBeforeRestoreSeconds : 0.5;\n }\n\n /**\n * @private\n */\n send(name, value) {\n }\n\n /**\n * Destroys this plugin.\n */\n destroy() {\n this.viewer.scene.camera.off(this._onCameraMatrix);\n this.viewer.scene.canvas.off(this._onCanvasBoundary);\n this.viewer.scene.input.off(this._onSceneMouseDown);\n this.viewer.scene.input.off(this._onSceneMouseUp);\n this.viewer.scene.input.off(this._onSceneMouseMove);\n this.viewer.scene.off(this._onSceneTick);\n super.destroy();\n }\n}\n\n/**\n * Default data access strategy for {@link GLTFLoaderPlugin}.\n *\n * This just loads assets using XMLHttpRequest.\n */\nclass GLTFDefaultDataSource {\n\n constructor() {\n }\n\n /**\n * Gets metamodel JSON.\n *\n * @param {String|Number} metaModelSrc Identifies the metamodel JSON asset.\n * @param {Function} ok Fired on successful loading of the metamodel JSON asset.\n * @param {Function} error Fired on error while loading the metamodel JSON asset.\n */\n getMetaModel(metaModelSrc, ok, error) {\n utils.loadJSON(metaModelSrc,\n (json) => {\n ok(json);\n },\n function (errMsg) {\n error(errMsg);\n });\n }\n\n /**\n * Gets glTF JSON.\n *\n * @param {String|Number} glTFSrc Identifies the glTF JSON asset.\n * @param {Function} ok Fired on successful loading of the glTF JSON asset.\n * @param {Function} error Fired on error while loading the glTF JSON asset.\n */\n getGLTF(glTFSrc, ok, error) {\n utils.loadArraybuffer(glTFSrc,\n (gltf) => {\n ok(gltf);\n },\n function (errMsg) {\n error(errMsg);\n });\n }\n\n /**\n * Gets binary glTF file.\n *\n * @param {String|Number} glbSrc Identifies the .glb asset.\n * @param {Function} ok Fired on successful loading of the .glb asset.\n * @param {Function} error Fired on error while loading the .glb asset.\n */\n getGLB(glbSrc, ok, error) {\n utils.loadArraybuffer(glbSrc,\n (arraybuffer) => {\n ok(arraybuffer);\n },\n function (errMsg) {\n error(errMsg);\n });\n }\n\n /**\n * Gets glTF binary attachment.\n *\n * Note that this method requires the source of the glTF JSON asset. This is because the binary attachment\n * source could be relative to the glTF source, IE. it may not be a global ID.\n *\n * @param {String|Number} glTFSrc Identifies the glTF JSON asset.\n * @param {String|Number} binarySrc Identifies the glTF binary asset.\n * @param {Function} ok Fired on successful loading of the glTF binary asset.\n * @param {Function} error Fired on error while loading the glTF binary asset.\n */\n getArrayBuffer(glTFSrc, binarySrc, ok, error) {\n loadArraybuffer(glTFSrc, binarySrc,\n (arrayBuffer) => {\n ok(arrayBuffer);\n },\n function (errMsg) {\n error(errMsg);\n });\n }\n}\n\nfunction loadArraybuffer(glTFSrc, binarySrc, ok, err) {\n // Check for data: URI\n var defaultCallback = () => {\n };\n ok = ok || defaultCallback;\n err = err || defaultCallback;\n const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;\n const dataUriRegexResult = binarySrc.match(dataUriRegex);\n if (dataUriRegexResult) { // Safari can't handle data URIs through XMLHttpRequest\n const isBase64 = !!dataUriRegexResult[2];\n var data = dataUriRegexResult[3];\n data = window.decodeURIComponent(data);\n if (isBase64) {\n data = window.atob(data);\n }\n try {\n const buffer = new ArrayBuffer(data.length);\n const view = new Uint8Array(buffer);\n for (var i = 0; i < data.length; i++) {\n view[i] = data.charCodeAt(i);\n }\n core.scheduleTask(function () {\n ok(buffer);\n });\n } catch (error) {\n core.scheduleTask(function () {\n err(error);\n });\n }\n } else {\n const basePath = getBasePath$1(glTFSrc);\n const url = basePath + binarySrc;\n const request = new XMLHttpRequest();\n request.open('GET', url, true);\n request.responseType = 'arraybuffer';\n request.onreadystatechange = function () {\n if (request.readyState === 4) {\n if (request.status === 200) {\n ok(request.response);\n } else {\n err('loadArrayBuffer error : ' + request.response);\n }\n }\n };\n request.send(null);\n }\n}\n\nfunction getBasePath$1(src) {\n var i = src.lastIndexOf(\"/\");\n return (i !== 0) ? src.substring(0, i + 1) : \"\";\n}\n\n/**\n * @desc Localization service for a {@link Viewer}.\n *\n * * A LocaleService is a container of string translations (\"messages\") for various locales.\n * * A {@link Viewer} has its own default LocaleService at {@link Viewer#localeService}.\n * * We can replace that with our own LocaleService, or a custom subclass, via the Viewer's constructor.\n * * Viewer plugins that need localized translations will attempt to them for the currently active locale from the LocaleService.\n * * Whenever we switch the LocaleService to a different locale, plugins will automatically refresh their translations for that locale.\n *\n * ## Usage\n *\n * In the example below, we'll create a {@link Viewer} that uses an {@link XKTLoaderPlugin} to load a BIM model, and a\n * {@link NavCubePlugin}, which shows a camera navigation cube in the corner of the canvas.\n *\n * We'll also configure our Viewer with our own LocaleService instance, configured with English, Māori and French\n * translations for our NavCubePlugin.\n *\n * We could instead have just used the Viewer's default LocaleService, but this example demonstrates how we might\n * configure the Viewer our own custom LocaleService subclass.\n *\n * The translations fetched by our NavCubePlugin will be:\n *\n * * \"NavCube.front\"\n * * \"NavCube.back\"\n * * \"NavCube.top\"\n * * \"NavCube.bottom\"\n * * \"NavCube.left\"\n * * \"NavCube.right\"\n *\n *
\n * These are paths that resolve to our translations for the currently active locale, and are hard-coded within\n * the NavCubePlugin.\n *\n * For example, if the LocaleService's locale is set to \"fr\", then the path \"NavCube.back\" will drill down\n * into ````messages->fr->NavCube->front```` and fetch \"Arrière\".\n *\n * If we didn't provide that particular translation in our LocaleService, or any translations for that locale,\n * then the NavCubePlugin will just fall back on its own default hard-coded translation, which in this case is \"BACK\".\n *\n * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/index.html#localization_NavCubePlugin)]\n *\n * ````javascript\n * import {Viewer, LocaleService, NavCubePlugin, XKTLoaderPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n *\n * canvasId: \"myCanvas\",\n *\n * localeService: new LocaleService({\n * messages: {\n * \"en\": { // English\n * \"NavCube\": {\n * \"front\": \"Front\",\n * \"back\": \"Back\",\n * \"top\": \"Top\",\n * \"bottom\": \"Bottom\",\n * \"left\": \"Left\",\n * \"right\": \"Right\"\n * }\n * },\n * \"mi\": { // Māori\n * \"NavCube\": {\n * \"front\": \"Mua\",\n * \"back\": \"Tuarā\",\n * \"top\": \"Runga\",\n * \"bottom\": \"Raro\",\n * \"left\": \"Mauī\",\n * \"right\": \"Tika\"\n * }\n * },\n * \"fr\": { // Francais\n * \"NavCube\": {\n * \"front\": \"Avant\",\n * \"back\": \"Arrière\",\n * \"top\": \"Supérieur\",\n * \"bottom\": \"Inférieur\",\n * \"left\": \"Gauche\",\n * \"right\": \"Droit\"\n * }\n * }\n * },\n * locale: \"en\"\n * })\n * });\n *\n * viewer.camera.eye = [-3.93, 2.85, 27.01];\n * viewer.camera.look = [4.40, 3.72, 8.89];\n * viewer.camera.up = [-0.01, 0.99, 0.03];\n *\n * const navCubePlugin = new NavCubePlugin(viewer, {\n * canvasID: \"myNavCubeCanvas\"\n * });\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/Duplex.ifc.xkt\",\n * edges: true\n * });\n * ````\n *\n * We can dynamically switch our Viewer to a different locale at any time, which will update the text on the\n * faces of our NavCube:\n *\n * ````javascript\n * viewer.localeService.locale = \"mi\"; // Switch to Māori\n * ````\n *\n * We can load new translations at any time:\n *\n * ````javascript\n * viewer.localeService.loadMessages({\n * \"jp\": { // Japanese\n * \"NavCube\": {\n * \"front\": \"前部\",\n * \"back\": \"裏\",\n * \"top\": \"上\",\n * \"bottom\": \"底\",\n * \"left\": \"左\",\n * \"right\": \"右\"\n * }\n * }\n * });\n * ````\n *\n * And we can clear the translations if needed:\n *\n * ````javascript\n * viewer.localeService.clearMessages();\n * ````\n *\n * We can get an \"updated\" event from the LocaleService whenever we switch locales or load messages, which is useful\n * for triggering UI elements to refresh themselves with updated translations. Internally, our {@link NavCubePlugin}\n * subscribes to this event, fetching new strings for itself via {@link LocaleService#translate} each time the\n * event is fired.\n *\n * ````javascript\n * viewer.localeService.on(\"updated\", () => {\n * console.log( viewer.localeService.translate(\"NavCube.left\") );\n * });\n * ````\n * @since 2.0\n */\nclass LocaleService {\n\n /**\n * Constructs a LocaleService.\n *\n * @param {*} [params={}]\n * @param {JSON} [params.messages]\n * @param {String} [params.locale]\n */\n constructor(params = {}) {\n\n this._eventSubIDMap = null;\n this._eventSubEvents = null;\n this._eventSubs = null;\n this._events = null;\n\n this._locale = \"en\";\n this._messages = {};\n this._locales = [];\n this._locale = \"en\";\n\n this.messages = params.messages;\n this.locale = params.locale;\n }\n\n /**\n * Replaces the current set of locale translations.\n *\n * * Fires an \"updated\" event when done.\n * * Automatically refreshes any plugins that depend on the translations.\n * * Does not change the current locale.\n *\n * ## Usage\n *\n * ````javascript\n * viewer.localeService.setMessages({\n * messages: {\n * \"en\": { // English\n * \"NavCube\": {\n * \"front\": \"Front\",\n * \"back\": \"Back\",\n * \"top\": \"Top\",\n * \"bottom\": \"Bottom\",\n * \"left\": \"Left\",\n * \"right\": \"Right\"\n * }\n * },\n * \"mi\": { // Māori\n * \"NavCube\": {\n * \"front\": \"Mua\",\n * \"back\": \"Tuarā\",\n * \"top\": \"Runga\",\n * \"bottom\": \"Raro\",\n * \"left\": \"Mauī\",\n * \"right\": \"Tika\"\n * }\n * }\n * }\n * });\n * ````\n *\n * @param {*} messages The new translations.\n */\n set messages(messages) {\n this._messages = messages || {};\n this._locales = Object.keys(this._messages);\n this.fire(\"updated\", this);\n }\n\n /**\n * Loads a new set of locale translations, adding them to the existing translations.\n *\n * * Fires an \"updated\" event when done.\n * * Automatically refreshes any plugins that depend on the translations.\n * * Does not change the current locale.\n *\n * ## Usage\n *\n * ````javascript\n * viewer.localeService.loadMessages({\n * \"jp\": { // Japanese\n * \"NavCube\": {\n * \"front\": \"前部\",\n * \"back\": \"裏\",\n * \"top\": \"上\",\n * \"bottom\": \"底\",\n * \"left\": \"左\",\n * \"right\": \"右\"\n * }\n * }\n * });\n * ````\n *\n * @param {*} messages The new translations.\n */\n loadMessages(messages = {}) {\n for (let locale in messages) {\n this._messages[locale] = messages[locale];\n }\n this.messages = this._messages;\n }\n\n /**\n * Clears all locale translations.\n *\n * * Fires an \"updated\" event when done.\n * * Does not change the current locale.\n * * Automatically refreshes any plugins that depend on the translations, which will cause those\n * plugins to fall back on their internal hard-coded text values, since this method removes all\n * our translations.\n */\n clearMessages() {\n this.messages = {};\n }\n\n /**\n * Gets the list of available locales.\n *\n * These are derived from the currently configured set of translations.\n *\n * @returns {String[]} The list of available locales.\n */\n get locales() {\n return this._locales;\n }\n\n /**\n * Sets the current locale.\n *\n * * Fires an \"updated\" event when done.\n * * The given locale does not need to be in the list of available locales returned by {@link LocaleService#locales}, since\n * this method assumes that you may want to load the locales at a later point.\n * * Automatically refreshes any plugins that depend on the translations.\n * * We can then get translations for the locale, if translations have been loaded for it, via {@link LocaleService#translate} and {@link LocaleService#translatePlurals}.\n *\n * @param {String} locale The new current locale.\n */\n set locale(locale) {\n locale = locale || \"de\";\n if (this._locale === locale) {\n return;\n }\n this._locale = locale;\n this.fire(\"updated\", locale);\n }\n\n /**\n * Gets the current locale.\n *\n * @returns {String} The current locale.\n */\n get locale() {\n return this._locale;\n }\n\n /**\n * Translates the given string according to the current locale.\n *\n * Returns null if no translation can be found.\n *\n * @param {String} msg String to translate.\n * @param {*} [args] Extra parameters.\n * @returns {String|null} Translated string if found, else null.\n */\n translate(msg, args) {\n const localeMessages = this._messages[this._locale];\n if (!localeMessages) {\n return null;\n }\n const localeMessage = resolvePath$1(msg, localeMessages);\n if (localeMessage) {\n if (args) {\n return vsprintf(localeMessage, args);\n }\n return localeMessage;\n }\n return null;\n }\n\n /**\n * Translates the given phrase according to the current locale.\n *\n * Returns null if no translation can be found.\n *\n * @param {String} msg Phrase to translate.\n * @param {Number} count The plural number.\n * @param {*} [args] Extra parameters.\n * @returns {String|null} Translated string if found, else null.\n */\n translatePlurals(msg, count, args) {\n const localeMessages = this._messages[this._locale];\n if (!localeMessages) {\n return null;\n }\n let localeMessage = resolvePath$1(msg, localeMessages);\n count = parseInt(\"\" + count, 10);\n if (count === 0) {\n localeMessage = localeMessage.zero;\n } else {\n localeMessage = (count > 1) ? localeMessage.other : localeMessage.one;\n }\n if (!localeMessage) {\n return null;\n }\n localeMessage = vsprintf(localeMessage, [count]);\n if (args) {\n localeMessage = vsprintf(localeMessage, args);\n }\n return localeMessage;\n }\n\n /**\n * Fires an event on this LocaleService.\n *\n * Notifies existing subscribers to the event, optionally retains the event to give to\n * any subsequent notifications on the event as they are made.\n *\n * @param {String} event The event type name.\n * @param {Object} value The event parameters.\n * @param {Boolean} [forget=false] When true, does not retain for subsequent subscribers.\n */\n fire(event, value, forget) {\n if (!this._events) {\n this._events = {};\n }\n if (!this._eventSubs) {\n this._eventSubs = {};\n }\n if (forget !== true) {\n this._events[event] = value || true; // Save notification\n }\n const subs = this._eventSubs[event];\n if (subs) {\n for (const subId in subs) {\n if (subs.hasOwnProperty(subId)) {\n const sub = subs[subId];\n sub.callback(value);\n }\n }\n }\n }\n\n /**\n * Subscribes to an event on this LocaleService.\n *\n * @param {String} event The event\n * @param {Function} callback Callback fired on the event\n * @return {String} Handle to the subscription, which may be used to unsubscribe with {@link #off}.\n */\n on(event, callback) {\n if (!this._events) {\n this._events = {};\n }\n if (!this._eventSubIDMap) {\n this._eventSubIDMap = new Map$1(); // Subscription subId pool\n }\n if (!this._eventSubEvents) {\n this._eventSubEvents = {};\n }\n if (!this._eventSubs) {\n this._eventSubs = {};\n }\n let subs = this._eventSubs[event];\n if (!subs) {\n subs = {};\n this._eventSubs[event] = subs;\n }\n const subId = this._eventSubIDMap.addItem(); // Create unique subId\n subs[subId] = {\n callback: callback\n };\n this._eventSubEvents[subId] = event;\n const value = this._events[event];\n if (value !== undefined) {\n callback(value);\n }\n return subId;\n }\n\n /**\n * Cancels an event subscription that was previously made with {@link LocaleService#on}.\n *\n * @param {String} subId Subscription ID\n */\n off(subId) {\n if (subId === undefined || subId === null) {\n return;\n }\n if (!this._eventSubEvents) {\n return;\n }\n const event = this._eventSubEvents[subId];\n if (event) {\n delete this._eventSubEvents[subId];\n const subs = this._eventSubs[event];\n if (subs) {\n delete subs[subId];\n }\n this._eventSubIDMap.removeItem(subId); // Release subId\n }\n }\n}\n\nfunction resolvePath$1(key, json) {\n if (json[key]) {\n return json[key];\n }\n const parts = key.split(\".\");\n let obj = json;\n for (let i = 0, len = parts.length; obj && (i < len); i++) {\n const part = parts[i];\n obj = obj[part];\n }\n return obj;\n}\n\nfunction vsprintf(msg, args = []) {\n return msg.replace(/\\{\\{|\\}\\}|\\{(\\d+)\\}/g, function (m, n) {\n if (m === \"{{\") {\n return \"{\";\n }\n if (m === \"}}\") {\n return \"}\";\n }\n return args[n];\n });\n}\n\n/**\n * @desc Abstract base class for curve classes.\n */\nclass Curve extends Component {\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this Curve as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Curve}, generated automatically when omitted.\n * @param {Object} [cfg] Configs for this Curve.\n * @param {Number} [cfg.t=0] Current position on this Curve, in range between ````0..1````.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this.t = cfg.t;\n }\n\n /**\n * Sets the progress along this Curve.\n *\n * Automatically clamps to range ````[0..1]````.\n *\n * Default value is ````0````.\n *\n * @param {Number} value The progress value.\n */\n set t(value) {\n value = value || 0;\n this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);\n }\n\n /**\n * Gets the progress along this Curve.\n *\n * @returns {Number} The progress value.\n */\n get t() {\n return this._t;\n }\n\n /**\n * Gets the tangent on this Curve at position {@link Curve#t}.\n *\n * @returns {Number[]} The tangent.\n */\n get tangent() {\n return this.getTangent(this._t);\n }\n\n /**\n * Gets the length of this Curve.\n *\n * @returns {Number} The Curve length.\n */\n get length() {\n var lengths = this._getLengths();\n return lengths[lengths.length - 1];\n }\n\n /**\n * Returns a normalized tangent vector on this Curve at the given position.\n *\n * @param {Number} t Position to get tangent at.\n * @returns {Number[]} Normalized tangent vector\n */\n getTangent(t) {\n var delta = 0.0001;\n if (t === undefined) {\n t = this._t;\n }\n var t1 = t - delta;\n var t2 = t + delta;\n if (t1 < 0) {\n t1 = 0;\n }\n if (t2 > 1) {\n t2 = 1;\n }\n var pt1 = this.getPoint(t1);\n var pt2 = this.getPoint(t2);\n var vec = math.subVec3(pt2, pt1, []);\n return math.normalizeVec3(vec, []);\n }\n\n getPointAt(u) {\n var t = this.getUToTMapping(u);\n return this.getPoint(t);\n }\n\n /**\n * Samples points on this Curve, at the given number of equally-spaced divisions.\n *\n * @param {Number} divisions The number of divisions.\n * @returns {{Array of Array}} Array of sampled 3D points.\n */\n getPoints(divisions) {\n if (!divisions) {\n divisions = 5;\n }\n var d, pts = [];\n for (d = 0; d <= divisions; d++) {\n pts.push(this.getPoint(d / divisions));\n }\n return pts;\n }\n\n _getLengths(divisions) {\n if (!divisions) {\n divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions) : 200;\n }\n if (this.cacheArcLengths && (this.cacheArcLengths.length === divisions + 1) && !this.needsUpdate) {\n return this.cacheArcLengths;\n\n }\n this.needsUpdate = false;\n var cache = [];\n var current;\n var last = this.getPoint(0);\n var p;\n var sum = 0;\n cache.push(0);\n for (p = 1; p <= divisions; p++) {\n current = this.getPoint(p / divisions);\n sum += math.lenVec3(math.subVec3(current, last, []));\n cache.push(sum);\n last = current;\n }\n this.cacheArcLengths = cache;\n return cache; // { sums: cache, sum:sum }, Sum is in the last element.\n }\n\n _updateArcLengths() {\n this.needsUpdate = true;\n this._getLengths();\n }\n\n // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance\n\n getUToTMapping(u, distance) {\n var arcLengths = this._getLengths();\n var i = 0;\n var il = arcLengths.length;\n var t;\n var targetArcLength; // The targeted u distance value to get\n if (distance) {\n targetArcLength = distance;\n } else {\n targetArcLength = u * arcLengths[il - 1];\n }\n //var time = Date.now();\n var low = 0, high = il - 1, comparison;\n while (low <= high) {\n i = Math.floor(low + (high - low) / 2); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats\n comparison = arcLengths[i] - targetArcLength;\n if (comparison < 0) {\n low = i + 1;\n } else if (comparison > 0) {\n high = i - 1;\n } else {\n high = i;\n break;\n // DONE\n }\n }\n i = high;\n if (arcLengths[i] === targetArcLength) {\n t = i / (il - 1);\n return t;\n }\n var lengthBefore = arcLengths[i];\n var lengthAfter = arcLengths[i + 1];\n var segmentLength = lengthAfter - lengthBefore;\n var segmentFraction = (targetArcLength - lengthBefore) / segmentLength;\n t = (i + segmentFraction) / (il - 1);\n return t;\n }\n}\n\n/**\n * @desc A {@link Curve} along which a 3D position can be animated.\n *\n * * As shown in the diagram below, a SplineCurve is defined by three or more control points.\n * * You can sample a {@link SplineCurve#point} and a {@link Curve#tangent} vector on a SplineCurve for any given value of {@link SplineCurve#t} in the range ````[0..1]````.\n * * When you set {@link SplineCurve#t} on a SplineCurve, its {@link SplineCurve#point} and {@link Curve#tangent} will update accordingly.\n * * To build a complex path, you can combine an unlimited combination of SplineCurves, {@link CubicBezierCurve} and {@link QuadraticBezierCurve} into a {@link Path}.\n *
\n *
\n *\n * * Spline Curve from Wikipedia*\n */\nclass SplineCurve extends Curve {\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this SplineCurve as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Array} [cfg.points=[]] Control points on this SplineCurve.\n * @param {Number} [cfg.t=0] Current position on this SplineCurve, in range between 0..1.\n * @param {Number} [cfg.t=0] Current position on this CubicBezierCurve, in range between 0..1.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this.points = cfg.points;\n this.t = cfg.t;\n }\n\n /**\n * Sets the control points on this SplineCurve.\n *\n * Default value is ````[]````.\n *\n * @param {Number[]} value New control points.\n */\n set points(value) {\n this._points = value || [];\n }\n\n /**\n * Gets the control points on this SplineCurve.\n *\n * Default value is ````[]````.\n *\n * @returns {Number[]} The control points.\n */\n get points() {\n return this._points;\n }\n\n /**\n * Sets the progress along this SplineCurve.\n *\n * Automatically clamps to range ````[0..1]````.\n *\n * Default value is ````0````.\n *\n * @param {Number} value The new progress.\n */\n set t(value) {\n value = value || 0;\n this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);\n }\n\n /**\n * Gets the progress along this SplineCurve.\n *\n * Automatically clamps to range ````[0..1]````.\n *\n * Default value is ````0````.\n *\n * @returns {Number} The new progress.\n */\n get t() {\n return this._t;\n }\n\n /**\n * Gets the point on this SplineCurve at position {@link SplineCurve#t}.\n *\n * @returns {Number[]} The point at {@link SplineCurve#t}.\n */\n get point() {\n return this.getPoint(this._t);\n }\n\n /**\n * Returns point on this SplineCurve at the given position.\n *\n * @param {Number} t Position to get point at.\n * @returns {Number[]} Point at the given position.\n */\n getPoint(t) {\n\n var points = this.points;\n\n if (points.length < 3) {\n this.error(\"Can't sample point from SplineCurve - not enough points on curve - returning [0,0,0].\");\n return;\n }\n\n var point = (points.length - 1) * t;\n\n var intPoint = Math.floor(point);\n var weight = point - intPoint;\n\n var point0 = points[intPoint === 0 ? intPoint : intPoint - 1];\n var point1 = points[intPoint];\n var point2 = points[intPoint > points.length - 2 ? points.length - 1 : intPoint + 1];\n var point3 = points[intPoint > points.length - 3 ? points.length - 1 : intPoint + 2];\n\n var vector = math.vec3();\n\n vector[0] = math.catmullRomInterpolate(point0[0], point1[0], point2[0], point3[0], weight);\n vector[1] = math.catmullRomInterpolate(point0[1], point1[1], point2[1], point3[1], weight);\n vector[2] = math.catmullRomInterpolate(point0[2], point1[2], point2[2], point3[2], weight);\n\n return vector;\n }\n\n getJSON() {\n return {\n points: points,\n t: this._t\n };\n }\n}\n\nconst tempVec3a$8 = math.vec3();\n\n/**\n * @desc Defines a sequence of frames along which a {@link CameraPathAnimation} can animate a {@link Camera}.\n *\n * See {@link CameraPathAnimation} for usage.\n */\nclass CameraPath extends Component {\n\n /**\n * Returns \"CameraPath\".\n *\n * @private\n *\n * @returns {string} \"CameraPath\"\n */\n get type() {\n return \"CameraPath\"\n }\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this CameraPath as well.\n * @param [cfg] {*} Configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {{t:Number, eye:Object, look:Object, up: Object}[]} [cfg.frames] Initial sequence of frames.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._frames = [];\n\n this._eyeCurve = new SplineCurve(this);\n this._lookCurve = new SplineCurve(this);\n this._upCurve = new SplineCurve(this);\n\n if (cfg.frames) {\n this.addFrames(cfg.frames);\n this.smoothFrameTimes(1);\n }\n }\n\n /**\n * Gets the camera frames in this CameraPath.\n *\n * @returns {{t:Number, eye:Object, look:Object, up: Object}[]} The frames on this CameraPath.\n */\n get frames() {\n return this._frames;\n }\n\n /**\n * Gets the {@link SplineCurve} along which {@link Camera#eye} travels.\n * @returns {SplineCurve} The SplineCurve for {@link Camera#eye}.\n */\n get eyeCurve() {\n return this._eyeCurve;\n }\n\n /**\n * Gets the {@link SplineCurve} along which {@link Camera#look} travels.\n * @returns {SplineCurve} The SplineCurve for {@link Camera#look}.\n */\n get lookCurve() {\n return this._lookCurve;\n }\n\n /**\n * Gets the {@link SplineCurve} along which {@link Camera#up} travels.\n * @returns {SplineCurve} The SplineCurve for {@link Camera#up}.\n */\n get upCurve() {\n return this._upCurve;\n }\n\n /**\n * Adds a frame to this CameraPath, given as the current position of the {@link Camera}.\n *\n * @param {Number} t Time instant for the new frame.\n */\n saveFrame(t) {\n const camera = this.scene.camera;\n this.addFrame(t, camera.eye, camera.look, camera.up);\n }\n\n /**\n * Adds a frame to this CameraPath, specified as values for eye, look and up vectors at a given time instant.\n *\n * @param {Number} t Time instant for the new frame.\n * @param {Number[]} eye A three-element vector specifying the eye position for the new frame.\n * @param {Number[]} look A three-element vector specifying the look position for the new frame.\n * @param {Number[]} up A three-element vector specifying the up vector for the new frame.\n */\n addFrame(t, eye, look, up) {\n const frame = {\n t: t,\n eye: eye.slice(0),\n look: look.slice(0),\n up: up.slice(0)\n };\n this._frames.push(frame);\n this._eyeCurve.points.push(frame.eye);\n this._lookCurve.points.push(frame.look);\n this._upCurve.points.push(frame.up);\n }\n\n /**\n * Adds multiple frames to this CameraPath, each frame specified as a set of values for eye, look and up vectors at a given time instant.\n *\n * @param {{t:Number, eye:Object, look:Object, up: Object}[]} frames Frames to add to this CameraPath.\n */\n addFrames(frames) {\n let frame;\n for (let i = 0, len = frames.length; i < len; i++) {\n frame = frames[i];\n this.addFrame(frame.t || 0, frame.eye, frame.look, frame.up);\n }\n }\n\n /**\n * Sets the position of the {@link Camera} to a position interpolated within this CameraPath at the given time instant.\n *\n * @param {Number} t Time instant.\n */\n loadFrame(t) {\n\n const camera = this.scene.camera;\n\n t = t / (this._frames[this._frames.length - 1].t - this._frames[0].t);\n t = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);\n\n camera.eye = this._eyeCurve.getPoint(t, tempVec3a$8);\n camera.look = this._lookCurve.getPoint(t, tempVec3a$8);\n camera.up = this._upCurve.getPoint(t, tempVec3a$8);\n }\n\n /**\n * Gets eye, look and up vectors on this CameraPath at a given instant.\n *\n * @param {Number} t Time instant.\n * @param {Number[]} eye The eye position to update.\n * @param {Number[]} look The look position to update.\n * @param {Number[]} up The up vector to update.\n */\n sampleFrame(t, eye, look, up) {\n t = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);\n this._eyeCurve.getPoint(t, eye);\n this._lookCurve.getPoint(t, look);\n this._upCurve.getPoint(t, up);\n }\n\n /**\n * Given a total duration (in seconds) for this CameraPath, recomputes the time instant at each frame so that,\n * when animated by {@link CameraPathAnimation}, the {@link Camera} will move along the path at a constant rate.\n *\n * @param {Number} duration The total duration for this CameraPath.\n */\n smoothFrameTimes(duration) {\n const numFrames = this._frames.length;\n if (numFrames === 0) {\n return;\n }\n const vec = math.vec3();\n var totalLen = 0;\n this._frames[0].t = 0;\n const lens = [];\n for (let i = 1, len = this._frames.length; i < len; i++) {\n var lenVec = math.lenVec3(math.subVec3(this._frames[i].eye, this._frames[i - 1].eye, vec));\n lens[i] = lenVec;\n totalLen += lenVec;\n }\n for (let i = 1, len = this._frames.length; i < len; i++) {\n const interFrameRate = (lens[i] / totalLen) * duration;\n this._frames[i].t = this._frames[i-1].t + interFrameRate;\n }\n }\n\n /**\n * Removes all frames from this CameraPath.\n */\n clearFrames() {\n this._frames = [];\n this._eyeCurve.points = [];\n this._lookCurve.points = [];\n this._upCurve.points = [];\n }\n}\n\nconst tempVec3$4 = math.vec3();\nconst newLook = math.vec3();\nconst newEye = math.vec3();\nconst newUp = math.vec3();\nconst newLookEyeVec = math.vec3();\n\n/**\n * @desc Jumps or flies the {@link Scene}'s {@link Camera} to a given target.\n *\n * * Located at {@link Viewer#cameraFlight}\n * * Can fly or jump to its target.\n * * While flying, can be stopped, or redirected to a different target.\n * * Can also smoothly transition between ortho and perspective projections.\n *\n *\n * A CameraFlightAnimation's target can be:\n *\n * * specific ````eye````, ````look```` and ````up```` positions,\n * * an axis-aligned World-space bounding box (AABB), or\n * * an instance or ID of any {@link Component} subtype that provides a World-space AABB.\n *\n * A target can also contain a ````projection```` type to transition into. For example, if your {@link Camera#projection} is\n * currently ````\"perspective\"```` and you supply {@link CameraFlightAnimation#flyTo} with a ````projection```` property\n * equal to \"ortho\", then CameraFlightAnimation will smoothly transition the Camera into an orthographic projection.\n *\n * Configure {@link CameraFlightAnimation#fit} and {@link CameraFlightAnimation#fitFOV} to make it stop at the point\n * where the target occupies a certain amount of the field-of-view.\n *\n * ## Flying to an Entity\n *\n * Flying to an {@link Entity}:\n *\n * ````Javascript\n * var entity = new Mesh(viewer.scene);\n *\n * // Fly to the Entity's World-space AABB\n * viewer.cameraFlight.flyTo(entity);\n * ````\n * ## Flying to a Position\n *\n * Flying the CameraFlightAnimation from the previous example to specified eye, look and up positions:\n *\n * ````Javascript\n * viewer.cameraFlight.flyTo({\n * eye: [-5,-5,-5],\n * look: [0,0,0]\n * up: [0,1,0],\n * duration: 1 // Default, seconds\n * },() => {\n * // Done\n * });\n * ````\n *\n * ## Flying to an AABB\n *\n * Flying the CameraFlightAnimation from the previous two examples explicitly to the {@link Boundary3D\"}}Boundary3D's{{/crossLink}}\n * axis-aligned bounding box:\n *\n * ````Javascript\n * viewer.cameraFlight.flyTo(entity.aabb);\n * ````\n *\n * ## Transitioning Between Projections\n *\n * CameraFlightAnimation also allows us to smoothly transition between Camera projections. We can do that by itself, or\n * in addition to flying the Camera to a target.\n *\n * Let's transition the Camera to orthographic projection:\n *\n * [[Run example](/examples/index.html#camera_CameraFlightAnimation_projection)]\n *\n * ````Javascript\n * viewer.cameraFlight.flyTo({ projection: \"ortho\", () => {\n * // Done\n * });\n * ````\n *\n * Now let's transition the Camera back to perspective projection:\n *\n * ````Javascript\n * viewer.cameraFlight.flyTo({ projection: \"perspective\"}, () => {\n * // Done\n * });\n * ````\n *\n * Fly Camera to a position, while transitioning to orthographic projection:\n *\n * ````Javascript\n * viewer.cameraFlight.flyTo({\n * eye: [-100,20,2],\n * look: [0,0,-40],\n * up: [0,1,0],\n * projection: \"ortho\", () => {\n * // Done\n * });\n * ````\n */\nclass CameraFlightAnimation extends Component {\n\n /**\n * @private\n */\n get type() {\n return \"CameraFlightAnimation\";\n }\n\n /**\n @constructor\n @private\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._look1 = math.vec3();\n this._eye1 = math.vec3();\n this._up1 = math.vec3();\n this._look2 = math.vec3();\n this._eye2 = math.vec3();\n this._up2 = math.vec3();\n this._orthoScale1 = 1;\n this._orthoScale2 = 1;\n this._flying = false;\n this._flyEyeLookUp = false;\n this._flyingEye = false;\n this._flyingLook = false;\n this._callback = null;\n this._callbackScope = null;\n this._time1 = null;\n this._time2 = null;\n this.easing = cfg.easing !== false;\n\n this.duration = cfg.duration;\n this.fit = cfg.fit;\n this.fitFOV = cfg.fitFOV;\n this.trail = cfg.trail;\n }\n\n /**\n * Flies the {@link Camera} to a target.\n *\n * * When the target is a boundary, the {@link Camera} will fly towards the target and stop when the target fills most of the canvas.\n * * When the target is an explicit {@link Camera} position, given as ````eye````, ````look```` and ````up````, then CameraFlightAnimation will interpolate the {@link Camera} to that target and stop there.\n *\n * @param {Object|Component} [params=Scene] Either a parameters object or a {@link Component} subtype that has\n * an AABB. Defaults to the {@link Scene}, which causes the {@link Camera} to fit the Scene in view.\n * @param {Number} [params.arc=0] Factor in range ````[0..1]```` indicating how much the {@link Camera#eye} position\n * will swing away from its {@link Camera#look} position as it flies to the target.\n * @param {Number|String|Component} [params.component] ID or instance of a component to fly to. Defaults to the entire {@link Scene}.\n * @param {Number[]} [params.aabb] World-space axis-aligned bounding box (AABB) target to fly to.\n * @param {Number[]} [params.eye] Position to fly the eye position to.\n * @param {Number[]} [params.look] Position to fly the look position to.\n * @param {Number[]} [params.up] Position to fly the up vector to.\n * @param {String} [params.projection] Projection type to transition into as we fly. Can be any of the values of {@link Camera.projection}.\n * @param {Boolean} [params.fit=true] Whether to fit the target to the view volume. Overrides {@link CameraFlightAnimation#fit}.\n * @param {Number} [params.fitFOV] How much of field-of-view, in degrees, that a target {@link Entity} or its AABB should\n * fill the canvas on arrival. Overrides {@link CameraFlightAnimation#fitFOV}.\n * @param {Number} [params.duration] Flight duration in seconds. Overrides {@link CameraFlightAnimation#duration}.\n * @param {Number} [params.orthoScale] Animate the Camera's orthographic scale to this target value. See {@link Ortho#scale}.\n * @param {Function} [callback] Callback fired on arrival.\n * @param {Object} [scope] Optional scope for callback.\n */\n flyTo(params, callback, scope) {\n\n params = params || this.scene;\n\n if (this._flying) {\n this.stop();\n }\n\n this._flying = false;\n this._flyingEye = false;\n this._flyingLook = false;\n this._flyingEyeLookUp = false;\n\n this._callback = callback;\n this._callbackScope = scope;\n\n const camera = this.scene.camera;\n const flyToProjection = (!!params.projection) && (params.projection !== camera.projection);\n\n this._eye1[0] = camera.eye[0];\n this._eye1[1] = camera.eye[1];\n this._eye1[2] = camera.eye[2];\n\n this._look1[0] = camera.look[0];\n this._look1[1] = camera.look[1];\n this._look1[2] = camera.look[2];\n\n this._up1[0] = camera.up[0];\n this._up1[1] = camera.up[1];\n this._up1[2] = camera.up[2];\n\n this._orthoScale1 = camera.ortho.scale;\n this._orthoScale2 = params.orthoScale || this._orthoScale1;\n\n let aabb;\n let eye;\n let look;\n let up;\n let componentId;\n\n if (params.aabb) {\n aabb = params.aabb;\n\n } else if (params.length === 6) {\n aabb = params;\n\n } else if ((params.eye && params.look) || params.up) {\n eye = params.eye;\n look = params.look;\n up = params.up;\n\n } else if (params.eye) {\n eye = params.eye;\n\n } else if (params.look) {\n look = params.look;\n\n } else { // Argument must be an instance or ID of a Component (subtype)\n\n let component = params;\n\n if (utils.isNumeric(component) || utils.isString(component)) {\n\n componentId = component;\n component = this.scene.components[componentId];\n\n if (!component) {\n this.error(\"Component not found: \" + utils.inQuotes(componentId));\n if (callback) {\n if (scope) {\n callback.call(scope);\n } else {\n callback();\n }\n }\n return;\n }\n }\n if (!flyToProjection) {\n aabb = component.aabb || this.scene.aabb;\n }\n }\n\n const poi = params.poi;\n\n if (aabb) {\n\n if (aabb[3] < aabb[0] || aabb[4] < aabb[1] || aabb[5] < aabb[2]) { // Don't fly to an inverted boundary\n return;\n }\n\n if (aabb[3] === aabb[0] && aabb[4] === aabb[1] && aabb[5] === aabb[2]) { // Don't fly to an empty boundary\n return;\n }\n\n aabb = aabb.slice();\n const aabbCenter = math.getAABB3Center(aabb);\n\n this._look2 = poi || aabbCenter;\n\n const eyeLookVec = math.subVec3(this._eye1, this._look1, tempVec3$4);\n const eyeLookVecNorm = math.normalizeVec3(eyeLookVec);\n const diag = poi ? math.getAABB3DiagPoint(aabb, poi) : math.getAABB3Diag(aabb);\n const fitFOV = params.fitFOV || this._fitFOV;\n const sca = Math.abs(diag / Math.tan(fitFOV * math.DEGTORAD));\n\n this._orthoScale2 = diag * 1.1;\n\n this._eye2[0] = this._look2[0] + (eyeLookVecNorm[0] * sca);\n this._eye2[1] = this._look2[1] + (eyeLookVecNorm[1] * sca);\n this._eye2[2] = this._look2[2] + (eyeLookVecNorm[2] * sca);\n\n this._up2[0] = this._up1[0];\n this._up2[1] = this._up1[1];\n this._up2[2] = this._up1[2];\n\n this._flyingEyeLookUp = true;\n\n } else if (eye || look || up) {\n\n this._flyingEyeLookUp = !!eye && !!look && !!up;\n this._flyingEye = !!eye && !look;\n this._flyingLook = !!look && !eye;\n\n if (eye) {\n this._eye2[0] = eye[0];\n this._eye2[1] = eye[1];\n this._eye2[2] = eye[2];\n }\n\n if (look) {\n this._look2[0] = look[0];\n this._look2[1] = look[1];\n this._look2[2] = look[2];\n }\n\n if (up) {\n this._up2[0] = up[0];\n this._up2[1] = up[1];\n this._up2[2] = up[2];\n }\n }\n\n if (flyToProjection) {\n\n if (params.projection === \"ortho\" && camera.projection !== \"ortho\") {\n this._projection2 = \"ortho\";\n this._projMatrix1 = camera.projMatrix.slice();\n this._projMatrix2 = camera.ortho.matrix.slice();\n camera.projection = \"customProjection\";\n }\n\n if (params.projection === \"perspective\" && camera.projection !== \"perspective\") {\n this._projection2 = \"perspective\";\n this._projMatrix1 = camera.projMatrix.slice();\n this._projMatrix2 = camera.perspective.matrix.slice();\n camera.projection = \"customProjection\";\n }\n } else {\n this._projection2 = null;\n }\n\n this.fire(\"started\", params, true);\n\n this._time1 = Date.now();\n this._time2 = this._time1 + (params.duration ? params.duration * 1000 : this._duration);\n\n this._flying = true; // False as soon as we stop\n\n core.scheduleTask(this._update, this);\n }\n\n /**\n * Jumps the {@link Scene}'s {@link Camera} to the given target.\n *\n * * When the target is a boundary, this CameraFlightAnimation will position the {@link Camera} at where the target fills most of the canvas.\n * * When the target is an explicit {@link Camera} position, given as ````eye````, ````look```` and ````up```` vectors, then this CameraFlightAnimation will jump the {@link Camera} to that target.\n *\n * @param {*|Component} params Either a parameters object or a {@link Component} subtype that has a World-space AABB.\n * @param {Number} [params.arc=0] Factor in range [0..1] indicating how much the {@link Camera#eye} will swing away from its {@link Camera#look} as it flies to the target.\n * @param {Number|String|Component} [params.component] ID or instance of a component to fly to.\n * @param {Number[]} [params.aabb] World-space axis-aligned bounding box (AABB) target to fly to.\n * @param {Number[]} [params.eye] Position to fly the eye position to.\n * @param {Number[]} [params.look] Position to fly the look position to.\n * @param {Number[]} [params.up] Position to fly the up vector to.\n * @param {String} [params.projection] Projection type to transition into. Can be any of the values of {@link Camera.projection}.\n * @param {Number} [params.fitFOV] How much of field-of-view, in degrees, that a target {@link Entity} or its AABB should fill the canvas on arrival. Overrides {@link CameraFlightAnimation#fitFOV}.\n * @param {Boolean} [params.fit] Whether to fit the target to the view volume. Overrides {@link CameraFlightAnimation#fit}.\n */\n jumpTo(params) {\n this._jumpTo(params);\n }\n\n _jumpTo(params) {\n\n if (this._flying) {\n this.stop();\n }\n\n const camera = this.scene.camera;\n\n var aabb;\n var componentId;\n var newEye;\n var newLook;\n var newUp;\n\n if (params.aabb) { // Boundary3D\n aabb = params.aabb;\n\n } else if (params.length === 6) { // AABB\n aabb = params;\n\n } else if (params.eye || params.look || params.up) { // Camera pose\n newEye = params.eye;\n newLook = params.look;\n newUp = params.up;\n\n } else { // Argument must be an instance or ID of a Component (subtype)\n\n let component = params;\n\n if (utils.isNumeric(component) || utils.isString(component)) {\n componentId = component;\n component = this.scene.components[componentId];\n if (!component) {\n this.error(\"Component not found: \" + utils.inQuotes(componentId));\n return;\n }\n }\n aabb = component.aabb || this.scene.aabb;\n }\n\n const poi = params.poi;\n\n if (aabb) {\n\n if (aabb[3] <= aabb[0] || aabb[4] <= aabb[1] || aabb[5] <= aabb[2]) { // Don't fly to an empty boundary\n return;\n }\n\n var diag = poi ? math.getAABB3DiagPoint(aabb, poi) : math.getAABB3Diag(aabb);\n\n newLook = poi || math.getAABB3Center(aabb, newLook);\n\n if (this._trail) {\n math.subVec3(camera.look, newLook, newLookEyeVec);\n } else {\n math.subVec3(camera.eye, camera.look, newLookEyeVec);\n }\n\n math.normalizeVec3(newLookEyeVec);\n let dist;\n const fit = (params.fit !== undefined) ? params.fit : this._fit;\n\n if (fit) {\n dist = Math.abs((diag) / Math.tan((params.fitFOV || this._fitFOV) * math.DEGTORAD));\n\n } else {\n dist = math.lenVec3(math.subVec3(camera.eye, camera.look, tempVec3$4));\n }\n\n math.mulVec3Scalar(newLookEyeVec, dist);\n\n camera.eye = math.addVec3(newLook, newLookEyeVec, tempVec3$4);\n camera.look = newLook;\n\n this.scene.camera.ortho.scale = diag * 1.1;\n\n } else if (newEye || newLook || newUp) {\n\n if (newEye) {\n camera.eye = newEye;\n }\n if (newLook) {\n camera.look = newLook;\n }\n if (newUp) {\n camera.up = newUp;\n }\n }\n\n if (params.projection) {\n camera.projection = params.projection;\n }\n }\n\n _update() {\n if (!this._flying) {\n return;\n }\n const time = Date.now();\n let t = (time - this._time1) / (this._time2 - this._time1);\n const stopping = (t >= 1);\n\n if (t > 1) {\n t = 1;\n }\n\n const tFlight = this.easing ? CameraFlightAnimation._ease(t, 0, 1, 1) : t;\n const camera = this.scene.camera;\n\n if (this._flyingEye || this._flyingLook) {\n\n if (this._flyingEye) {\n math.subVec3(camera.eye, camera.look, newLookEyeVec);\n camera.eye = math.lerpVec3(tFlight, 0, 1, this._eye1, this._eye2, newEye);\n camera.look = math.subVec3(newEye, newLookEyeVec, newLook);\n } else if (this._flyingLook) {\n camera.look = math.lerpVec3(tFlight, 0, 1, this._look1, this._look2, newLook);\n camera.up = math.lerpVec3(tFlight, 0, 1, this._up1, this._up2, newUp);\n }\n\n } else if (this._flyingEyeLookUp) {\n\n camera.eye = math.lerpVec3(tFlight, 0, 1, this._eye1, this._eye2, newEye);\n camera.look = math.lerpVec3(tFlight, 0, 1, this._look1, this._look2, newLook);\n camera.up = math.lerpVec3(tFlight, 0, 1, this._up1, this._up2, newUp);\n }\n\n if (this._projection2) {\n const tProj = (this._projection2 === \"ortho\") ? CameraFlightAnimation._easeOutExpo(t, 0, 1, 1) : CameraFlightAnimation._easeInCubic(t, 0, 1, 1);\n camera.customProjection.matrix = math.lerpMat4(tProj, 0, 1, this._projMatrix1, this._projMatrix2);\n\n } else {\n camera.ortho.scale = this._orthoScale1 + (t * (this._orthoScale2 - this._orthoScale1));\n }\n\n if (stopping) {\n camera.ortho.scale = this._orthoScale2;\n this.stop();\n return;\n }\n core.scheduleTask(this._update, this); // Keep flying\n }\n\n static _ease(t, b, c, d) { // Quadratic easing out - decelerating to zero velocity http://gizma.com/easing\n t /= d;\n return -c * t * (t - 2) + b;\n }\n\n static _easeInCubic(t, b, c, d) {\n t /= d;\n return c * t * t * t + b;\n }\n\n static _easeOutExpo(t, b, c, d) {\n return c * (-Math.pow(2, -10 * t / d) + 1) + b;\n }\n\n /**\n * Stops an earlier flyTo, fires arrival callback.\n */\n stop() {\n if (!this._flying) {\n return;\n }\n this._flying = false;\n this._time1 = null;\n this._time2 = null;\n if (this._projection2) {\n this.scene.camera.projection = this._projection2;\n }\n const callback = this._callback;\n if (callback) {\n this._callback = null;\n if (this._callbackScope) {\n callback.call(this._callbackScope);\n } else {\n callback();\n }\n }\n this.fire(\"stopped\", true, true);\n }\n\n /**\n * Cancels an earlier flyTo without calling the arrival callback.\n */\n cancel() {\n if (!this._flying) {\n return;\n }\n this._flying = false;\n this._time1 = null;\n this._time2 = null;\n if (this._callback) {\n this._callback = null;\n }\n this.fire(\"canceled\", true, true);\n }\n\n /**\n * Sets the flight duration, in seconds, when calling {@link CameraFlightAnimation#flyTo}.\n *\n * Stops any flight currently in progress.\n *\n * default value is ````0.5````.\n *\n * @param {Number} value New duration value.\n */\n set duration(value) {\n this._duration = value ? (value * 1000.0) : 500;\n this.stop();\n }\n\n /**\n * Gets the flight duration, in seconds, when calling {@link CameraFlightAnimation#flyTo}.\n *\n * default value is ````0.5````.\n *\n * @returns {Number} New duration value.\n */\n get duration() {\n return this._duration / 1000.0;\n }\n\n /**\n * Sets if, when CameraFlightAnimation is flying to a boundary, it will always adjust the distance between the\n * {@link Camera#eye} and {@link Camera#look} so as to ensure that the target boundary is always filling the view volume.\n *\n * When false, the eye will remain at its current distance from the look position.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} value Set ````true```` to activate this behaviour.\n */\n set fit(value) {\n this._fit = value !== false;\n }\n\n /**\n * Gets if, when CameraFlightAnimation is flying to a boundary, it will always adjust the distance between the\n * {@link Camera#eye} and {@link Camera#look} so as to ensure that the target boundary is always filling the view volume.\n *\n * When false, the eye will remain at its current distance from the look position.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} value Set ````true```` to activate this behaviour.\n */\n get fit() {\n return this._fit;\n }\n\n /**\n * Sets how much of the perspective field-of-view, in degrees, that a target {@link Entity#aabb} should\n * fill the canvas when calling {@link CameraFlightAnimation#flyTo} or {@link CameraFlightAnimation#jumpTo}.\n *\n * Default value is ````45````.\n *\n * @param {Number} value New FOV value.\n */\n set fitFOV(value) {\n this._fitFOV = value || 45;\n }\n\n /**\n * Gets how much of the perspective field-of-view, in degrees, that a target {@link Entity#aabb} should\n * fill the canvas when calling {@link CameraFlightAnimation#flyTo} or {@link CameraFlightAnimation#jumpTo}.\n *\n * Default value is ````45````.\n *\n * @returns {Number} Current FOV value.\n */\n get fitFOV() {\n return this._fitFOV;\n }\n\n /**\n * Sets if this CameraFlightAnimation to point the {@link Camera}\n * in the direction that it is travelling when flying to a target after calling {@link CameraFlightAnimation#flyTo}.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} value Set ````true```` to activate trailing behaviour.\n */\n set trail(value) {\n this._trail = !!value;\n }\n\n /**\n * Gets if this CameraFlightAnimation points the {@link Camera}\n * in the direction that it is travelling when flying to a target after calling {@link CameraFlightAnimation#flyTo}.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} True if trailing behaviour is active.\n */\n get trail() {\n return this._trail;\n }\n\n /**\n * @private\n */\n destroy() {\n this.stop();\n super.destroy();\n }\n}\n\n/**\n * @desc Animates the {@link Scene}'s's {@link Camera} along a {@link CameraPath}.\n *\n * ## Usage\n *\n * In the example below, we'll load a model using a {@link GLTFLoaderPlugin}, then animate a {@link Camera}\n * through the frames in a {@link CameraPath}.\n *\n * * [[Run this example](/examples/index.html#camera_CameraPathAnimation)]\n *\n * ````Javascript\n * import {Viewer, GLTFLoaderPlugin, CameraPath, CameraPathAnimation} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer and arrange camera\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.camera.eye = [124.86756896972656, -93.50288391113281, 173.2632598876953];\n * viewer.camera.look = [102.14186096191406, -90.24193572998047, 173.4224395751953];\n * viewer.camera.up = [0.23516440391540527, 0.9719591736793518, -0.0016466031083837152];\n *\n * // Load model\n *\n * const gltfLoader = new GLTFLoaderPlugin(viewer);\n *\n * const model = gltfLoader.load({\n * id: \"myModel\",\n * src: \"./models/gltf/modern_office/scene.gltf\",\n * edges: true,\n * edgeThreshold: 20,\n * xrayed: false\n * });\n *\n * // Create a CameraPath\n *\n * var cameraPath = new CameraPath(viewer.scene, {\n * frames: [\n * {\n * t: 0,\n * eye: [124.86, -93.50, 173.26],\n * look: [102.14, -90.24, 173.42],\n * up: [0.23, 0.97, -0.00]\n * },\n * {\n * t: 1,\n * eye: [79.75, -85.98, 226.57],\n * look: [99.24, -84.11, 238.56],\n * up: [-0.14, 0.98, -0.09]\n * },\n * // Rest of the frames omitted for brevity\n * ]\n * });\n *\n * // Create a CameraPathAnimation to play our CameraPath\n *\n * var cameraPathAnimation = new CameraPathAnimation(viewer.scene, {\n * cameraPath: cameraPath,\n * playingRate: 0.2 // Playing 0.2 time units per second\n * });\n *\n * // Once model loaded, start playing after a couple of seconds delay\n *\n * model.on(\"loaded\", function () {\n * setTimeout(function () {\n * cameraPathAnimation.play(0); // Play from the beginning of the CameraPath\n * }, 2000);\n * });\n * ````\n */\nclass CameraPathAnimation extends Component {\n\n /**\n * Returns \"CameraPathAnimation\".\n *\n * @private\n * @returns {string} \"CameraPathAnimation\"\n */\n get type() {\n return \"CameraPathAnimation\"\n }\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this CameraPathAnimation as well.\n * @param {*} [cfg] Configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {CameraPath} [cfg.eyeCurve] A {@link CameraPath} that defines the path of a {@link Camera}.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._cameraFlightAnimation = new CameraFlightAnimation(this);\n this._t = 0;\n this.state = CameraPathAnimation.SCRUBBING;\n this._playingFromT = 0;\n this._playingToT = 0;\n this._playingRate = cfg.playingRate || 1.0;\n this._playingDir = 1.0;\n this._lastTime = null;\n\n this.cameraPath = cfg.cameraPath;\n\n this._tick = this.scene.on(\"tick\", this._updateT, this);\n }\n\n _updateT() {\n const cameraPath = this._cameraPath;\n if (!cameraPath) {\n return;\n }\n let numFrames;\n let t;\n const time = performance.now();\n const elapsedSecs = (this._lastTime) ? (time - this._lastTime) * 0.001 : 0;\n this._lastTime = time;\n if (elapsedSecs === 0) {\n return;\n }\n switch (this.state) {\n case CameraPathAnimation.SCRUBBING:\n return;\n case CameraPathAnimation.PLAYING:\n this._t += this._playingRate * elapsedSecs;\n numFrames = this._cameraPath.frames.length;\n if (numFrames === 0 || (this._playingDir < 0 && this._t <= 0) || (this._playingDir > 0 && this._t >= this._cameraPath.frames[numFrames - 1].t)) {\n this.state = CameraPathAnimation.SCRUBBING;\n this._t = this._cameraPath.frames[numFrames - 1].t;\n this.fire(\"stopped\");\n return;\n }\n cameraPath.loadFrame(this._t);\n break;\n case CameraPathAnimation.PLAYING_TO:\n t = this._t + (this._playingRate * elapsedSecs * this._playingDir);\n if ((this._playingDir < 0 && t <= this._playingToT) || (this._playingDir > 0 && t >= this._playingToT)) {\n t = this._playingToT;\n this.state = CameraPathAnimation.SCRUBBING;\n this.fire(\"stopped\");\n }\n this._t = t;\n cameraPath.loadFrame(this._t);\n break;\n }\n }\n\n /*\n * @private\n */\n _ease(t, b, c, d) {\n t /= d;\n return -c * t * (t - 2) + b;\n }\n\n /**\n * Sets the {@link CameraPath} animated by this CameraPathAnimation.\n *\n @param {CameraPath} value The new CameraPath.\n */\n set cameraPath(value) {\n this._cameraPath = value;\n }\n\n /**\n * Gets the {@link CameraPath} animated by this CameraPathAnimation.\n *\n @returns {CameraPath} The CameraPath.\n */\n get cameraPath() {\n return this._cameraPath;\n }\n\n /**\n * Sets the rate at which the CameraPathAnimation animates the {@link Camera} along the {@link CameraPath}.\n *\n * @param {Number} value The amount of progress per second.\n */\n set rate(value) {\n this._playingRate = value;\n }\n\n /**\n * Gets the rate at which the CameraPathAnimation animates the {@link Camera} along the {@link CameraPath}.\n *\n * @returns {*|number} The current playing rate.\n */\n get rate() {\n return this._playingRate;\n }\n\n /**\n * Begins animating the {@link Camera} along CameraPathAnimation's {@link CameraPath} from the beginning.\n */\n play() {\n if (!this._cameraPath) {\n return;\n }\n this._lastTime = null;\n this.state = CameraPathAnimation.PLAYING;\n }\n\n /**\n * Begins animating the {@link Camera} along CameraPathAnimation's {@link CameraPath} from the given time.\n *\n * @param {Number} t Time instant.\n */\n playToT(t) {\n const cameraPath = this._cameraPath;\n if (!cameraPath) {\n return;\n }\n this._playingFromT = this._t;\n this._playingToT = t;\n this._playingDir = (this._playingToT - this._playingFromT) < 0 ? -1 : 1;\n this._lastTime = null;\n this.state = CameraPathAnimation.PLAYING_TO;\n }\n\n /**\n * Animates the {@link Camera} along CameraPathAnimation's {@link CameraPath} to the given frame.\n *\n * @param {Number} frameIdx Index of the frame to play to.\n */\n playToFrame(frameIdx) {\n const cameraPath = this._cameraPath;\n if (!cameraPath) {\n return;\n }\n const frame = cameraPath.frames[frameIdx];\n if (!frame) {\n this.error(\"playToFrame - frame index out of range: \" + frameIdx);\n return;\n }\n this.playToT(frame.t);\n }\n\n /**\n * Flies the {@link Camera} directly to the given frame on the CameraPathAnimation's {@link CameraPath}.\n *\n * @param {Number} frameIdx Index of the frame to play to.\n * @param {Function} [ok] Callback to fire when playing is complete.\n */\n flyToFrame(frameIdx, ok) {\n const cameraPath = this._cameraPath;\n if (!cameraPath) {\n return;\n }\n const frame = cameraPath.frames[frameIdx];\n if (!frame) {\n this.error(\"flyToFrame - frame index out of range: \" + frameIdx);\n return;\n }\n this.state = CameraPathAnimation.SCRUBBING;\n this._cameraFlightAnimation.flyTo(frame, ok);\n }\n\n /**\n * Scrubs the {@link Camera} to the given time on the CameraPathAnimation's {@link CameraPath}.\n *\n * @param {Number} t Time instant.\n */\n scrubToT(t) {\n const cameraPath = this._cameraPath;\n if (!cameraPath) {\n return;\n }\n const camera = this.scene.camera;\n if (!camera) {\n return;\n }\n this._t = t;\n cameraPath.loadFrame(this._t);\n this.state = CameraPathAnimation.SCRUBBING;\n }\n\n /**\n * Scrubs the {@link Camera} to the given frame on the CameraPathAnimation's {@link CameraPath}.\n *\n * @param {Number} frameIdx Index of the frame to scrub to.\n */\n scrubToFrame(frameIdx) {\n const cameraPath = this._cameraPath;\n if (!cameraPath) {\n return;\n }\n const camera = this.scene.camera;\n if (!camera) {\n return;\n }\n const frame = cameraPath.frames[frameIdx];\n if (!frame) {\n this.error(\"playToFrame - frame index out of range: \" + frameIdx);\n return;\n }\n cameraPath.loadFrame(this._t);\n this.state = CameraPathAnimation.SCRUBBING;\n }\n\n /**\n * Stops playing this CameraPathAnimation.\n */\n stop() {\n this.state = CameraPathAnimation.SCRUBBING;\n this.fire(\"stopped\");\n }\n\n destroy() {\n super.destroy();\n this.scene.off(this._tick);\n }\n}\n\nCameraPathAnimation.STOPPED = 0;\nCameraPathAnimation.SCRUBBING = 1;\nCameraPathAnimation.PLAYING = 2;\nCameraPathAnimation.PLAYING_TO = 3;\n\nconst tempVec3$3 = math.vec3();\nconst tempVec3b$5 = math.vec3();\nmath.vec3();\nconst zeroVec$2 = math.vec3([0, -1, 0]);\nconst tempQuat = math.vec4([0, 0, 0, 1]);\n\n/**\n * @desc A plane-shaped 3D object containing a bitmap image.\n *\n * Use ````ImagePlane```` to embed bitmap images in your scenes.\n *\n * As shown in the examples below, ````ImagePlane```` is useful for creating ground planes from satellite maps and embedding 2D plan\n * view images in cross-section slicing planes.\n *\n * # Example 1: Create a ground plane from a satellite image\n *\n * In our first example, we'll load the Schependomlaan model, then use\n * an ````ImagePlane```` to create a ground plane, which will contain\n * a satellite image sourced from Google Maps.\n *\n * \n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_groundPlane)\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_groundPlane)]\n *\n * ````javascript\n * import {Viewer, ImagePlane, XKTLoaderPlugin} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.camera.eye = [-8.31, 42.21, 54.30];\n * viewer.camera.look = [-0.86, 15.40, 14.90];\n * viewer.camera.up = [0.10, 0.83, -0.54];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * xktLoader.load({ // Load IFC model\n * id: \"myModel\",\n * src: \"./models/xkt/Schependomlaan.xkt\",\n * edges: true,\n *\n * rotation: [0, 22, 0], // Rotate, position and scale the model to align it correctly with the ImagePlane\n * position: [-8, 0, 15],\n * scale: [1.1, 1.1, 1.1]\n * });\n *\n * new ImagePlane(viewer.scene, {\n * src: \"./images/schependomlaanSatMap.png\", // Google satellite image; accepted file types are PNG and JPEG\n * visible: true, // Show the ImagePlane\n * gridVisible: true, // Show the grid - grid is only visible when ImagePlane is also visible\n * size: 190, // Size of ImagePlane's longest edge\n * position: [0, -1, 0], // World-space position of ImagePlane's center\n * rotation: [0, 0, 0], // Euler angles for X, Y and Z\n * opacity: 1.0, // Fully opaque\n * collidable: false, // ImagePlane does not contribute to Scene boundary\n * clippable: true, // ImagePlane can be clipped by SectionPlanes\n * pickable: true // Allow the ground plane to be picked\n * });\n * ````\n *
\n *\n * # Example 2: Embed an image in a cross-section plane\n *\n * In our second example, we'll load the Schependomlaan model again, then slice it in half with\n * a {@link SectionPlanesPlugin}, then use an ````ImagePlane```` to embed a 2D plan view image in the slicing plane.\n *\n * \n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_imageInSectionPlane)\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_imageInSectionPlane)]\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, SectionPlanesPlugin, ImagePlane} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.camera.eye = [-9.11, 20.01, 5.13];\n * viewer.camera.look = [9.07, 0.77, -9.78];\n * viewer.camera.up = [0.47, 0.76, -0.38];\n *\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const sectionPlanes = new SectionPlanesPlugin(viewer, {\n * overviewVisible: false\n * });\n *\n * model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/schependomlaan/schependomlaan.xkt\",\n * metaModelSrc: \"./metaModels/schependomlaan/metaModel.json\",\n * edges: true,\n * });\n *\n * const sectionPlane = sectionPlanes.createSectionPlane({\n * id: \"mySectionPlane\",\n * pos: [10.95, 1.95, -10.35],\n * dir: [0.0, -1.0, 0.0]\n * });\n *\n * const imagePlane = new ImagePlane(viewer.scene, {\n * src: \"./images/schependomlaanPlanView.png\", // Plan view image; accepted file types are PNG and JPEG\n * visible: true,\n * gridVisible: true,\n * size: 23.95,\n * position: sectionPlane.pos,\n * dir: sectionPlane.dir,\n * collidable: false,\n * opacity: 0.75,\n * clippable: false, // Don't allow ImagePlane to be clipped by the SectionPlane\n * pickable: false // Don't allow ImagePlane to be picked\n * });\n * ````\n */\nclass ImagePlane extends Component {\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this ````ImagePlane```` as well.\n * @param {*} [cfg] ````ImagePlane```` configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Boolean} [cfg.visible=true] Indicates whether or not this ````ImagePlane```` is visible.\n * @param {Boolean} [cfg.gridVisible=true] Indicates whether or not the grid is visible. Grid is only visible when ````ImagePlane```` is also visible.\n * @param {Number[]} [cfg.position=[0,0,0]] World-space position of the ````ImagePlane````.\n * @param {Number[]} [cfg.size=1] World-space size of the longest edge of the ````ImagePlane````. Note that\n * ````ImagePlane```` sets its aspect ratio to match its image. If we set a value of ````1000````, and the image\n * has size ````400x300````, then the ````ImagePlane```` will then have size ````1000 x 750````.\n * @param {Number[]} [cfg.rotation=[0,0,0]] Local rotation of the ````ImagePlane````, as Euler angles given in degrees, for each of the X, Y and Z axis.\n * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] Modelling transform matrix for the ````ImagePlane````. Overrides the ````position````, ````size```, ````rotation```` and ````dir```` parameters.\n * @param {Boolean} [cfg.collidable=true] Indicates if the ````ImagePlane```` is initially included in boundary calculations.\n * @param {Boolean} [cfg.clippable=true] Indicates if the ````ImagePlane```` is initially clippable.\n * @param {Boolean} [cfg.pickable=true] Indicates if the ````ImagePlane```` is initially pickable.\n * @param {Number} [cfg.opacity=1.0] ````ImagePlane````'s initial opacity factor, multiplies by the rendered fragment alpha.\n * @param {String} [cfg.src] URL of image. Accepted file types are PNG and JPEG.\n * @param {HTMLImageElement} [cfg.image] An ````HTMLImageElement```` to source the image from. Overrides ````src````.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._src = null;\n this._image = null;\n this._pos = math.vec3();\n this._origin = math.vec3();\n this._rtcPos = math.vec3();\n this._dir = math.vec3();\n this._size = 1.0;\n this._imageSize = math.vec2();\n\n this._texture = new Texture(this);\n\n this._plane = new Mesh(this, {\n\n geometry: new ReadableGeometry(this, buildPlaneGeometry({\n center: [0, 0, 0],\n xSize: 1,\n zSize: 1,\n xSegments: 10,\n zSegments: 10\n })),\n\n material: new PhongMaterial(this, {\n diffuse: [0, 0, 0],\n ambient: [0, 0, 0],\n specular: [0, 0, 0],\n diffuseMap: this._texture,\n emissiveMap: this._texture,\n backfaces: true\n }),\n clippable: cfg.clippable\n });\n\n this._grid = new Mesh(this, {\n geometry: new ReadableGeometry(this, buildGridGeometry({\n size: 1,\n divisions: 10\n })),\n material: new PhongMaterial(this, {\n diffuse: [0.0, 0.0, 0.0],\n ambient: [0.0, 0.0, 0.0],\n emissive: [0.2, 0.8, 0.2]\n }),\n position: [0, 0.001, 0.0],\n clippable: cfg.clippable\n });\n\n this._node = new Node$2(this, {\n rotation: [0, 0, 0],\n position: [0, 0, 0],\n scale: [1, 1, 1],\n clippable: false,\n children: [\n this._plane,\n this._grid\n ]\n });\n\n this._gridVisible = false;\n\n this.visible = true;\n this.gridVisible = cfg.gridVisible;\n this.position = cfg.position;\n this.rotation = cfg.rotation;\n this.dir = cfg.dir;\n this.size = cfg.size;\n this.collidable = cfg.collidable;\n this.clippable = cfg.clippable;\n this.pickable = cfg.pickable;\n this.opacity = cfg.opacity;\n\n if (cfg.image) {\n this.image = cfg.image;\n } else {\n this.src = cfg.src;\n }\n }\n\n /**\n * Sets if this ````ImagePlane```` is visible or not.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} visible Set ````true```` to make this ````ImagePlane```` visible.\n */\n set visible(visible) {\n this._plane.visible = visible;\n this._grid.visible = (this._gridVisible && visible);\n }\n\n /**\n * Gets if this ````ImagePlane```` is visible or not.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} Returns ````true```` if visible.\n */\n get visible() {\n return this._plane.visible;\n }\n\n /**\n * Sets if this ````ImagePlane````'s grid is visible or not.\n *\n * Default value is ````false````.\n *\n * Grid is only visible when ````ImagePlane```` is also visible.\n *\n * @param {Boolean} visible Set ````true```` to make this ````ImagePlane````'s grid visible.\n */\n set gridVisible(visible) {\n visible = (visible !== false);\n this._gridVisible = visible;\n this._grid.visible = (this._gridVisible && this.visible);\n }\n\n /**\n * Gets if this ````ImagePlane````'s grid is visible or not.\n *\n * Default value is ````false````.\n *\n * @returns {Boolean} Returns ````true```` if visible.\n */\n get gridVisible() {\n return this._gridVisible;\n }\n\n /**\n * Sets an ````HTMLImageElement```` to source the image from.\n *\n * Sets {@link Texture#src} null.\n *\n * @type {HTMLImageElement}\n */\n set image(image) {\n this._image = image;\n if (this._image) {\n this._imageSize[0] = image.width;\n this._imageSize[1] = image.height;\n this._updatePlaneSizeFromImage();\n this._src = null;\n this._texture.image = this._image;\n }\n }\n\n /**\n * Gets the ````HTMLImageElement```` the ````ImagePlane````'s image is sourced from, if set.\n *\n * Returns null if not set.\n *\n * @type {HTMLImageElement}\n */\n get image() {\n return this._image;\n }\n\n /**\n * Sets an image file path that the ````ImagePlane````'s image is sourced from.\n *\n * Accepted file types are PNG and JPEG.\n *\n * Sets {@link Texture#image} null.\n *\n * @type {String}\n */\n set src(src) {\n this._src = src;\n if (this._src) {\n this._image = null;\n const image = new Image();\n image.onload = () => {\n this._texture.image = image;\n this._imageSize[0] = image.width;\n this._imageSize[1] = image.height;\n this._updatePlaneSizeFromImage();\n };\n image.src = this._src;\n }\n }\n\n /**\n * Gets the image file path that the ````ImagePlane````'s image is sourced from, if set.\n *\n * Returns null if not set.\n *\n * @type {String}\n */\n get src() {\n return this._src;\n }\n\n /**\n * Sets the World-space position of this ````ImagePlane````.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * @param {Number[]} value New position.\n */\n set position(value) {\n this._pos.set(value || [0, 0, 0]);\n worldToRTCPos(this._pos, this._origin, this._rtcPos);\n this._node.origin = this._origin;\n this._node.position = this._rtcPos;\n }\n\n /**\n * Gets the World-space position of this ````ImagePlane````.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * @returns {Number[]} Current position.\n */\n get position() {\n return this._pos;\n }\n\n /**\n * Sets the direction of this ````ImagePlane```` using Euler angles.\n *\n * Default value is ````[0, 0, 0]````.\n *\n * @param {Number[]} value Euler angles for ````X````, ````Y```` and ````Z```` axis rotations.\n */\n set rotation(value) {\n this._node.rotation = value;\n }\n\n /**\n * Gets the direction of this ````ImagePlane```` as Euler angles.\n *\n * @returns {Number[]} Euler angles for ````X````, ````Y```` and ````Z```` axis rotations.\n */\n get rotation() {\n return this._node.rotation;\n }\n\n /**\n * Sets the World-space size of the longest edge of the ````ImagePlane````.\n *\n * Note that ````ImagePlane```` sets its aspect ratio to match its image. If we set a value of ````1000````, and\n * the image has size ````400x300````, then the ````ImagePlane```` will then have size ````1000 x 750````.\n *\n * Default value is ````1.0````.\n *\n * @param {Number} size New World-space size of the ````ImagePlane````.\n */\n set size(size) {\n this._size = (size === undefined || size === null) ? 1.0 : size;\n if (this._image) {\n this._updatePlaneSizeFromImage();\n }\n }\n\n /**\n * Gets the World-space size of the longest edge of the ````ImagePlane````.\n *\n * Returns {Number} World-space size of the ````ImagePlane````.\n */\n get size() {\n return this._size;\n }\n\n /**\n * Sets the direction of this ````ImagePlane```` as a direction vector.\n *\n * Default value is ````[0, 0, -1]````.\n *\n * @param {Number[]} dir New direction vector.\n */\n set dir(dir) {\n\n this._dir.set(dir || [0, 0, -1]);\n\n if (dir) {\n\n const origin = this.scene.center;\n const negDir = [-this._dir[0], -this._dir[1], -this._dir[2]];\n\n math.subVec3(origin, this.position, tempVec3$3);\n\n const dist = -math.dotVec3(negDir, tempVec3$3);\n\n math.normalizeVec3(negDir);\n math.mulVec3Scalar(negDir, dist, tempVec3b$5);\n math.vec3PairToQuaternion(zeroVec$2, dir, tempQuat);\n\n this._node.quaternion = tempQuat;\n }\n }\n\n /**\n * Gets the direction of this ````ImagePlane```` as a direction vector.\n *\n * @returns {Number[]} value Current direction.\n */\n get dir() {\n return this._dir;\n }\n\n /**\n * Sets if this ````ImagePlane```` is included in boundary calculations.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set collidable(value) {\n this._node.collidable = (value !== false);\n }\n\n /**\n * Gets if this ````ImagePlane```` is included in boundary calculations.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get collidable() {\n return this._node.collidable;\n }\n\n /**\n * Sets if this ````ImagePlane```` is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set clippable(value) {\n this._node.clippable = (value !== false);\n }\n\n /**\n * Gets if this ````ImagePlane```` is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get clippable() {\n return this._node.clippable;\n }\n\n /**\n * Sets if this ````ImagePlane```` is pickable.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set pickable(value) {\n this._node.pickable = (value !== false);\n }\n\n /**\n * Gets if this ````ImagePlane```` is pickable.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get pickable() {\n return this._node.pickable;\n }\n\n /**\n * Sets the opacity factor for this ````ImagePlane````.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n set opacity(opacity) {\n this._node.opacity = opacity;\n }\n\n /**\n * Gets this ````ImagePlane````'s opacity factor.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n get opacity() {\n return this._node.opacity;\n }\n\n /**\n * @destroy\n */\n destroy() {\n super.destroy();\n }\n\n _updatePlaneSizeFromImage() {\n const size = this._size;\n const width = this._imageSize[0];\n const height = this._imageSize[1];\n if (width > height) {\n const aspect = height / width;\n this._node.scale = [size, 1.0, size * aspect];\n } else {\n const aspect = width / height;\n this._node.scale = [size * aspect, 1.0, size];\n }\n }\n}\n\n/**\n * A positional light source that originates from a single point and spreads outward in all directions, with optional attenuation over distance.\n *\n * * Has a position in {@link PointLight#pos}, but no direction.\n * * Defined in either *World* or *View* coordinate space. When in World-space, {@link PointLight#pos} is relative to\n * the World coordinate system, and will appear to move as the {@link Camera} moves. When in View-space,\n * {@link PointLight#pos} is relative to the View coordinate system, and will behave as if fixed to the viewer's head.\n * * Has {@link PointLight#constantAttenuation}, {@link PointLight#linearAttenuation} and {@link PointLight#quadraticAttenuation}\n * factors, which indicate how intensity attenuates over distance.\n * * {@link AmbientLight}s, {@link PointLight}s and {@link PointLight}s are registered by their {@link Component#id} on {@link Scene#lights}.\n *\n * ## Usage\n *\n * In the example below we'll replace the {@link Scene}'s default light sources with three World-space PointLights.\n *\n * [[Run this example](/examples/index.html#lights_PointLight_world)]\n *\n * ````javascript\n * import {Viewer, Mesh, buildSphereGeometry, buildPlaneGeometry,\n * ReadableGeometry, PhongMaterial, Texture, PointLight} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer and arrange the camera\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * // Replace the Scene's default lights with three custom world-space PointLights\n *\n * viewer.scene.clearLights();\n *\n * new PointLight(viewer.scene,{\n * id: \"keyLight\",\n * pos: [-80, 60, 80],\n * color: [1.0, 0.3, 0.3],\n * intensity: 1.0,\n * space: \"world\"\n * });\n *\n * new PointLight(viewer.scene,{\n * id: \"fillLight\",\n * pos: [80, 40, 40],\n * color: [0.3, 1.0, 0.3],\n * intensity: 1.0,\n * space: \"world\"\n * });\n *\n * new PointLight(viewer.scene,{\n * id: \"rimLight\",\n * pos: [-20, 80, -80],\n * color: [0.6, 0.6, 0.6],\n * intensity: 1.0,\n * space: \"world\"\n * });\n *\n * // Create a sphere and ground plane\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * radius: 1.3\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuse: [0.7, 0.7, 0.7],\n * specular: [1.0, 1.0, 1.0],\n * emissive: [0, 0, 0],\n * alpha: 1.0,\n * ambient: [1, 1, 0],\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * })\n * })\n * });\n *\n * new Mesh(viewer.scene, {\n * geometry: buildPlaneGeometry(ReadableGeometry, viewer.scene, {\n * xSize: 30,\n * zSize: 30\n * }),\n * material: new PhongMaterial(viewer.scene, {\n * diffuseMap: new Texture(viewer.scene, {\n * src: \"textures/diffuse/uvGrid2.jpg\"\n * }),\n * backfaces: true\n * }),\n * position: [0, -2.1, 0]\n * });\n * ````\n */\nclass PointLight extends Light {\n\n /**\n @private\n */\n get type() {\n return \"PointLight\";\n }\n\n /**\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this PointLight as well.\n * @param {*} [cfg] The PointLight configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.pos=[ 1.0, 1.0, 1.0 ]] Position, in either World or View space, depending on the value of the **space** parameter.\n * @param {Number[]} [cfg.color=[0.7, 0.7, 0.8 ]] Color of this PointLight.\n * @param {Number} [cfg.intensity=1.0] Intensity of this PointLight, as a factor in range ````[0..1]````.\n * @param {Number} [cfg.constantAttenuation=0] Constant attenuation factor.\n * @param {Number} [cfg.linearAttenuation=0] Linear attenuation factor.\n * @param {Number} [cfg.quadraticAttenuation=0]Quadratic attenuation factor.\n * @param {String} [cfg.space=\"view\"]The coordinate system this PointLight is defined in - \"view\" or \"world\".\n * @param {Boolean} [cfg.castsShadow=false] Flag which indicates if this PointLight casts a castsShadow.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n const self = this;\n\n this._shadowRenderBuf = null;\n this._shadowViewMatrix = null;\n this._shadowProjMatrix = null;\n this._shadowViewMatrixDirty = true;\n this._shadowProjMatrixDirty = true;\n\n const camera = this.scene.camera;\n const canvas = this.scene.canvas;\n\n this._onCameraViewMatrix = camera.on(\"viewMatrix\", () => {\n this._shadowViewMatrixDirty = true;\n });\n\n this._onCameraProjMatrix = camera.on(\"projMatrix\", () => {\n this._shadowProjMatrixDirty = true;\n });\n\n this._onCanvasBoundary = canvas.on(\"boundary\", () => {\n this._shadowProjMatrixDirty = true;\n });\n\n this._state = new RenderState({\n\n type: \"point\",\n pos: math.vec3([1.0, 1.0, 1.0]),\n color: math.vec3([0.7, 0.7, 0.8]),\n intensity: 1.0, attenuation: [0.0, 0.0, 0.0],\n space: cfg.space || \"view\",\n castsShadow: false,\n\n getShadowViewMatrix: () => {\n if (self._shadowViewMatrixDirty) {\n if (!self._shadowViewMatrix) {\n self._shadowViewMatrix = math.identityMat4();\n }\n const eye = self._state.pos;\n const look = camera.look;\n const up = camera.up;\n math.lookAtMat4v(eye, look, up, self._shadowViewMatrix);\n self._shadowViewMatrixDirty = false;\n }\n return self._shadowViewMatrix;\n },\n\n getShadowProjMatrix: () => {\n if (self._shadowProjMatrixDirty) { // TODO: Set when canvas resizes\n if (!self._shadowProjMatrix) {\n self._shadowProjMatrix = math.identityMat4();\n }\n const canvas = self.scene.canvas.canvas;\n math.perspectiveMat4(70 * (Math.PI / 180.0), canvas.clientWidth / canvas.clientHeight, 0.1, 500.0, self._shadowProjMatrix);\n self._shadowProjMatrixDirty = false;\n }\n return self._shadowProjMatrix;\n },\n\n getShadowRenderBuf: () => {\n if (!self._shadowRenderBuf) {\n self._shadowRenderBuf = new RenderBuffer(self.scene.canvas.canvas, self.scene.canvas.gl, {size: [1024, 1024]}); // Super old mobile devices have a limit of 1024x1024 textures\n }\n return self._shadowRenderBuf;\n }\n });\n\n this.pos = cfg.pos;\n this.color = cfg.color;\n this.intensity = cfg.intensity;\n this.constantAttenuation = cfg.constantAttenuation;\n this.linearAttenuation = cfg.linearAttenuation;\n this.quadraticAttenuation = cfg.quadraticAttenuation;\n this.castsShadow = cfg.castsShadow;\n\n this.scene._lightCreated(this);\n }\n\n /**\n * Sets the position of this PointLight.\n *\n * This will be either World- or View-space, depending on the value of {@link PointLight#space}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @param {Number[]} pos The position.\n */\n set pos(pos) {\n this._state.pos.set(pos || [1.0, 1.0, 1.0]);\n this._shadowViewMatrixDirty = true;\n this.glRedraw();\n }\n\n /**\n * Gets the position of this PointLight.\n *\n * This will be either World- or View-space, depending on the value of {@link PointLight#space}.\n *\n * Default value is ````[1.0, 1.0, 1.0]````.\n *\n * @returns {Number[]} The position.\n */\n get pos() {\n return this._state.pos;\n }\n\n /**\n * Sets the RGB color of this PointLight.\n *\n * Default value is ````[0.7, 0.7, 0.8]````.\n *\n * @param {Number[]} color The PointLight's RGB color.\n */\n set color(color) {\n this._state.color.set(color || [0.7, 0.7, 0.8]);\n this.glRedraw();\n }\n\n /**\n * Gets the RGB color of this PointLight.\n *\n * Default value is ````[0.7, 0.7, 0.8]````.\n *\n * @returns {Number[]} The PointLight's RGB color.\n */\n get color() {\n return this._state.color;\n }\n\n /**\n * Sets the intensity of this PointLight.\n *\n * Default intensity is ````1.0```` for maximum intensity.\n *\n * @param {Number} intensity The PointLight's intensity\n */\n set intensity(intensity) {\n intensity = intensity !== undefined ? intensity : 1.0;\n this._state.intensity = intensity;\n this.glRedraw();\n }\n\n /**\n * Gets the intensity of this PointLight.\n *\n * Default value is ````1.0```` for maximum intensity.\n *\n * @returns {Number} The PointLight's intensity.\n */\n get intensity() {\n return this._state.intensity;\n }\n\n /**\n * Sets the constant attenuation factor for this PointLight.\n *\n * Default value is ````0````.\n *\n * @param {Number} value The constant attenuation factor.\n */\n set constantAttenuation(value) {\n this._state.attenuation[0] = value || 0.0;\n this.glRedraw();\n }\n\n /**\n * Gets the constant attenuation factor for this PointLight.\n *\n * Default value is ````0````.\n *\n * @returns {Number} The constant attenuation factor.\n */\n get constantAttenuation() {\n return this._state.attenuation[0];\n }\n\n /**\n * Sets the linear attenuation factor for this PointLight.\n *\n * Default value is ````0````.\n *\n * @param {Number} value The linear attenuation factor.\n */\n set linearAttenuation(value) {\n this._state.attenuation[1] = value || 0.0;\n this.glRedraw();\n }\n\n /**\n * Gets the linear attenuation factor for this PointLight.\n *\n * Default value is ````0````.\n *\n * @returns {Number} The linear attenuation factor.\n */\n get linearAttenuation() {\n return this._state.attenuation[1];\n }\n\n /**\n * Sets the quadratic attenuation factor for this PointLight.\n *\n * Default value is ````0````.\n *\n * @param {Number} value The quadratic attenuation factor.\n */\n set quadraticAttenuation(value) {\n this._state.attenuation[2] = value || 0.0;\n this.glRedraw();\n }\n\n /**\n * Gets the quadratic attenuation factor for this PointLight.\n *\n * Default value is ````0````.\n *\n * @returns {Number} The quadratic attenuation factor.\n */\n get quadraticAttenuation() {\n return this._state.attenuation[2];\n }\n\n /**\n * Sets if this PointLight casts a shadow.\n *\n * Default value is ````false````.\n *\n * @param {Boolean} castsShadow Set ````true```` to cast shadows.\n */\n set castsShadow(castsShadow) {\n castsShadow = !!castsShadow;\n if (this._state.castsShadow === castsShadow) {\n return;\n }\n this._state.castsShadow = castsShadow;\n this._shadowViewMatrixDirty = true;\n this.glRedraw();\n }\n\n /**\n * Gets if this PointLight casts a shadow.\n *\n * Default value is ````false````.\n *\n * @returns {Boolean} ````true```` if this PointLight casts shadows.\n */\n get castsShadow() {\n return this._state.castsShadow;\n }\n\n /**\n * Destroys this PointLight.\n */\n destroy() {\n\n const camera = this.scene.camera;\n const canvas = this.scene.canvas;\n camera.off(this._onCameraViewMatrix);\n camera.off(this._onCameraProjMatrix);\n canvas.off(this._onCanvasBoundary);\n\n super.destroy();\n\n this._state.destroy();\n if (this._shadowRenderBuf) {\n this._shadowRenderBuf.destroy();\n }\n this.scene._lightDestroyed(this);\n this.glRedraw();\n }\n}\n\nfunction ensureImageSizePowerOfTwo(image) {\n if (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height)) {\n const canvas = document.createElement(\"canvas\");\n canvas.width = nextHighestPowerOfTwo(image.width);\n canvas.height = nextHighestPowerOfTwo(image.height);\n const ctx = canvas.getContext(\"2d\");\n ctx.drawImage(image,\n 0, 0, image.width, image.height,\n 0, 0, canvas.width, canvas.height);\n image = canvas;\n }\n return image;\n}\n\nfunction isPowerOfTwo(x) {\n return (x & (x - 1)) === 0;\n}\n\nfunction nextHighestPowerOfTwo(x) {\n --x;\n for (let i = 1; i < 32; i <<= 1) {\n x = x | x >> i;\n }\n return x + 1;\n}\n\n/**\n * @desc A cube texture map.\n */\nclass CubeTexture extends Component {\n\n /**\n @private\n */\n get type() {\n return \"CubeTexture\";\n }\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for this CubeTexture, unique among all components in the parent scene, generated automatically when omitted.\n * @param {String[]} [cfg.src=null] Paths to six image files to load into this CubeTexture.\n * @param {Boolean} [cfg.flipY=false] Flips this CubeTexture's source data along its vertical axis when true.\n * @param {Number} [cfg.encoding=LinearEncoding] Encoding format. Supported values are {@link LinearEncoding} and {@link sRGBEncoding}.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n const gl = this.scene.canvas.gl;\n\n this._state = new RenderState({\n texture: new Texture2D({gl, target: gl.TEXTURE_CUBE_MAP}),\n flipY: this._checkFlipY(cfg.minFilter),\n encoding: this._checkEncoding(cfg.encoding),\n minFilter: LinearMipmapLinearFilter,\n magFilter: LinearFilter,\n wrapS: ClampToEdgeWrapping,\n wrapT: ClampToEdgeWrapping,\n mipmaps: true\n });\n\n this._src = cfg.src;\n this._images = [];\n\n this._loadSrc(cfg.src);\n\n stats.memory.textures++;\n }\n\n _checkFlipY(value) {\n return !!value;\n }\n\n _checkEncoding(value) {\n value = value || LinearEncoding;\n if (value !== LinearEncoding && value !== sRGBEncoding) {\n this.error(\"Unsupported value for 'encoding' - supported values are LinearEncoding and sRGBEncoding. Defaulting to LinearEncoding.\");\n value = LinearEncoding;\n }\n return value;\n }\n\n _webglContextRestored() {\n this.scene.canvas.gl;\n this._state.texture = null;\n // if (this._images.length > 0) {\n // this._state.texture = new xeokit.renderer.Texture2D(gl, gl.TEXTURE_CUBE_MAP);\n // this._state.texture.setImage(this._images, this._state);\n // this._state.texture.setProps(this._state);\n // } else\n if (this._src) {\n this._loadSrc(this._src);\n }\n }\n\n _loadSrc(src) {\n const self = this;\n const gl = this.scene.canvas.gl;\n this._images = [];\n let loadFailed = false;\n let numLoaded = 0;\n for (let i = 0; i < src.length; i++) {\n const image = new Image();\n image.onload = (function () {\n let _image = image;\n const index = i;\n return function () {\n if (loadFailed) {\n return;\n }\n _image = ensureImageSizePowerOfTwo(_image);\n self._images[index] = _image;\n numLoaded++;\n if (numLoaded === 6) {\n let texture = self._state.texture;\n if (!texture) {\n texture = new Texture2D({gl, target: gl.TEXTURE_CUBE_MAP});\n self._state.texture = texture;\n }\n texture.setImage(self._images, self._state);\n self.fire(\"loaded\", self._src, false);\n self.glRedraw();\n }\n };\n })();\n image.onerror = function () {\n loadFailed = true;\n };\n image.src = src[i];\n }\n }\n\n /**\n * Destroys this CubeTexture\n *\n */\n destroy() {\n super.destroy();\n if (this._state.texture) {\n this._state.texture.destroy();\n }\n stats.memory.textures--;\n this._state.destroy();\n }\n}\n\n/**\n * @desc A reflection cube map.\n *\n * ## Usage\n *\n * ````javascript\n * import {Viewer, Mesh, buildSphereGeometry,\n * ReadableGeometry, MetallicMaterial, ReflectionMap} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer and arrange the camera\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new ReflectionMap(viewer.scene, {\n * src: [\n * \"textures/reflect/Uffizi_Gallery/Uffizi_Gallery_Radiance_PX.png\",\n * \"textures/reflect/Uffizi_Gallery/Uffizi_Gallery_Radiance_NX.png\",\n * \"textures/reflect/Uffizi_Gallery/Uffizi_Gallery_Radiance_PY.png\",\n * \"textures/reflect/Uffizi_Gallery/Uffizi_Gallery_Radiance_NY.png\",\n * \"textures/reflect/Uffizi_Gallery/Uffizi_Gallery_Radiance_PZ.png\",\n * \"textures/reflect/Uffizi_Gallery/Uffizi_Gallery_Radiance_NZ.png\"\n * ]\n * });\n *\n * // Create a sphere and ground plane\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * radius: 2.0\n * }),\n * new MetallicMaterial(viewer.scene, {\n * baseColor: [1, 1, 1],\n * metallic: 1.0,\n * roughness: 1.0\n * })\n * });\n * ````\n */\nclass ReflectionMap extends CubeTexture {\n\n /**\n @private\n */\n get type() {\n return \"ReflectionMap\";\n }\n\n /**\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for this ReflectionMap, unique among all components in the parent scene, generated automatically when omitted.\n * @param {String[]} [cfg.src=null] Paths to six image files to load into this ReflectionMap.\n * @param {Boolean} [cfg.flipY=false] Flips this ReflectionMap's source data along its vertical axis when true.\n * @param {Number} [cfg.encoding=LinearEncoding] Encoding format. Supported values are {@link LinearEncoding} and {@link sRGBEncoding}.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this.scene._lightsState.addReflectionMap(this._state);\n this.scene._reflectionMapCreated(this);\n }\n\n /**\n * Destroys this ReflectionMap.\n */\n destroy() {\n super.destroy();\n this.scene._reflectionMapDestroyed(this);\n }\n}\n\n/**\n * @desc A **LightMap** specifies a cube texture light map.\n *\n * ## Usage\n *\n * ````javascript\n * import {Viewer, Mesh, buildSphereGeometry,\n * ReadableGeometry, MetallicMaterial, LightMap} from \"xeokit-sdk.es.js\";\n *\n * // Create a Viewer and arrange the camera\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 5];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new LightMap(viewer.scene, {\n * src: [\n * \"textures/light/Uffizi_Gallery/Uffizi_Gallery_Irradiance_PX.png\",\n * \"textures/light/Uffizi_Gallery/Uffizi_Gallery_Irradiance_NX.png\",\n * \"textures/light/Uffizi_Gallery/Uffizi_Gallery_Irradiance_PY.png\",\n * \"textures/light/Uffizi_Gallery/Uffizi_Gallery_Irradiance_NY.png\",\n * \"textures/light/Uffizi_Gallery/Uffizi_Gallery_Irradiance_PZ.png\",\n * \"textures/light/Uffizi_Gallery/Uffizi_Gallery_Irradiance_NZ.png\"\n * ]\n * });\n *\n * // Create a sphere and ground plane\n *\n * new Mesh(viewer.scene, {\n * geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({\n * radius: 2.0\n * }),\n * new MetallicMaterial(viewer.scene, {\n * baseColor: [1, 1, 1],\n * metallic: 1.0,\n * roughness: 1.0\n * })\n * });\n * ````\n */\nclass LightMap extends CubeTexture {\n\n /**\n @private\n */\n get type() {\n return \"LightMap\";\n }\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for this LightMap, unique among all components in the parent scene, generated automatically when omitted.\n * @param {String:Object} [cfg.meta] Optional map of user-defined metadata to attach to this LightMap.\n * @param {String[]} [cfg.src=null] Paths to six image files to load into this LightMap.\n * @param {Boolean} [cfg.flipY=false] Flips this LightMap's source data along its vertical axis when true.\n * @param {Number} [cfg.encoding=LinearEncoding] Encoding format. Supported values are {@link LinearEncoding} and {@link sRGBEncoding}.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this.scene._lightMapCreated(this);\n }\n\n destroy() {\n super.destroy();\n this.scene._lightMapDestroyed(this);\n }\n}\n\n/**\n * A {@link Marker} with a billboarded and textured quad attached to it.\n *\n * * Extends {@link Marker}\n * * Keeps the quad oriented towards the viewpoint\n * * Auto-fits the quad to the texture\n * * Has a world-space position\n * * Can be configured to hide the quad whenever the position is occluded by some other object\n *\n * ## Usage\n *\n * [[Run this example](/examples/index.html#markers_SpriteMarker)]\n *\n * ```` javascript\n * import {Viewer, SpriteMarker } from \"./https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/xeokit-sdk.es.min.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\",\n * transparent: true\n * });\n *\n * viewer.scene.camera.eye = [0, 0, 25];\n * viewer.scene.camera.look = [0, 0, 0];\n * viewer.scene.camera.up = [0, 1, 0];\n *\n * new SpriteMarker(viewer.scene, {\n * worldPos: [-10, 0, 0],\n * src: \"../assets/textures/diffuse/uvGrid2_512x1024.jpg\",\n * size: 5,\n * occludable: false\n * });\n *\n * new SpriteMarker(viewer.scene, {\n * worldPos: [+10, 0, 0],\n * src: \"../assets/textures/diffuse/uvGrid2_1024x512.jpg\",\n * size: 4,\n * occludable: false\n * });\n *````\n */\nclass SpriteMarker extends Marker {\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this SpriteMarker as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID for this SpriteMarker, unique among all components in the parent scene, generated automatically when omitted.\n * @param {Entity} [cfg.entity] Entity to associate this Marker with. When the SpriteMarker has an Entity, then {@link Marker#visible} will always be ````false```` if {@link Entity#visible} is false.\n * @param {Boolean} [cfg.occludable=false] Indicates whether or not this Marker is hidden (ie. {@link Marker#visible} is ````false```` whenever occluded by {@link Entity}s in the {@link Scene}.\n * @param {Number[]} [cfg.worldPos=[0,0,0]] World-space 3D Marker position.\n * @param {String} [cfg.src=null] Path to image file to load into this SpriteMarker. See the {@link SpriteMarker#src} property for more info.\n * @param {HTMLImageElement} [cfg.image=null] HTML Image object to load into this SpriteMarker. See the {@link SpriteMarker#image} property for more info.\n * @param {Boolean} [cfg.flipY=false] Flips this SpriteMarker's texture image along its vertical axis when true.\n * @param {String} [cfg.encoding=\"linear\"] Texture encoding format. See the {@link Texture#encoding} property for more info.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, {\n entity: cfg.entity,\n occludable: cfg.occludable,\n worldPos: cfg.worldPos\n });\n\n this._occluded = false;\n this._visible = true;\n this._src = null;\n this._image = null;\n this._pos = math.vec3();\n this._origin = math.vec3();\n this._rtcPos = math.vec3();\n this._dir = math.vec3();\n this._size = 1.0;\n this._imageSize = math.vec2();\n\n this._texture = new Texture(this, {\n src: cfg.src\n });\n\n this._geometry = new ReadableGeometry(this, {\n primitive: \"triangles\",\n positions: [3, 3, 0, -3, 3, 0, -3, -3, 0, 3, -3, 0],\n normals: [-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0],\n uv: [1, -1, 0, -1, 0, 0, 1, 0],\n indices: [0, 1, 2, 0, 2, 3] // Ensure these will be front-faces\n });\n\n this._mesh = new Mesh(this, {\n geometry: this._geometry,\n material: new PhongMaterial(this, {\n ambient: [0.9, 0.3, 0.9],\n shininess: 30,\n diffuseMap: this._texture,\n backfaces: true\n }),\n scale: [1, 1, 1], // Note: by design, scale does not work with billboard\n position: cfg.worldPos,\n rotation: [90, 0, 0],\n billboard: \"spherical\",\n occluder: false // Don't occlude SpriteMarkers or Annotations\n });\n\n this.visible = true;\n this.collidable = cfg.collidable;\n this.clippable = cfg.clippable;\n this.pickable = cfg.pickable;\n this.opacity = cfg.opacity;\n this.size = cfg.size;\n\n if (cfg.image) {\n this.image = cfg.image;\n } else {\n this.src = cfg.src;\n }\n }\n\n _setVisible(visible) { // Called by VisibilityTester and this._entity.on(\"destroyed\"..)\n this._occluded = (!visible);\n this._mesh.visible = this._visible && (!this._occluded);\n super._setVisible(visible);\n }\n\n /**\n * Sets if this ````SpriteMarker```` is visible or not.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} visible Set ````true```` to make this ````SpriteMarker```` visible.\n */\n set visible(visible) {\n this._visible = (visible === null || visible === undefined) ? true : visible;\n this._mesh.visible = this._visible && (!this._occluded);\n }\n\n /**\n * Gets if this ````SpriteMarker```` is visible or not.\n *\n * Default value is ````true````.\n *\n * @returns {Boolean} Returns ````true```` if visible.\n */\n get visible() {\n return this._visible;\n }\n\n /**\n * Sets an ````HTMLImageElement```` to source the image from.\n *\n * Sets {@link Texture#src} null.\n *\n * @type {HTMLImageElement}\n */\n set image(image) {\n this._image = image;\n if (this._image) {\n this._imageSize[0] = this._image.width;\n this._imageSize[1] = this._image.height;\n this._updatePlaneSizeFromImage();\n this._src = null;\n this._texture.image = this._image;\n }\n }\n\n /**\n * Gets the ````HTMLImageElement```` the ````SpriteMarker````'s image is sourced from, if set.\n *\n * Returns null if not set.\n *\n * @type {HTMLImageElement}\n */\n get image() {\n return this._image;\n }\n\n /**\n * Sets an image file path that the ````SpriteMarker````'s image is sourced from.\n *\n * Accepted file types are PNG and JPEG.\n *\n * Sets {@link Texture#image} null.\n *\n * @type {String}\n */\n set src(src) {\n this._src = src;\n if (this._src) {\n this._image = null;\n const image = new Image();\n image.onload = () => {\n this._texture.image = image;\n this._imageSize[0] = image.width;\n this._imageSize[1] = image.height;\n this._updatePlaneSizeFromImage();\n };\n image.src = this._src;\n }\n }\n\n /**\n * Gets the image file path that the ````SpriteMarker````'s image is sourced from, if set.\n *\n * Returns null if not set.\n *\n * @type {String}\n */\n get src() {\n return this._src;\n }\n\n /**\n * Sets the World-space size of the longest edge of the ````SpriteMarker````.\n *\n * Note that ````SpriteMarker```` sets its aspect ratio to match its image. If we set a value of ````1000````, and\n * the image has size ````400x300````, then the ````SpriteMarker```` will then have size ````1000 x 750````.\n *\n * Default value is ````1.0````.\n *\n * @param {Number} size New World-space size of the ````SpriteMarker````.\n */\n set size(size) {\n this._size = (size === undefined || size === null) ? 1.0 : size;\n if (this._image) {\n this._updatePlaneSizeFromImage();\n }\n }\n\n /**\n * Gets the World-space size of the longest edge of the ````SpriteMarker````.\n *\n * Returns {Number} World-space size of the ````SpriteMarker````.\n */\n get size() {\n return this._size;\n }\n\n /**\n * Sets if this ````SpriteMarker```` is included in boundary calculations.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set collidable(value) {\n this._mesh.collidable = (value !== false);\n }\n\n /**\n * Gets if this ````SpriteMarker```` is included in boundary calculations.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get collidable() {\n return this._mesh.collidable;\n }\n\n /**\n * Sets if this ````SpriteMarker```` is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set clippable(value) {\n this._mesh.clippable = (value !== false);\n }\n\n /**\n * Gets if this ````SpriteMarker```` is clippable.\n *\n * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get clippable() {\n return this._mesh.clippable;\n }\n\n /**\n * Sets if this ````SpriteMarker```` is pickable.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n set pickable(value) {\n this._mesh.pickable = (value !== false);\n }\n\n /**\n * Gets if this ````SpriteMarker```` is pickable.\n *\n * Default is ````true````.\n *\n * @type {Boolean}\n */\n get pickable() {\n return this._mesh.pickable;\n }\n\n /**\n * Sets the opacity factor for this ````SpriteMarker````.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n set opacity(opacity) {\n this._mesh.opacity = opacity;\n }\n\n /**\n * Gets this ````SpriteMarker````'s opacity factor.\n *\n * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas.\n *\n * @type {Number}\n */\n get opacity() {\n return this._mesh.opacity;\n }\n\n _updatePlaneSizeFromImage() {\n const halfSize = this._size * 0.5;\n const width = this._imageSize[0];\n const height = this._imageSize[1];\n const aspect = height / width;\n if (width > height) {\n this._geometry.positions = [\n halfSize, halfSize * aspect, 0,\n -halfSize, halfSize * aspect, 0,\n -halfSize, -halfSize * aspect, 0,\n halfSize, -halfSize * aspect, 0\n ];\n } else {\n this._geometry.positions = [\n halfSize / aspect, halfSize, 0,\n -halfSize / aspect, halfSize, 0,\n -halfSize / aspect, -halfSize, 0,\n halfSize / aspect, -halfSize, 0\n ];\n }\n }\n}\n\n/**\n * @desc Saves and restores the state of a {@link Scene}'s {@link Camera}.\n *\n * ## See Also\n *\n * * {@link ModelMemento} - Saves and restores a snapshot of the visual state of the {@link Entity}'s of a model within a {@link Scene}.\n * * {@link ObjectsMemento} - Saves and restores a snapshot of the visual state of the {@link Entity}'s that represent objects within a {@link Scene}.\n *\n * ## Usage\n *\n * In the example below, we'll create a {@link Viewer} and use an {@link XKTLoaderPlugin} to load an ````.xkt```` model. When the model has loaded, we'll save a snapshot of the {@link Camera} state in an CameraMemento. Then we'll move the Camera, and then we'll restore its original state again from the CameraMemento.\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, CameraMemento} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * // Load a model\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/schependomlaan/schependomlaan.xkt\"\n * });\n *\n * // Set camera\n * viewer.camera.eye = [-2.56, 8.38, 8.27];\n * viewer.camera.look = [13.44, 3.31, -14.83];\n * viewer.camera.up = [0.10, 0.98, -0.14];\n *\n * model.on(\"loaded\", () => {\n *\n * // Model has loaded\n *\n * // Save memento of camera state\n * const cameraMemento = new CameraMemento();\n *\n * cameraMemento.saveCamera(viewer.scene);\n *\n * // Move the camera\n * viewer.camera.eye = [45.3, 2.00, 5.13];\n * viewer.camera.look = [0.0, 5.5, 10.0];\n * viewer.camera.up = [0.10, 0.98, -0.14];\n *\n * // Restore the camera state again\n * objectsMemento.restoreCamera(viewer.scene);\n * });\n * ````\n */\nclass CameraMemento {\n\n /**\n * Creates a CameraState.\n *\n * @param {Scene} [scene] When given, immediately saves the state of the given {@link Scene}'s {@link Camera}.\n */\n constructor(scene) {\n\n /** @private */\n this._eye = math.vec3();\n\n /** @private */\n this._look = math.vec3();\n\n /** @private */\n this._up = math.vec3();\n\n /** @private */\n this._projection = {};\n\n if (scene) {\n this.saveCamera(scene);\n }\n }\n\n /**\n * Saves the state of the given {@link Scene}'s {@link Camera}.\n *\n * @param {Scene} scene The scene that contains the {@link Camera}.\n */\n saveCamera(scene) {\n\n const camera = scene.camera;\n const project = camera.project;\n\n this._eye.set(camera.eye);\n this._look.set(camera.look);\n this._up.set(camera.up);\n\n switch (camera.projection) {\n\n case \"perspective\":\n this._projection = {\n projection: \"perspective\",\n fov: project.fov,\n fovAxis: project.fovAxis,\n near: project.near,\n far: project.far\n };\n break;\n\n case \"ortho\":\n this._projection = {\n projection: \"ortho\",\n scale: project.scale,\n near: project.near,\n far: project.far\n };\n break;\n\n case \"frustum\":\n this._projection = {\n projection: \"frustum\",\n left: project.left,\n right: project.right,\n top: project.top,\n bottom: project.bottom,\n near: project.near,\n far: project.far\n };\n break;\n\n case \"custom\":\n this._projection = {\n projection: \"custom\",\n matrix: project.matrix.slice()\n };\n break;\n }\n }\n\n /**\n * Restores a {@link Scene}'s {@link Camera} to the state previously captured with {@link CameraMemento#saveCamera}.\n *\n * @param {Scene} scene The scene.\n * @param {Function} [done] When this callback is given, will fly the {@link Camera} to the saved state then fire the callback. Otherwise will just jump the Camera to the saved state.\n */\n restoreCamera(scene, done) {\n\n const camera = scene.camera;\n const savedProjection = this._projection;\n\n function restoreProjection() {\n\n switch (savedProjection.type) {\n\n case \"perspective\":\n camera.perspective.fov = savedProjection.fov;\n camera.perspective.fovAxis = savedProjection.fovAxis;\n camera.perspective.near = savedProjection.near;\n camera.perspective.far = savedProjection.far;\n break;\n\n case \"ortho\":\n camera.ortho.scale = savedProjection.scale;\n camera.ortho.near = savedProjection.near;\n camera.ortho.far = savedProjection.far;\n break;\n\n case \"frustum\":\n camera.frustum.left = savedProjection.left;\n camera.frustum.right = savedProjection.right;\n camera.frustum.top = savedProjection.top;\n camera.frustum.bottom = savedProjection.bottom;\n camera.frustum.near = savedProjection.near;\n camera.frustum.far = savedProjection.far;\n break;\n\n case \"custom\":\n camera.customProjection.matrix = savedProjection.matrix;\n break;\n }\n }\n\n if (done) {\n scene.viewer.cameraFlight.flyTo({\n eye: this._eye,\n look: this._look,\n up: this._up,\n orthoScale: savedProjection.scale,\n projection: savedProjection.projection\n }, () => {\n restoreProjection();\n done();\n });\n } else {\n camera.eye = this._eye;\n camera.look = this._look;\n camera.up = this._up;\n restoreProjection();\n camera.projection = savedProjection.projection;\n }\n }\n}\n\nconst color$3 = math.vec3();\n\n/**\n * @desc Saves and restores a snapshot of the visual state of the {@link Entity}'s of a model within a {@link Scene}.\n *\n * ## Usage\n *\n * In the example below, we'll create a {@link Viewer} and use an {@link XKTLoaderPlugin} to load an ````.xkt```` model. When the model has loaded, we'll hide a couple of {@link Entity}s and save a snapshot of the visual states of all its Entitys in an ModelMemento. Then we'll show all the Entitys\n * again, and then we'll restore the visual states of all the Entitys again from the ModelMemento, which will hide those two Entitys again.\n *\n * ## See Also\n *\n * * {@link CameraMemento} - Saves and restores the state of a {@link Scene}'s {@link Camera}.\n * * {@link ObjectsMemento} - Saves and restores a snapshot of the visual state of the {@link Entity}'s that represent objects within a {@link Scene}.\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, ModelMemento} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * // Load a model\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/schependomlaan/schependomlaan.xkt\"\n * });\n *\n * model.on(\"loaded\", () => {\n *\n * // Model has loaded\n *\n * // Hide a couple of objects\n * viewer.scene.objects[\"0u4wgLe6n0ABVaiXyikbkA\"].visible = false;\n * viewer.scene.objects[\"3u4wgLe3n0AXVaiXyikbYO\"].visible = false;\n *\n * // Save memento of all object states, which includes those two hidden objects\n * const ModelMemento = new ModelMemento();\n *\n * const metaModel = viewer.metaScene.metaModels\n * ModelMemento.saveObjects(viewer.scene);\n *\n * // Show all objects\n * viewer.scene.setObjectsVisible(viewer.scene.objectIds, true);\n *\n * // Restore the objects states again, which involves hiding those two objects again\n * ModelMemento.restoreObjects(viewer.scene);\n * });\n * `````\n *\n * ## Masking Saved State\n *\n * We can optionally supply a mask to focus what state we save and restore.\n *\n * For example, to save and restore only the {@link Entity#visible} and {@link Entity#clippable} states:\n *\n * ````javascript\n * ModelMemento.saveObjects(viewer.scene, {\n * visible: true,\n * clippable: true\n * });\n *\n * //...\n *\n * // Restore the objects states again\n * ModelMemento.restoreObjects(viewer.scene);\n * ````\n */\nclass ModelMemento {\n\n /**\n * Creates a ModelMemento.\n *\n * @param {MetaModel} [metaModel] When given, immediately saves the model's {@link Entity} states to this ModelMemento.\n */\n constructor(metaModel) {\n\n /** @private */\n this.objectsVisible = [];\n\n /** @private */\n this.objectsEdges = [];\n\n /** @private */\n this.objectsXrayed = [];\n\n /** @private */\n this.objectsHighlighted = [];\n\n /** @private */\n this.objectsSelected = [];\n\n /** @private */\n this.objectsClippable = [];\n\n /** @private */\n this.objectsPickable = [];\n\n /** @private */\n this.objectsColorize = [];\n\n /** @private */\n this.objectsOpacity = [];\n\n /** @private */\n this.numObjects = 0;\n\n if (metaModel) {\n const metaScene = metaModel.metaScene;\n const scene = metaScene.scene;\n this.saveObjects(scene, metaModel);\n }\n }\n\n /**\n * Saves a snapshot of the visual state of the {@link Entity}'s that represent objects within a model.\n *\n * @param {Scene} scene The scene.\n * @param {MetaModel} metaModel Represents the model. Corresponds with an {@link Entity} that represents the model in the scene.\n * @param {Object} [mask] Masks what state gets saved. Saves all state when not supplied.\n * @param {boolean} [mask.visible] Saves {@link Entity#visible} values when ````true````.\n * @param {boolean} [mask.visible] Saves {@link Entity#visible} values when ````true````.\n * @param {boolean} [mask.edges] Saves {@link Entity#edges} values when ````true````.\n * @param {boolean} [mask.xrayed] Saves {@link Entity#xrayed} values when ````true````.\n * @param {boolean} [mask.highlighted] Saves {@link Entity#highlighted} values when ````true````.\n * @param {boolean} [mask.selected] Saves {@link Entity#selected} values when ````true````.\n * @param {boolean} [mask.clippable] Saves {@link Entity#clippable} values when ````true````.\n * @param {boolean} [mask.pickable] Saves {@link Entity#pickable} values when ````true````.\n * @param {boolean} [mask.colorize] Saves {@link Entity#colorize} values when ````true````.\n * @param {boolean} [mask.opacity] Saves {@link Entity#opacity} values when ````true````.\n */\n saveObjects(scene, metaModel, mask) {\n\n this.numObjects = 0;\n\n this._mask = mask ? utils.apply(mask, {}) : null;\n\n const visible = (!mask || mask.visible);\n const edges = (!mask || mask.edges);\n const xrayed = (!mask || mask.xrayed);\n const highlighted = (!mask || mask.highlighted);\n const selected = (!mask || mask.selected);\n const clippable = (!mask || mask.clippable);\n const pickable = (!mask || mask.pickable);\n const colorize = (!mask || mask.colorize);\n const opacity = (!mask || mask.opacity);\n\n const metaObjects = metaModel.metaObjects;\n const objects = scene.objects;\n\n for (let i = 0, len = metaObjects.length; i < len; i++) {\n const metaObject = metaObjects[i];\n const objectId = metaObject.id;\n const object = objects[objectId];\n if (!object) {\n continue;\n }\n if (visible) {\n this.objectsVisible[i] = object.visible;\n }\n if (edges) {\n this.objectsEdges[i] = object.edges;\n }\n if (xrayed) {\n this.objectsXrayed[i] = object.xrayed;\n }\n if (highlighted) {\n this.objectsHighlighted[i] = object.highlighted;\n }\n if (selected) {\n this.objectsSelected[i] = object.selected;\n }\n if (clippable) {\n this.objectsClippable[i] = object.clippable;\n }\n if (pickable) {\n this.objectsPickable[i] = object.pickable;\n }\n if (colorize) {\n const objectColor = object.colorize;\n this.objectsColorize[i * 3 + 0] = objectColor[0];\n this.objectsColorize[i * 3 + 1] = objectColor[1];\n this.objectsColorize[i * 3 + 2] = objectColor[2];\n }\n if (opacity) {\n this.objectsOpacity[i] = object.opacity;\n }\n this.numObjects++;\n }\n }\n\n /**\n * Restores a {@link Scene}'s {@link Entity}'s to their state previously captured with {@link ModelMemento#saveObjects}.\n *\n * Assumes that the model has not been destroyed or modified since saving.\n *\n * @param {Scene} scene The scene that was given to {@link ModelMemento#saveObjects}.\n * @param {MetaModel} metaModel The metamodel that was given to {@link ModelMemento#saveObjects}.\n */\n restoreObjects(scene, metaModel) {\n\n const mask = this._mask;\n\n const visible = (!mask || mask.visible);\n const edges = (!mask || mask.edges);\n const xrayed = (!mask || mask.xrayed);\n const highlighted = (!mask || mask.highlighted);\n const selected = (!mask || mask.selected);\n const clippable = (!mask || mask.clippable);\n const pickable = (!mask || mask.pickable);\n const colorize = (!mask || mask.colorize);\n const opacity = (!mask || mask.opacity);\n\n const metaObjects = metaModel.metaObjects;\n const objects = scene.objects;\n\n for (let i = 0, len = metaObjects.length; i < len; i++) {\n const metaObject = metaObjects[i];\n const objectId = metaObject.id;\n const object = objects[objectId];\n if (!object) {\n continue;\n }\n if (visible) {\n object.visible = this.objectsVisible[i];\n }\n if (edges) {\n object.edges = this.objectsEdges[i];\n }\n if (xrayed) {\n object.xrayed = this.objectsXrayed[i];\n }\n if (highlighted) {\n object.highlighted = this.objectsHighlighted[i];\n }\n if (selected) {\n object.selected = this.objectsSelected[i];\n }\n if (clippable) {\n object.clippable = this.objectsClippable[i];\n }\n if (pickable) {\n object.pickable = this.objectsPickable[i];\n }\n if (colorize) {\n color$3[0] = this.objectsColorize[i * 3 + 0];\n color$3[1] = this.objectsColorize[i * 3 + 1];\n color$3[2] = this.objectsColorize[i * 3 + 2];\n object.colorize = color$3;\n }\n if (opacity) {\n object.opacity = this.objectsOpacity[i];\n }\n }\n }\n}\n\nconst color$2 = math.vec3();\n\n/**\n * @desc Saves and restores a snapshot of the visual state of the {@link Entity}'s that represent objects within a {@link Scene}.\n *\n * * An Entity represents an object when {@link Entity#isObject} is ````true````.\n * * Each object-Entity is registered by {@link Entity#id} in {@link Scene#objects}.\n *\n * ## See Also\n *\n * * {@link CameraMemento} - Saves and restores the state of a {@link Scene}'s {@link Camera}.\n * * {@link ModelMemento} - Saves and restores a snapshot of the visual state of the {@link Entity}'s of a model within a {@link Scene}.\n *\n * ## Usage\n *\n * In the example below, we'll create a {@link Viewer} and use an {@link XKTLoaderPlugin} to load an ````.xkt```` model. When the model has loaded, we'll hide a couple of {@link Entity}s and save a snapshot of the visual states of all the Entitys in an ObjectsMemento. Then we'll show all the Entitys\n * again, and then we'll restore the visual states of all the Entitys again from the ObjectsMemento, which will hide those two Entitys again.\n *\n * ````javascript\n * import {Viewer, XKTLoaderPlugin, ObjectsMemento} from \"xeokit-sdk.es.js\";\n *\n * const viewer = new Viewer({\n * canvasId: \"myCanvas\"\n * });\n *\n * // Load a model\n * const xktLoader = new XKTLoaderPlugin(viewer);\n *\n * const model = xktLoader.load({\n * id: \"myModel\",\n * src: \"./models/xkt/schependomlaan/schependomlaan.xkt\"\n * });\n *\n * model.on(\"loaded\", () => {\n *\n * // Model has loaded\n *\n * // Hide a couple of objects\n * viewer.scene.objects[\"0u4wgLe6n0ABVaiXyikbkA\"].visible = false;\n * viewer.scene.objects[\"3u4wgLe3n0AXVaiXyikbYO\"].visible = false;\n *\n * // Save memento of all object states, which includes those two hidden objects\n * const objectsMemento = new ObjectsMemento();\n *\n * objectsMemento.saveObjects(viewer.scene);\n *\n * // Show all objects\n * viewer.scene.setObjectsVisible(viewer.scene.objectIds, true);\n *\n * // Restore the objects states again, which involves hiding those two objects again\n * objectsMemento.restoreObjects(viewer.scene);\n * });\n * `````\n *\n * ## Masking Saved State\n *\n * We can optionally supply a mask to focus what state we save and restore.\n *\n * For example, to save and restore only the {@link Entity#visible} and {@link Entity#clippable} states:\n *\n * ````javascript\n * objectsMemento.saveObjects(viewer.scene, {\n * visible: true,\n * clippable: true\n * });\n *\n * //...\n *\n * // Restore the objects states again\n * objectsMemento.restoreObjects(viewer.scene);\n * ````\n */\nclass ObjectsMemento {\n\n /**\n * Creates an ObjectsMemento.\n */\n constructor() {\n\n /** @private */\n this.objectsVisible = [];\n\n /** @private */\n this.objectsEdges = [];\n\n /** @private */\n this.objectsXrayed = [];\n\n /** @private */\n this.objectsHighlighted = [];\n\n /** @private */\n this.objectsSelected = [];\n\n /** @private */\n this.objectsClippable = [];\n\n /** @private */\n this.objectsPickable = [];\n\n /** @private */\n this.objectsColorize = [];\n\n /** @private */\n this.objectsHasColorize = [];\n\n /** @private */\n this.objectsOpacity = [];\n\n /** @private */\n this.numObjects = 0;\n }\n\n /**\n * Saves a snapshot of the visual state of the {@link Entity}'s that represent objects within a {@link Scene}.\n *\n * @param {Scene} scene The scene.\n * @param {Object} [mask] Masks what state gets saved. Saves all state when not supplied.\n * @param {boolean} [mask.visible] Saves {@link Entity#visible} values when ````true````.\n * @param {boolean} [mask.visible] Saves {@link Entity#visible} values when ````true````.\n * @param {boolean} [mask.edges] Saves {@link Entity#edges} values when ````true````.\n * @param {boolean} [mask.xrayed] Saves {@link Entity#xrayed} values when ````true````.\n * @param {boolean} [mask.highlighted] Saves {@link Entity#highlighted} values when ````true````.\n * @param {boolean} [mask.selected] Saves {@link Entity#selected} values when ````true````.\n * @param {boolean} [mask.clippable] Saves {@link Entity#clippable} values when ````true````.\n * @param {boolean} [mask.pickable] Saves {@link Entity#pickable} values when ````true````.\n * @param {boolean} [mask.colorize] Saves {@link Entity#colorize} values when ````true````.\n * @param {boolean} [mask.opacity] Saves {@link Entity#opacity} values when ````true````.\n */\n saveObjects(scene, mask) {\n\n this.numObjects = 0;\n\n this._mask = mask ? utils.apply(mask, {}) : null;\n\n const objects = scene.objects;\n const visible = (!mask || mask.visible);\n const edges = (!mask || mask.edges);\n const xrayed = (!mask || mask.xrayed);\n const highlighted = (!mask || mask.highlighted);\n const selected = (!mask || mask.selected);\n const clippable = (!mask || mask.clippable);\n const pickable = (!mask || mask.pickable);\n const colorize = (!mask || mask.colorize);\n const opacity = (!mask || mask.opacity);\n\n for (let objectId in objects) {\n if (objects.hasOwnProperty(objectId)) {\n const object = objects[objectId];\n const i = this.numObjects;\n if (visible) {\n this.objectsVisible[i] = object.visible;\n }\n if (edges) {\n this.objectsEdges[i] = object.edges;\n }\n if (xrayed) {\n this.objectsXrayed[i] = object.xrayed;\n }\n if (highlighted) {\n this.objectsHighlighted[i] = object.highlighted;\n }\n if (selected) {\n this.objectsSelected[i] = object.selected;\n }\n if (clippable) {\n this.objectsClippable[i] = object.clippable;\n }\n if (pickable) {\n this.objectsPickable[i] = object.pickable;\n }\n if (colorize) {\n const objectColor = object.colorize;\n if (objectColor) {\n this.objectsColorize[i * 3 + 0] = objectColor[0];\n this.objectsColorize[i * 3 + 1] = objectColor[1];\n this.objectsColorize[i * 3 + 2] = objectColor[2];\n this.objectsHasColorize[i] = true;\n } else {\n this.objectsHasColorize[i] = false;\n }\n }\n if (opacity) {\n this.objectsOpacity[i] = object.opacity;\n }\n this.numObjects++;\n }\n }\n }\n\n /**\n * Restores a {@link Scene}'s {@link Entity}'s to their state previously captured with {@link ObjectsMemento#saveObjects}.\n * @param {Scene} scene The scene.\n */\n restoreObjects(scene) {\n\n const mask = this._mask;\n\n const visible = (!mask || mask.visible);\n const edges = (!mask || mask.edges);\n const xrayed = (!mask || mask.xrayed);\n const highlighted = (!mask || mask.highlighted);\n const selected = (!mask || mask.selected);\n const clippable = (!mask || mask.clippable);\n const pickable = (!mask || mask.pickable);\n const colorize = (!mask || mask.colorize);\n const opacity = (!mask || mask.opacity);\n\n var i = 0;\n\n const objects = scene.objects;\n\n for (let objectId in objects) {\n if (objects.hasOwnProperty(objectId)) {\n const object = objects[objectId];\n if (visible) {\n object.visible = this.objectsVisible[i];\n }\n if (edges) {\n object.edges = this.objectsEdges[i];\n }\n if (xrayed) {\n object.xrayed = this.objectsXrayed[i];\n }\n if (highlighted) {\n object.highlighted = this.objectsHighlighted[i];\n }\n if (selected) {\n object.selected = this.objectsSelected[i];\n }\n if (clippable) {\n object.clippable = this.objectsClippable[i];\n }\n if (pickable) {\n object.pickable = this.objectsPickable[i];\n }\n if (colorize ) {\n if (this.objectsHasColorize[i]) {\n color$2[0] = this.objectsColorize[i * 3 + 0];\n color$2[1] = this.objectsColorize[i * 3 + 1];\n color$2[2] = this.objectsColorize[i * 3 + 2];\n object.colorize = color$2;\n } else {\n object.colorize = null;\n }\n }\n if (opacity) {\n object.opacity = this.objectsOpacity[i];\n }\n i++;\n }\n }\n }\n}\n\n/**\n * @desc A {@link Curve} along which a 3D position can be animated.\n *\n * * As shown in the diagram below, a CubicBezierCurve is defined by four control points.\n * * You can sample a {@link CubicBezierCurve#point} and a {@link CubicBezierCurve#tangent} vector on a CubicBezierCurve for any given value of {@link CubicBezierCurve#t} in the range [0..1].\n * * When you set {@link CubicBezierCurve#t} on a CubicBezierCurve, its {@link CubicBezierCurve#point} and {@link CubicBezierCurve#tangent} properties will update accordingly.\n * * To build a complex path, you can combine an unlimited combination of CubicBezierCurves, {@link QuadraticBezierCurve}s and {@link SplineCurve}s into a {@link Path}.\n *\n *
\n * \n *
\n * [Cubic Bezier Curve from WikiPedia](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)\n */\nclass CubicBezierCurve extends Curve {\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this CubicBezierCurve as well.\n * @param {*} [cfg] Configs\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {Number[]} [cfg.v0=[0,0,0]] The starting point.\n * @param {Number[]} [cfg.v1=[0,0,0]] The first control point.\n * @param {Number[]} [cfg.v2=[0,0,0]] The middle control point.\n * @param {Number[]} [cfg.v3=[0,0,0]] The ending point.\n * @param {Number} [cfg.t=0] Current position on this CubicBezierCurve, in range between 0..1.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this.v0 = cfg.v0;\n this.v1 = cfg.v1;\n this.v2 = cfg.v2;\n this.v3 = cfg.v3;\n this.t = cfg.t;\n }\n\n /**\n * Sets the starting point on this CubicBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @param {Number[]} value The starting point.\n */\n set v0(value) {\n this._v0 = value || math.vec3([0, 0, 0]);\n }\n\n /**\n * Gets the starting point on this CubicBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @returns {Number[]} The starting point.\n */\n get v0() {\n return this._v0;\n }\n\n /**\n * Sets the first control point on this CubicBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @param {Number[]} value The first control point.\n */\n set v1(value) {\n this._v1 = value || math.vec3([0, 0, 0]);\n }\n\n /**\n * Gets the first control point on this CubicBezierCurve.\n *\n * Fires a {@link CubicBezierCurve#v1:event} event on change.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @returns {Number[]} The first control point.\n */\n get v1() {\n return this._v1;\n }\n\n /**\n * Sets the second control point on this CubicBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @param {Number[]} value The second control point.\n */\n set v2(value) {\n this._v2 = value || math.vec3([0, 0, 0]);\n }\n\n /**\n * Gets the second control point on this CubicBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @returns {Number[]} The second control point.\n */\n get v2() {\n return this._v2;\n }\n\n /**\n * Sets the end point on this CubicBezierCurve.\n *\n * Fires a {@link CubicBezierCurve#v3:event} event on change.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @param {Number[]} value The end point.\n */\n set v3(value) {\n this.fire(\"v3\", this._v3 = value || math.vec3([0, 0, 0]));\n }\n\n /**\n * Gets the end point on this CubicBezierCurve.\n *\n * Fires a {@link CubicBezierCurve#v3:event} event on change.\n *\n * Default value is ````[0.0, 0.0, 0.0]````\n *\n * @returns {Number[]} The end point.\n */\n get v3() {\n return this._v3;\n }\n\n /**\n * Sets the current position of progress along this CubicBezierCurve.\n *\n * Automatically clamps to range ````[0..1]````.\n *\n * @param {Number} value New progress time value.\n */\n set t(value) {\n value = value || 0;\n this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);\n }\n\n /**\n * Gets the current position of progress along this CubicBezierCurve.\n *\n * @returns {Number} Current progress time value.\n */\n get t() {\n return this._t;\n }\n\n /**\n * Returns point on this CubicBezierCurve at the given position.\n *\n * @param {Number} t Position to get point at.\n *\n * @returns {Number[]} The point at the given position.\n */\n get point() {\n return this.getPoint(this._t);\n }\n\n /**\n * Returns point on this CubicBezierCurve at the given position.\n *\n * @param {Number} t Position to get point at.\n *\n * @returns {Number[]} The point at the given position.\n */\n getPoint(t) {\n\n var vector = math.vec3();\n\n vector[0] = math.b3(t, this._v0[0], this._v1[0], this._v2[0], this._v3[0]);\n vector[1] = math.b3(t, this._v0[1], this._v1[1], this._v2[1], this._v3[1]);\n vector[2] = math.b3(t, this._v0[2], this._v1[2], this._v2[2], this._v3[2]);\n\n return vector;\n }\n\n getJSON() {\n return {\n v0: this._v0,\n v1: this._v1,\n v2: this._v2,\n v3: this._v3,\n t: this._t\n };\n }\n}\n\n/**\n * @desc A complex curved path constructed from various {@link Curve} subtypes.\n *\n * * A Path can be constructed from these {@link Curve} subtypes: {@link SplineCurve}, {@link CubicBezierCurve} and {@link QuadraticBezierCurve}.\n * * You can sample a {@link Path#point} and a {@link Curve#tangent} vector on a Path for any given value of {@link Path#t} in the range ````[0..1]````.\n * * When you set {@link Path#t} on a Path, its {@link Path#point} and {@link Curve#tangent} properties will update accordingly.\n */\nclass Path extends Curve {\n\n /**\n * @constructor\n * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this SectionPlane as well.\n * @param {*} [cfg] Path configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.\n * @param {String []} [cfg.paths=[]] IDs or instances of {{#crossLink \"path\"}}{{/crossLink}} subtypes to add to this Path.\n * @param {Number} [cfg.t=0] Current position on this Path, in range between 0..1.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this._cachedLengths = [];\n this._dirty = true;\n this._curves = []; // Array of child Curve components\n this._t = 0;\n this._dirtySubs = []; // Subscriptions to \"dirty\" events from child Curve components\n this._destroyedSubs = []; // Subscriptions to \"destroyed\" events from child Curve components\n this.curves = cfg.curves || []; // Add initial curves\n this.t = cfg.t; // Set initial progress\n }\n\n /**\n * Adds a {@link Curve} to this Path.\n *\n * @param {Curve} curve The {@link Curve} to add.\n */\n addCurve(curve) {\n this._curves.push(curve);\n this._dirty = true;\n }\n\n /**\n * Sets the {@link Curve}s in this Path.\n *\n * Default value is ````[]````.\n *\n * @param {{Array of Spline, Path, QuadraticBezierCurve or CubicBezierCurve}} value.\n */\n set curves(value) {\n\n value = value || [];\n\n var curve;\n // Unsubscribe from events on old curves\n var i;\n var len;\n for (i = 0, len = this._curves.length; i < len; i++) {\n curve = this._curves[i];\n curve.off(this._dirtySubs[i]);\n curve.off(this._destroyedSubs[i]);\n }\n\n this._curves = [];\n this._dirtySubs = [];\n this._destroyedSubs = [];\n\n var self = this;\n\n function curveDirty() {\n self._dirty = true;\n }\n\n function curveDestroyed() {\n var id = this.id;\n for (i = 0, len = self._curves.length; i < len; i++) {\n if (self._curves[i].id === id) {\n self._curves = self._curves.slice(i, i + 1);\n self._dirtySubs = self._dirtySubs.slice(i, i + 1);\n self._destroyedSubs = self._destroyedSubs.slice(i, i + 1);\n self._dirty = true;\n return;\n }\n }\n }\n\n for (i = 0, len = value.length; i < len; i++) {\n curve = value[i];\n if (utils.isNumeric(curve) || utils.isString(curve)) {\n // ID given for curve - find the curve component\n var id = curve;\n curve = this.scene.components[id];\n if (!curve) {\n this.error(\"Component not found: \" + _inQuotes(id));\n continue;\n }\n }\n\n var type = curve.type;\n\n if (type !== \"xeokit.SplineCurve\" &&\n type !== \"xeokit.Path\" &&\n type !== \"xeokit.CubicBezierCurve\" &&\n type !== \"xeokit.QuadraticBezierCurve\") {\n\n this.error(\"Component \" + _inQuotes(curve.id)\n + \" is not a xeokit.SplineCurve, xeokit.Path or xeokit.QuadraticBezierCurve\");\n\n continue;\n }\n\n this._curves.push(curve);\n this._dirtySubs.push(curve.on(\"dirty\", curveDirty));\n this._destroyedSubs.push(curve.once(\"destroyed\", curveDestroyed));\n }\n\n this._dirty = true;\n }\n\n /**\n * Gets the {@link Curve}s in this Path.\n *\n * @returns {{Array of Spline, Path, QuadraticBezierCurve or CubicBezierCurve}} the {@link Curve}s in this path.\n */\n get curves() {\n return this._curves;\n }\n\n /**\n * Sets the current point of progress along this Path.\n *\n * Automatically clamps to range ````[0..1]````.\n *\n * Default value is ````0````.\n *\n * @param {Number} value The current point of progress.\n */\n set t(value) {\n value = value || 0;\n this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);\n }\n\n /**\n * Gets the current point of progress along this Path.\n *\n * Default value is ````0````.\n *\n * @returns {Number} The current point of progress.\n */\n get t() {\n return this._t;\n }\n\n /**\n * Gets point on this Path corresponding to the current value of {@link Path#t}.\n *\n * @returns {Number[]} The point.\n */\n get point() {\n return this.getPoint(this._t);\n }\n\n /**\n * Length of this Path, which is the cumulative length of all {@link Curve}s currently in {@link Path#curves}.\n *\n * @return {Number} Length of this path.\n */\n get length() {\n var lens = this._getCurveLengths();\n return lens[lens.length - 1];\n }\n\n /**\n * Gets a point on this Path corresponding to the given progress position.\n *\n * @param {Number} t Indicates point of progress along this curve, in the range [0..1].\n * @returns {Number[]}\n */\n getPoint(t) {\n var d = t * this.length;\n var curveLengths = this._getCurveLengths();\n var i = 0, diff, curve;\n while (i < curveLengths.length) {\n if (curveLengths[i] >= d) {\n diff = curveLengths[i] - d;\n curve = this._curves[i];\n var u = 1 - diff / curve.length;\n return curve.getPointAt(u);\n }\n i++;\n }\n return null;\n }\n\n _getCurveLengths() {\n if (!this._dirty) {\n return this._cachedLengths;\n }\n var lengths = [];\n var sums = 0;\n var i, il = this._curves.length;\n for (i = 0; i < il; i++) {\n sums += this._curves[i].length;\n lengths.push(sums);\n\n }\n this._cachedLengths = lengths;\n this._dirty = false;\n return lengths;\n }\n\n _getJSON() {\n var curveIds = [];\n for (var i = 0, len = this._curves.length; i < len; i++) {\n curveIds.push(this._curves[i].id);\n }\n return {\n curves: curveIds,\n t: this._t\n };\n }\n\n /**\n * Destroys this Path.\n */\n destroy() {\n super.destroy();\n var i;\n var len;\n var curve;\n for (i = 0, len = this._curves.length; i < len; i++) {\n curve = this._curves[i];\n curve.off(this._dirtySubs[i]);\n curve.off(this._destroyedSubs[i]);\n }\n }\n}\n\n/**\n * A **QuadraticBezierCurve** is a {@link Curve} along which a 3D position can be animated.\n *\n * * As shown in the diagram below, a QuadraticBezierCurve is defined by three control points\n * * You can sample a {@link QuadraticBezierCurve#point} and a {@link Curve#tangent} vector on a QuadraticBezierCurve for any given value of {@link QuadraticBezierCurve#t} in the range ````[0..1]````\n * * When you set {@link QuadraticBezierCurve#t} on a QuadraticBezierCurve, its {@link QuadraticBezierCurve#point} and {@link Curve#tangent} will update accordingly.\n * * To build a complex path, you can combine an unlimited combination of QuadraticBezierCurves, {@link CubicBezierCurve}s and {@link SplineCurve}s into a {@link Path}.\n *
\n * \n *
\n * *[Quadratic Bezier Curve from WikiPedia](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)*\n */\nclass QuadraticBezierCurve extends Curve {\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this MetallicMaterial as well.\n * @param {*} [cfg] Configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {{#crossLink \"Scene\"}}Scene{{/crossLink}}, generated automatically when omitted.\n * @param {Number[]} [cfg.v0=[0,0,0]] The starting point.\n * @param {Number[]} [cfg.v1=[0,0,0]] The middle control point.\n * @param {Number[]} [cfg.v2=[0,0,0]] The end point.\n * @param {Number[]} [cfg.t=0] Current position on this QuadraticBezierCurve, in range between ````0..1````.\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n this.v0 = cfg.v0;\n this.v1 = cfg.v1;\n this.v2 = cfg.v2;\n this.t = cfg.t;\n }\n\n /**\n * Sets the starting point on this QuadraticBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @param {Number[]} value New starting point.\n */\n set v0(value) {\n this._v0 = value || math.vec3([0, 0, 0]);\n }\n\n /**\n * Gets the starting point on this QuadraticBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @returns {Number[]} The starting point.\n */\n get v0() {\n return this._v0;\n }\n\n /**\n * Sets the middle control point on this QuadraticBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @param {Number[]} value New middle control point.\n */\n set v1(value) {\n this._v1 = value || math.vec3([0, 0, 0]);\n }\n\n /**\n * Gets the middle control point on this QuadraticBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @returns {Number[]} The middle control point.\n */\n get v1() {\n return this._v1;\n }\n\n /**\n * Sets the end point on this QuadraticBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @param {Number[]} value The new end point.\n */\n set v2(value) {\n this._v2 = value || math.vec3([0, 0, 0]);\n }\n\n /**\n * Gets the end point on this QuadraticBezierCurve.\n *\n * Default value is ````[0.0, 0.0, 0.0]````.\n *\n * @returns {Number[]} The end point.\n */\n get v2() {\n return this._v2;\n }\n\n /**\n * Sets the progress along this QuadraticBezierCurve.\n *\n * Automatically clamps to range [0..1].\n *\n * Default value is ````0````.\n *\n * @param {Number} value The new progress location.\n */\n set t(value) {\n value = value || 0;\n this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);\n }\n\n /**\n * Gets the progress along this QuadraticBezierCurve.\n *\n * Default value is ````0````.\n *\n * @returns {Number} The current progress location.\n */\n get t() {\n return this._t;\n }\n\n /**\n Point on this QuadraticBezierCurve at position {@link QuadraticBezierCurve/t}.\n\n @property point\n @type {Number[]}\n */\n get point() {\n return this.getPoint(this._t);\n }\n\n /**\n * Returns the point on this QuadraticBezierCurve at the given position.\n *\n * @param {Number} t Position to get point at.\n * @returns {Number[]} The point.\n */\n getPoint(t) {\n var vector = math.vec3();\n vector[0] = math.b2(t, this._v0[0], this._v1[0], this._v2[0]);\n vector[1] = math.b2(t, this._v0[1], this._v1[1], this._v2[1]);\n vector[2] = math.b2(t, this._v0[2], this._v1[2], this._v2[2]);\n return vector;\n }\n\n getJSON() {\n return {\n v0: this._v0,\n v1: this._v1,\n v2: this._v2,\n t: this._t\n };\n }\n}\n\n/**\n * @desc A high-performance model representation for efficient rendering and low memory usage.\n *\n * * PerformanceModel was replaced with {@link SceneModel} in ````xeokit-sdk v2.4````.\n * * PerformanceModel currently extends {@link SceneModel}, in order to maintain backward-compatibility until we remove PerformanceModel.\n * * See {@link SceneModel} for API details.\n *\n * @deprecated\n * @implements {Drawable}\n * @implements {Entity}\n * @extends {SceneModel}\n */\nclass PerformanceModel extends SceneModel {\n\n /**\n * See {@link VBOSceneModel} for details.\n *\n * @param owner\n * @param cfg\n */\n constructor(owner, cfg = {}) {\n super(owner, cfg);\n }\n}\n\n/**\n * @desc A Skybox.\n */\nclass Skybox extends Component {\n\n /**\n * @constructor\n * @param {Component} owner Owner component. When destroyed, the owner will destroy this PointLight as well.\n * @param {*} [cfg] Skybox configuration\n * @param {String} [cfg.id] Optional ID, unique among all components in the parent {Scene}, generated automatically when omitted.\n * @param {String} [cfg.src=null] Path to skybox texture\n * @param {String} [cfg.encoding=\"linear\"] Texture encoding format. See the {@link Texture#encoding} property for more info.\n * @param {Number} [cfg.size=1000] Size of this Skybox, given as the distance from the center at ````[0,0,0]```` to each face.\n * @param {Boolean} [cfg.active=true] True when this Skybox is visible.\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n this._skyboxMesh = new Mesh(this, {\n\n geometry: new ReadableGeometry(this, { // Box-shaped geometry\n primitive: \"triangles\",\n positions: [\n 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // v0-v1-v2-v3 front\n 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, // v0-v3-v4-v5 right\n 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, // v0-v5-v6-v1 top\n -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // v1-v6-v7-v2 left\n -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, // v7-v4-v3-v2 bottom\n 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1 // v4-v7-v6-v5 back\n ],\n uv: [\n 0.5, 0.6666, 0.25, 0.6666, 0.25, 0.3333, 0.5, 0.3333, 0.5, 0.6666, 0.5, 0.3333, 0.75, 0.3333, 0.75, 0.6666,\n 0.5, 0.6666, 0.5, 1, 0.25, 1, 0.25, 0.6666, 0.25, 0.6666, 0.0, 0.6666, 0.0, 0.3333, 0.25, 0.3333,\n 0.25, 0, 0.50, 0, 0.50, 0.3333, 0.25, 0.3333, 0.75, 0.3333, 1.0, 0.3333, 1.0, 0.6666, 0.75, 0.6666\n ],\n indices: [\n 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11,\n 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23\n ]\n }),\n background: true,\n scale: [2000, 2000, 2000], // Overridden when we initialize the 'size' property, below\n rotation: [0, -90, 0],\n material: new PhongMaterial(this, {\n ambient: [0, 0, 0],\n diffuse: [0, 0, 0],\n specular: [0, 0, 0],\n emissive: [1, 1, 1],\n emissiveMap: new Texture(this, {\n src: cfg.src,\n flipY: true,\n wrapS: \"clampToEdge\",\n wrapT: \"clampToEdge\",\n encoding: cfg.encoding || \"sRGB\"\n }),\n backfaces: true // Show interior faces of our skybox geometry\n }),\n // stationary: true,\n visible: false,\n pickable: false,\n clippable: false,\n collidable: false\n });\n\n this.size = cfg.size; // Sets 'xyz' property on the Mesh's Scale transform\n this.active = cfg.active;\n }\n\n\n /**\n * Sets the size of this Skybox, given as the distance from the center at [0,0,0] to each face.\n *\n * Default value is ````1000````.\n *\n * @param {Number} value The size.\n */\n set size(value) {\n this._size = value || 1000;\n this._skyboxMesh.scale = [this._size, this._size, this._size];\n }\n\n /**\n * Gets the size of this Skybox, given as the distance from the center at [0,0,0] to each face.\n *\n * Default value is ````1000````.\n *\n * @returns {Number} The size.\n */\n get size() {\n return this._size;\n }\n\n /**\n * Sets whether this Skybox is visible or not.\n *\n * Default value is ````true````.\n *\n * @param {Boolean} active Whether to make active or not.\n */\n set active(active) {\n this._skyboxMesh.visible = active;\n }\n\n /**\n * Gets if this Skybox is visible or not.\n *\n * Default active is ````true````.\n *\n * @returns {Boolean} ````true```` if the Skybox is active.\n */\n get active() {\n return this._skyboxMesh.visible;\n }\n}\n\n/**\n * Transcodes texture data.\n *\n * A {@link SceneModel} configured with an appropriate TextureTranscoder will allow us to add textures from\n * transcoded buffers or files. For a concrete example, see {@link VBOSceneModel}, which can be configured with\n * a {@link KTX2TextureTranscoder}, which allows us to add textures from KTX2 buffers and files.\n *\n * @interface\n */\nclass TextureTranscoder {\n\n /**\n * Transcodes texture data from transcoded buffers into a {@link Texture2D}.\n *\n * @param {ArrayBuffer[]} buffers Transcoded texture data. Given as an array of buffers so that we can support\n * multi-image textures, such as cube maps.\n * @param {*} config Transcoding options.\n * @param {Texture2D} texture The texture to load.\n * @returns {Promise} Resolves when the texture has loaded.\n */\n transcode(buffers, texture, config = {}) {\n }\n\n /**\n * Destroys this transcoder.\n */\n destroy() {\n }\n}\n\nconst screenPos = math.vec4();\nconst viewPos = math.vec4();\n\nconst tempVec3a$7 = math.vec3();\nconst tempVec3b$4 = math.vec3();\nconst tempVec3c$3 = math.vec3();\n\nconst tempVec4a$4 = math.vec4();\nconst tempVec4b$4 = math.vec4();\nconst tempVec4c$1 = math.vec4();\n\n/**\n * @private\n */\nclass PanController {\n\n constructor(scene) {\n this._scene = scene;\n }\n\n /**\n * Dollys the Camera towards the given target 2D canvas position.\n *\n * When the target's corresponding World-space position is also provided, then this function will also test if we've\n * dollied past the target, and will return ````true```` if that's the case.\n *\n * @param [optionalTargetWorldPos] Optional world position of the target\n * @param targetCanvasPos Canvas position of the target\n * @param dollyDelta Amount to dolly\n * @return True if optionalTargetWorldPos was given, and we've dollied past that position.\n */\n dollyToCanvasPos(optionalTargetWorldPos, targetCanvasPos, dollyDelta) {\n\n let dolliedThroughSurface = false;\n\n const camera = this._scene.camera;\n\n if (optionalTargetWorldPos) {\n const eyeToWorldPosVec = math.subVec3(optionalTargetWorldPos, camera.eye, tempVec3a$7);\n const eyeWorldPosDist = math.lenVec3(eyeToWorldPosVec);\n dolliedThroughSurface = (eyeWorldPosDist < dollyDelta);\n }\n\n if (camera.projection === \"perspective\") {\n\n camera.ortho.scale = camera.ortho.scale - dollyDelta;\n\n const unprojectedWorldPos = this._unproject(targetCanvasPos, tempVec4a$4);\n const offset = math.subVec3(unprojectedWorldPos, camera.eye, tempVec4c$1);\n const moveVec = math.mulVec3Scalar(math.normalizeVec3(offset), -dollyDelta, []);\n\n camera.eye = [camera.eye[0] - moveVec[0], camera.eye[1] - moveVec[1], camera.eye[2] - moveVec[2]];\n camera.look = [camera.look[0] - moveVec[0], camera.look[1] - moveVec[1], camera.look[2] - moveVec[2]];\n\n if (optionalTargetWorldPos) {\n\n // Subtle UX tweak - if we have a target World position, then set camera eye->look distance to\n // the same distance as from eye->target. This just gives us a better position for look,\n // if we subsequently orbit eye about look, so that we don't orbit a position that's\n // suddenly a lot closer than the point we pivoted about on the surface of the last object\n // that we click-drag-pivoted on.\n\n const eyeTargetVec = math.subVec3(optionalTargetWorldPos, camera.eye, tempVec3a$7);\n const lenEyeTargetVec = math.lenVec3(eyeTargetVec);\n const eyeLookVec = math.mulVec3Scalar(math.normalizeVec3(math.subVec3(camera.look, camera.eye, tempVec3b$4)), lenEyeTargetVec);\n camera.look = [camera.eye[0] + eyeLookVec[0], camera.eye[1] + eyeLookVec[1], camera.eye[2] + eyeLookVec[2]];\n }\n\n } else if (camera.projection === \"ortho\") {\n\n // - set ortho scale, getting the unprojected targetCanvasPos before and after, get that difference in a vector;\n // - get the vector in which we're dollying;\n // - add both vectors to camera eye and look.\n\n const worldPos1 = this._unproject(targetCanvasPos, tempVec4a$4);\n\n camera.ortho.scale = camera.ortho.scale - dollyDelta;\n camera.ortho._update(); // HACK\n\n const worldPos2 = this._unproject(targetCanvasPos, tempVec4b$4);\n const offset = math.subVec3(worldPos2, worldPos1, tempVec4c$1);\n const eyeLookMoveVec = math.mulVec3Scalar(math.normalizeVec3(math.subVec3(camera.look, camera.eye, tempVec3a$7)), -dollyDelta, tempVec3b$4);\n const moveVec = math.addVec3(offset, eyeLookMoveVec, tempVec3c$3);\n\n camera.eye = [camera.eye[0] - moveVec[0], camera.eye[1] - moveVec[1], camera.eye[2] - moveVec[2]];\n camera.look = [camera.look[0] - moveVec[0], camera.look[1] - moveVec[1], camera.look[2] - moveVec[2]];\n }\n\n return dolliedThroughSurface;\n }\n\n _unproject(canvasPos, worldPos) {\n\n const camera = this._scene.camera;\n const transposedProjectMat = camera.project.transposedMatrix;\n const Pt3 = transposedProjectMat.subarray(8, 12);\n const Pt4 = transposedProjectMat.subarray(12);\n const D = [0, 0, -1.0, 1];\n const screenZ = math.dotVec4(D, Pt3) / math.dotVec4(D, Pt4);\n\n camera.project.unproject(canvasPos, screenZ, screenPos, viewPos, worldPos);\n\n return worldPos;\n }\n\n destroy() {\n }\n}\n\nconst tempVec3a$6 = math.vec3();\nconst tempVec3b$3 = math.vec3();\nconst tempVec3c$2 = math.vec3();\n\nconst tempVec4a$3 = math.vec4();\nconst tempVec4b$3 = math.vec4();\nconst tempVec4c = math.vec4();\n\n\n/** @private */\nclass PivotController {\n\n /**\n * @private\n */\n constructor(scene, configs) {\n\n // Pivot math by: http://www.derschmale.com/\n\n this._scene = scene;\n this._configs = configs;\n this._pivotWorldPos = math.vec3();\n this._cameraOffset = math.vec3();\n this._azimuth = 0;\n this._polar = 0;\n this._radius = 0;\n this._pivotPosSet = false; // Initially false, true as soon as _pivotWorldPos has been set to some value\n this._pivoting = false; // True while pivoting\n this._shown = false;\n\n this._pivotSphereEnabled = false;\n this._pivotSphere = null;\n this._pivotSphereSize = 1;\n this._pivotSphereGeometry = null;\n this._pivotSphereMaterial = null;\n this._rtcCenter = math.vec3();\n this._rtcPos = math.vec3();\n\n this._pivotViewPos = math.vec4();\n this._pivotProjPos = math.vec4();\n this._pivotCanvasPos = math.vec2();\n this._cameraDirty = true;\n\n this._onViewMatrix = this._scene.camera.on(\"viewMatrix\", () => {\n this._cameraDirty = true;\n });\n\n this._onProjMatrix = this._scene.camera.on(\"projMatrix\", () => {\n this._cameraDirty = true;\n });\n\n this._onTick = this._scene.on(\"tick\", () => {\n this.updatePivotElement();\n this.updatePivotSphere();\n });\n }\n\n createPivotSphere() {\n const currentPos = this.getPivotPos();\n const cameraPos = math.vec3();\n math.decomposeMat4(math.inverseMat4(this._scene.viewer.camera.viewMatrix, math.mat4()), cameraPos, math.vec4(), math.vec3());\n const length = math.distVec3(cameraPos, currentPos);\n let radius = (Math.tan(Math.PI / 500) * length) * this._pivotSphereSize;\n\n if (this._scene.camera.projection == \"ortho\") {\n radius /= (this._scene.camera.ortho.scale / 2);\n }\n\n worldToRTCPos(currentPos, this._rtcCenter, this._rtcPos);\n this._pivotSphereGeometry = new VBOGeometry(\n this._scene,\n buildSphereGeometry({ radius })\n );\n this._pivotSphere = new Mesh(this._scene, {\n geometry: this._pivotSphereGeometry,\n material: this._pivotSphereMaterial,\n pickable: false,\n position: this._rtcPos,\n rtcCenter: this._rtcCenter\n });\n };\n\n destroyPivotSphere() {\n if (this._pivotSphere) {\n this._pivotSphere.destroy();\n this._pivotSphere = null;\n }\n if (this._pivotSphereGeometry) {\n this._pivotSphereGeometry.destroy();\n this._pivotSphereGeometry = null;\n }\n }\n\n updatePivotElement() {\n\n const camera = this._scene.camera;\n const canvas = this._scene.canvas;\n\n if (this._pivoting && this._cameraDirty) {\n\n math.transformPoint3(camera.viewMatrix, this.getPivotPos(), this._pivotViewPos);\n this._pivotViewPos[3] = 1;\n math.transformPoint4(camera.projMatrix, this._pivotViewPos, this._pivotProjPos);\n\n const canvasAABB = canvas.boundary;\n const canvasWidth = canvasAABB[2];\n const canvasHeight = canvasAABB[3];\n\n this._pivotCanvasPos[0] = Math.floor((1 + this._pivotProjPos[0] / this._pivotProjPos[3]) * canvasWidth / 2);\n this._pivotCanvasPos[1] = Math.floor((1 - this._pivotProjPos[1] / this._pivotProjPos[3]) * canvasHeight / 2);\n\n // data-textures: avoid to do continuous DOM layout calculations \n let canvasBoundingRect = canvas._lastBoundingClientRect;\n\n if (!canvasBoundingRect || canvas._canvasSizeChanged)\n {\n const canvasElem = canvas.canvas;\n\n canvasBoundingRect = canvas._lastBoundingClientRect = canvasElem.getBoundingClientRect ();\n }\n\n if (this._pivotElement) {\n this._pivotElement.style.left = (Math.floor(canvasBoundingRect.left + this._pivotCanvasPos[0]) - (this._pivotElement.clientWidth / 2) + window.scrollX) + \"px\";\n this._pivotElement.style.top = (Math.floor(canvasBoundingRect.top + this._pivotCanvasPos[1]) - (this._pivotElement.clientHeight / 2) + window.scrollY) + \"px\";\n }\n this._cameraDirty = false;\n }\n }\n\n updatePivotSphere() {\n if (this._pivoting && this._pivotSphere) {\n worldToRTCPos(this.getPivotPos(), this._rtcCenter, this._rtcPos);\n if(!math.compareVec3(this._rtcPos, this._pivotSphere.position)) {\n this.destroyPivotSphere();\n this.createPivotSphere();\n }\n }\n }\n /**\n * Sets the HTML DOM element that will represent the pivot position.\n *\n * @param pivotElement\n */\n setPivotElement(pivotElement) {\n this._pivotElement = pivotElement;\n }\n\n /**\n * Sets a sphere as the representation of the pivot position.\n *\n * @param {Object} [cfg] Sphere configuration.\n * @param {String} [cfg.size=1] Optional size factor of the sphere. Defaults to 1.\n * @param {String} [cfg.color=Array] Optional maretial color. Defaults to a red.\n */\n enablePivotSphere(cfg = {}) {\n this.destroyPivotSphere();\n this._pivotSphereEnabled = true;\n if (cfg.size) {\n this._pivotSphereSize = cfg.size;\n }\n const color = cfg.color || [1, 0, 0];\n this._pivotSphereMaterial = new PhongMaterial(this._scene, {\n emissive: color,\n ambient: color,\n specular: [0,0,0],\n diffuse: [0,0,0],\n });\n }\n\n /**\n * Remove the sphere as the representation of the pivot position.\n *\n */\n disablePivotSphere() {\n this.destroyPivotSphere();\n this._pivotSphereEnabled = false;\n }\n\n /**\n * Begins pivoting.\n */\n startPivot() {\n\n if (this._cameraLookingDownwards()) {\n this._pivoting = false;\n return false;\n }\n\n const camera = this._scene.camera;\n\n let lookat = math.lookAtMat4v(camera.eye, camera.look, camera.worldUp);\n math.transformPoint3(lookat, this.getPivotPos(), this._cameraOffset);\n\n const pivotPos = this.getPivotPos();\n this._cameraOffset[2] += math.distVec3(camera.eye, pivotPos);\n\n lookat = math.inverseMat4(lookat);\n\n const offset = math.transformVec3(lookat, this._cameraOffset);\n const diff = math.vec3();\n\n math.subVec3(camera.eye, pivotPos, diff);\n math.addVec3(diff, offset);\n\n if (camera.zUp) {\n const t = diff[1];\n diff[1] = diff[2];\n diff[2] = t;\n }\n\n this._radius = math.lenVec3(diff);\n this._polar = Math.acos(diff[1] / this._radius);\n this._azimuth = Math.atan2(diff[0], diff[2]);\n this._pivoting = true;\n }\n\n _cameraLookingDownwards() { // Returns true if angle between camera viewing direction and World-space \"up\" axis is too small\n const camera = this._scene.camera;\n const forwardAxis = math.normalizeVec3(math.subVec3(camera.look, camera.eye, tempVec3a$6));\n const rightAxis = math.cross3Vec3(forwardAxis, camera.worldUp, tempVec3b$3);\n let rightAxisLen = math.sqLenVec3(rightAxis);\n return (rightAxisLen <= 0.0001);\n }\n\n /**\n * Returns true if we are currently pivoting.\n *\n * @returns {Boolean}\n */\n getPivoting() {\n return this._pivoting;\n }\n\n /**\n * Sets a 3D World-space position to pivot about.\n *\n * @param {Number[]} worldPos The new World-space pivot position.\n */\n setPivotPos(worldPos) {\n this._pivotWorldPos.set(worldPos);\n this._pivotPosSet = true;\n }\n\n /**\n * Sets the pivot position to the 3D projection of the given 2D canvas coordinates on a sphere centered\n * at the viewpoint. The radius of the sphere is configured via {@link CameraControl#smartPivot}.\n *\n *\n * @param canvasPos\n */\n setCanvasPivotPos(canvasPos) {\n const camera = this._scene.camera;\n const pivotShereRadius = Math.abs(math.distVec3(this._scene.center, camera.eye));\n const transposedProjectMat = camera.project.transposedMatrix;\n const Pt3 = transposedProjectMat.subarray(8, 12);\n const Pt4 = transposedProjectMat.subarray(12);\n const D = [0, 0, -1.0, 1];\n const screenZ = math.dotVec4(D, Pt3) / math.dotVec4(D, Pt4);\n const worldPos = tempVec4a$3;\n camera.project.unproject(canvasPos, screenZ, tempVec4b$3, tempVec4c, worldPos);\n const eyeWorldPosVec = math.normalizeVec3(math.subVec3(worldPos, camera.eye, tempVec3a$6));\n const posOnSphere = math.addVec3(camera.eye, math.mulVec3Scalar(eyeWorldPosVec, pivotShereRadius, tempVec3b$3), tempVec3c$2);\n this.setPivotPos(posOnSphere);\n }\n\n /**\n * Gets the current position we're pivoting about.\n * @returns {Number[]} The current World-space pivot position.\n */\n getPivotPos() {\n return (this._pivotPosSet) ? this._pivotWorldPos : this._scene.camera.look; // Avoid pivoting about [0,0,0] by default\n }\n\n /**\n * Continues to pivot.\n *\n * @param {Number} yawInc Yaw rotation increment.\n * @param {Number} pitchInc Pitch rotation increment.\n */\n continuePivot(yawInc, pitchInc) {\n if (!this._pivoting) {\n return;\n }\n if (yawInc === 0 && pitchInc === 0) {\n return;\n }\n const camera = this._scene.camera;\n var dx = -yawInc;\n const dy = -pitchInc;\n if (camera.worldUp[2] === 1) {\n dx = -dx;\n }\n this._azimuth += -dx * .01;\n this._polar += dy * .01;\n this._polar = math.clamp(this._polar, .001, Math.PI - .001);\n const pos = [\n this._radius * Math.sin(this._polar) * Math.sin(this._azimuth),\n this._radius * Math.cos(this._polar),\n this._radius * Math.sin(this._polar) * Math.cos(this._azimuth)\n ];\n if (camera.worldUp[2] === 1) {\n const t = pos[1];\n pos[1] = pos[2];\n pos[2] = t;\n }\n // Preserve the eye->look distance, since in xeokit \"look\" is the point-of-interest, not the direction vector.\n const eyeLookLen = math.lenVec3(math.subVec3(camera.look, camera.eye, math.vec3()));\n const pivotPos = this.getPivotPos();\n math.addVec3(pos, pivotPos);\n let lookat = math.lookAtMat4v(pos, pivotPos, camera.worldUp);\n lookat = math.inverseMat4(lookat);\n const offset = math.transformVec3(lookat, this._cameraOffset);\n lookat[12] -= offset[0];\n lookat[13] -= offset[1];\n lookat[14] -= offset[2];\n const zAxis = [lookat[8], lookat[9], lookat[10]];\n camera.eye = [lookat[12], lookat[13], lookat[14]];\n math.subVec3(camera.eye, math.mulVec3Scalar(zAxis, eyeLookLen), camera.look);\n camera.up = [lookat[4], lookat[5], lookat[6]];\n this.showPivot();\n }\n\n /**\n * Shows the pivot position.\n *\n * Only works if we set an HTML DOM element to represent the pivot position.\n */\n showPivot() {\n if (this._shown) {\n return;\n }\n if (this._pivotElement) {\n this.updatePivotElement();\n this._pivotElement.style.visibility = \"visible\";\n }\n if (this._pivotSphereEnabled) {\n this.destroyPivotSphere();\n this.createPivotSphere();\n }\n this._shown = true;\n }\n\n /**\n * Hides the pivot position.\n *\n * Only works if we set an HTML DOM element to represent the pivot position.\n */\n hidePivot() {\n if (!this._shown) {\n return;\n }\n if (this._pivotElement) {\n this._pivotElement.style.visibility = \"hidden\";\n }\n if (this._pivotSphereEnabled) {\n this.destroyPivotSphere();\n }\n this._shown = false;\n }\n\n /**\n * Finishes pivoting.\n */\n endPivot() {\n this._pivoting = false;\n }\n\n destroy() {\n this.destroyPivotSphere();\n this._scene.camera.off(this._onViewMatrix);\n this._scene.camera.off(this._onProjMatrix);\n this._scene.off(this._onTick);\n }\n}\n\n/**\n *\n * @private\n */\nclass PickController {\n\n constructor(cameraControl, configs) {\n /**\n * @type {Scene}\n */\n this._scene = cameraControl.scene;\n\n this._cameraControl = cameraControl;\n\n this._scene.canvas.canvas.oncontextmenu = function (e) {\n e.preventDefault();\n };\n\n this._configs = configs;\n\n /**\n * Set true to schedule picking of an Entity.\n * @type {boolean}\n */\n this.schedulePickEntity = false;\n\n /**\n * Set true to schedule picking of a position on teh surface of an Entity.\n * @type {boolean}\n */\n this.schedulePickSurface = false;\n\n /**\n * Set true to schedule snap-picking with surface picking as a fallback - used for measurement.\n * @type {boolean}\n */\n this.scheduleSnapOrPick = false;\n\n /**\n * The canvas position at which to do the next scheduled pick.\n * @type {Number[]}\n */\n this.pickCursorPos = math.vec2();\n\n /**\n * Will be true after picking to indicate that something was picked.\n * @type {boolean}\n */\n this.picked = false;\n\n /**\n * Will be true after picking to indicate that a position on the surface of an Entity was picked.\n * @type {boolean}\n */\n this.pickedSurface = false;\n\n /**\n * Will hold the PickResult after after picking.\n * @type {PickResult}\n */\n this.pickResult = null;\n\n this._lastPickedEntityId = null;\n\n this._lastHash = null;\n\n this._needFireEvents = 0;\n }\n\n /**\n * Immediately attempts a pick, if scheduled.\n */\n update() {\n\n if (!this._configs.pointerEnabled) {\n return;\n }\n\n if (!this.schedulePickEntity && !this.schedulePickSurface) {\n return;\n }\n\n const hash = `${~~this.pickCursorPos[0]}-${~~this.pickCursorPos[1]}-${this.scheduleSnapOrPick}-${this.schedulePickSurface}-${this.schedulePickEntity}`;\n if (this._lastHash === hash) {\n return;\n }\n\n this.picked = false;\n this.pickedSurface = false;\n this.snappedOrPicked = false;\n this.hoveredSnappedOrSurfaceOff = false;\n\n const hasHoverSurfaceSubs = this._cameraControl.hasSubs(\"hoverSurface\");\n\n if (this.scheduleSnapOrPick) {\n const snapPickResult = this._scene.pick({\n canvasPos: this.pickCursorPos,\n snapRadius: this._configs.snapRadius,\n snapToVertex: this._configs.snapToVertex,\n snapToEdge: this._configs.snapToEdge,\n });\n if (snapPickResult && (snapPickResult.snappedToEdge || snapPickResult.snappedToVertex)) {\n this.snapPickResult = snapPickResult;\n this.snappedOrPicked = true;\n this._needFireEvents++;\n } else {\n this.schedulePickSurface = true; // Fallback\n this.snapPickResult = null;\n }\n }\n\n if (this.schedulePickSurface) {\n if (this.pickResult && this.pickResult.worldPos) {\n const pickResultCanvasPos = this.pickResult.canvasPos;\n if (pickResultCanvasPos[0] === this.pickCursorPos[0] && pickResultCanvasPos[1] === this.pickCursorPos[1]) {\n this.picked = true;\n this.pickedSurface = true;\n this._needFireEvents += hasHoverSurfaceSubs ? 1 : 0;\n this.schedulePickEntity = false;\n this.schedulePickSurface = false;\n if (this.scheduleSnapOrPick) {\n this.snappedOrPicked = true;\n } else {\n this.hoveredSnappedOrSurfaceOff = true;\n }\n this.scheduleSnapOrPick = false;\n return;\n }\n }\n }\n\n if (this.schedulePickEntity) {\n if (this.pickResult && (this.pickResult.canvasPos || this.pickResult.snappedCanvasPos)) {\n const pickResultCanvasPos = this.pickResult.canvasPos || this.pickResult.snappedCanvasPos;\n if (pickResultCanvasPos[0] === this.pickCursorPos[0] && pickResultCanvasPos[1] === this.pickCursorPos[1]) {\n this.picked = true;\n this.pickedSurface = false;\n this.schedulePickEntity = false;\n this.schedulePickSurface = false;\n return;\n }\n }\n }\n\n if (this.schedulePickSurface || (this.scheduleSnapOrPick && !this.snapPickResult)) {\n this.pickResult = this._scene.pick({\n pickSurface: true,\n pickSurfaceNormal: false,\n canvasPos: this.pickCursorPos\n });\n if (this.pickResult) {\n this.picked = true;\n if (this.scheduleSnapOrPick) {\n this.snappedOrPicked = true;\n } else {\n this.pickedSurface = true;\n }\n this._needFireEvents++;\n } else if (this.scheduleSnapOrPick) {\n this.hoveredSnappedOrSurfaceOff = true;\n this._needFireEvents++;\n }\n\n } else { // schedulePickEntity == true\n\n this.pickResult = this._scene.pick({\n canvasPos: this.pickCursorPos\n });\n\n if (this.pickResult) {\n this.picked = true;\n this.pickedSurface = false;\n this._needFireEvents++;\n }\n }\n\n this.scheduleSnapOrPick = false;\n this.schedulePickEntity = false;\n this.schedulePickSurface = false;\n }\n\n fireEvents() {\n\n if (this._needFireEvents === 0) {\n return;\n }\n\n if (this.hoveredSnappedOrSurfaceOff) {\n this._cameraControl.fire(\"hoverSnapOrSurfaceOff\", {\n canvasPos: this.pickCursorPos,\n pointerPos : this.pickCursorPos\n }, true);\n }\n\n if (this.snappedOrPicked) {\n if (this.snapPickResult) {\n const pickResult = new PickResult();\n pickResult.entity = this.snapPickResult.entity;\n pickResult.snappedToVertex = this.snapPickResult.snappedToVertex;\n pickResult.snappedToEdge = this.snapPickResult.snappedToEdge;\n pickResult.worldPos = this.snapPickResult.worldPos;\n pickResult.canvasPos = this.pickCursorPos;\n pickResult.snappedCanvasPos = this.snapPickResult.snappedCanvasPos;\n this._cameraControl.fire(\"hoverSnapOrSurface\", pickResult, true);\n this.snapPickResult = null;\n } else {\n this._cameraControl.fire(\"hoverSnapOrSurface\", this.pickResult, true);\n }\n }\n\n if (this.picked && this.pickResult && (this.pickResult.entity || this.pickResult.worldPos)) {\n\n if (this.pickResult.entity) {\n\n const pickedEntityId = this.pickResult.entity.id;\n\n if (this._lastPickedEntityId !== pickedEntityId) {\n\n if (this._lastPickedEntityId !== undefined) {\n this._cameraControl.fire(\"hoverOut\", {\n entity: this._scene.objects[this._lastPickedEntityId]\n }, true);\n }\n\n this._cameraControl.fire(\"hoverEnter\", this.pickResult, true);\n this._lastPickedEntityId = pickedEntityId;\n }\n }\n\n this._cameraControl.fire(\"hover\", this.pickResult, true);\n\n if (this.pickResult.worldPos) {\n this.pickedSurface = true;\n this._cameraControl.fire(\"hoverSurface\", this.pickResult, true);\n }\n\n } else {\n\n if (this._lastPickedEntityId !== undefined) {\n this._cameraControl.fire(\"hoverOut\", {\n entity: this._scene.objects[this._lastPickedEntityId]\n }, true);\n this._lastPickedEntityId = undefined;\n }\n\n this._cameraControl.fire(\"hoverOff\", {\n canvasPos: this.pickCursorPos\n }, true);\n }\n\n this.pickResult = null;\n\n this._needFireEvents = 0;\n }\n}\n\n/**\n * @private\n */\n\nconst canvasPos = math.vec2();\n\nconst getCanvasPosFromEvent$3 = function (event, canvasPos) {\n if (!event) {\n event = window.event;\n canvasPos[0] = event.x;\n canvasPos[1] = event.y;\n } else {\n let element = event.target;\n let totalOffsetLeft = 0;\n let totalOffsetTop = 0;\n let totalScrollX = 0;\n let totalScrollY = 0;\n while (element.offsetParent) {\n totalOffsetLeft += element.offsetLeft;\n totalOffsetTop += element.offsetTop;\n totalScrollX += element.scrollLeft;\n totalScrollY += element.scrollTop;\n element = element.offsetParent;\n }\n canvasPos[0] = event.pageX + totalScrollX - totalOffsetLeft;\n canvasPos[1] = event.pageY + totalScrollY - totalOffsetTop;\n }\n return canvasPos;\n};\n\n/**\n * @private\n */\nclass MousePanRotateDollyHandler {\n\n constructor(scene, controllers, configs, states, updates) {\n\n this._scene = scene;\n\n const pickController = controllers.pickController;\n\n let lastX = 0;\n let lastY = 0;\n let lastXDown = 0;\n let lastYDown = 0;\n\n let mouseDownLeft;\n let mouseDownMiddle;\n let mouseDownRight;\n\n let mouseDownPicked = false;\n const pickedWorldPos = math.vec3();\n\n let mouseMovedOnCanvasSinceLastWheel = true;\n\n const canvas = this._scene.canvas.canvas;\n\n const keyDown = [];\n\n document.addEventListener(\"keydown\", this._documentKeyDownHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled) || (!scene.input.keyboardEnabled)) {\n return;\n }\n const keyCode = e.keyCode;\n keyDown[keyCode] = true;\n });\n\n document.addEventListener(\"keyup\", this._documentKeyUpHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled) || (!scene.input.keyboardEnabled)) {\n return;\n }\n const keyCode = e.keyCode;\n keyDown[keyCode] = false;\n });\n\n function setMousedownState(pick = true) {\n canvas.style.cursor = \"move\";\n setMousedownPositions();\n if (pick) {\n setMousedownPick();\n }\n }\n\n function setMousedownPositions() {\n\n lastX = states.pointerCanvasPos[0];\n lastY = states.pointerCanvasPos[1];\n lastXDown = states.pointerCanvasPos[0];\n lastYDown = states.pointerCanvasPos[1];\n }\n\n function setMousedownPick() {\n pickController.pickCursorPos = states.pointerCanvasPos;\n pickController.schedulePickSurface = true;\n pickController.update();\n\n if (pickController.picked && pickController.pickedSurface && pickController.pickResult && pickController.pickResult.worldPos) {\n mouseDownPicked = true;\n pickedWorldPos.set(pickController.pickResult.worldPos);\n } else {\n mouseDownPicked = false;\n }\n }\n\n canvas.addEventListener(\"mousedown\", this._mouseDownHandler = (e) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n switch (e.which) {\n\n case 1: // Left button\n\n if (keyDown[scene.input.KEY_SHIFT] || configs.planView) {\n\n mouseDownLeft = true;\n\n setMousedownState();\n\n } else {\n\n mouseDownLeft = true;\n\n setMousedownState(false);\n }\n\n break;\n\n case 2: // Middle/both buttons\n\n mouseDownMiddle = true;\n\n setMousedownState();\n\n break;\n\n case 3: // Right button\n\n mouseDownRight = true;\n\n if (configs.panRightClick) {\n\n setMousedownState();\n }\n\n break;\n }\n });\n\n document.addEventListener(\"mousemove\", this._documentMouseMoveHandler = (e) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n if (!mouseDownLeft && !mouseDownMiddle && !mouseDownRight) {\n return;\n }\n\n // Scaling drag-rotate to canvas boundary\n\n const canvasBoundary = scene.canvas.boundary;\n\n const canvasWidth = canvasBoundary[2];\n const canvasHeight = canvasBoundary[3];\n const x = states.pointerCanvasPos[0];\n const y = states.pointerCanvasPos[1];\n\n const panning = keyDown[scene.input.KEY_SHIFT] || configs.planView || (!configs.panRightClick && mouseDownMiddle) || (configs.panRightClick && mouseDownRight);\n\n const xDelta = document.pointerLockElement ? e.movementX : (x - lastX);\n const yDelta = document.pointerLockElement ? e.movementY : (y - lastY);\n\n if (panning) {\n\n const camera = scene.camera;\n\n // We use only canvasHeight here so that aspect ratio does not distort speed\n\n if (camera.projection === \"perspective\") {\n\n const depth = Math.abs(mouseDownPicked ? math.lenVec3(math.subVec3(pickedWorldPos, scene.camera.eye, [])) : scene.camera.eyeLookDist);\n const targetDistance = depth * Math.tan((camera.perspective.fov / 2) * Math.PI / 180.0);\n\n updates.panDeltaX += (1.5 * xDelta * targetDistance / canvasHeight);\n updates.panDeltaY += (1.5 * yDelta * targetDistance / canvasHeight);\n\n } else {\n\n updates.panDeltaX += 0.5 * camera.ortho.scale * (xDelta / canvasHeight);\n updates.panDeltaY += 0.5 * camera.ortho.scale * (yDelta / canvasHeight);\n }\n\n } else if (mouseDownLeft && !mouseDownMiddle && !mouseDownRight) {\n\n if (!configs.planView) { // No rotating in plan-view mode\n\n if (configs.firstPerson) {\n updates.rotateDeltaY -= (xDelta / canvasWidth) * configs.dragRotationRate / 2;\n updates.rotateDeltaX += (yDelta / canvasHeight) * (configs.dragRotationRate / 4);\n\n } else {\n updates.rotateDeltaY -= (xDelta / canvasWidth) * (configs.dragRotationRate * 1.5);\n updates.rotateDeltaX += (yDelta / canvasHeight) * (configs.dragRotationRate * 1.5);\n }\n }\n }\n\n lastX = x;\n lastY = y;\n });\n\n canvas.addEventListener(\"mousemove\", this._canvasMouseMoveHandler = (e) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n if (!states.mouseover) {\n return;\n }\n\n mouseMovedOnCanvasSinceLastWheel = true;\n });\n\n document.addEventListener(\"mouseup\", this._documentMouseUpHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n switch (e.which) {\n case 1: // Left button\n mouseDownLeft = false;\n mouseDownMiddle = false;\n mouseDownRight = false;\n break;\n case 2: // Middle/both buttons\n mouseDownLeft = false;\n mouseDownMiddle = false;\n mouseDownRight = false;\n break;\n case 3: // Right button\n mouseDownLeft = false;\n mouseDownMiddle = false;\n mouseDownRight = false;\n break;\n }\n });\n\n canvas.addEventListener(\"mouseup\", this._mouseUpHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n switch (e.which) {\n case 3: // Right button\n getCanvasPosFromEvent$3(e, canvasPos);\n const x = canvasPos[0];\n const y = canvasPos[1];\n if (Math.abs(x - lastXDown) < 3 && Math.abs(y - lastYDown) < 3) {\n controllers.cameraControl.fire(\"rightClick\", { // For context menus\n pagePos: [Math.round(e.pageX), Math.round(e.pageY)],\n canvasPos: canvasPos,\n event: e\n }, true);\n }\n break;\n }\n canvas.style.removeProperty(\"cursor\");\n });\n\n canvas.addEventListener(\"mouseenter\", this._mouseEnterHandler = () => {\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n });\n\n const maxElapsed = 1 / 20;\n const minElapsed = 1 / 60;\n\n let secsNowLast = null;\n\n canvas.addEventListener(\"wheel\", this._mouseWheelHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n const secsNow = performance.now() / 1000.0;\n var secsElapsed = (secsNowLast !== null) ? (secsNow - secsNowLast) : 0;\n secsNowLast = secsNow;\n if (secsElapsed > maxElapsed) {\n secsElapsed = maxElapsed;\n }\n if (secsElapsed < minElapsed) {\n secsElapsed = minElapsed;\n }\n const delta = Math.max(-1, Math.min(1, -e.deltaY * 40));\n if (delta === 0) {\n return;\n }\n const normalizedDelta = delta / Math.abs(delta);\n updates.dollyDelta += -normalizedDelta * secsElapsed * configs.mouseWheelDollyRate;\n\n if (mouseMovedOnCanvasSinceLastWheel) {\n states.followPointerDirty = true;\n mouseMovedOnCanvasSinceLastWheel = false;\n }\n\n }, {passive: true});\n }\n\n reset() {\n }\n\n destroy() {\n\n const canvas = this._scene.canvas.canvas;\n\n document.removeEventListener(\"keydown\", this._documentKeyDownHandler);\n document.removeEventListener(\"keyup\", this._documentKeyUpHandler);\n canvas.removeEventListener(\"mousedown\", this._mouseDownHandler);\n document.removeEventListener(\"mousemove\", this._documentMouseMoveHandler);\n canvas.removeEventListener(\"mousemove\", this._canvasMouseMoveHandler);\n document.removeEventListener(\"mouseup\", this._documentMouseUpHandler);\n canvas.removeEventListener(\"mouseup\", this._mouseUpHandler);\n canvas.removeEventListener(\"mouseenter\", this._mouseEnterHandler);\n canvas.removeEventListener(\"wheel\", this._mouseWheelHandler);\n }\n}\n\nconst center = math.vec3();\nconst tempVec3a$5 = math.vec3();\nconst tempVec3b$2 = math.vec3();\nconst tempVec3c$1 = math.vec3();\nconst tempVec3d = math.vec3();\n\nconst tempCameraTarget = {\n eye: math.vec3(),\n look: math.vec3(),\n up: math.vec3()\n};\n\n/**\n * @private\n */\nclass KeyboardAxisViewHandler {\n\n constructor(scene, controllers, configs, states) {\n\n this._scene = scene;\n const cameraControl = controllers.cameraControl;\n const camera = scene.camera;\n\n this._onSceneKeyDown = scene.input.on(\"keydown\", () => {\n\n if (!(configs.active && configs.pointerEnabled) || (!scene.input.keyboardEnabled)) {\n return;\n }\n\n if (!states.mouseover) {\n return;\n }\n\n const axisViewRight = cameraControl._isKeyDownForAction(cameraControl.AXIS_VIEW_RIGHT);\n const axisViewBack = cameraControl._isKeyDownForAction(cameraControl.AXIS_VIEW_BACK);\n const axisViewLeft = cameraControl._isKeyDownForAction(cameraControl.AXIS_VIEW_LEFT);\n const axisViewFront = cameraControl._isKeyDownForAction(cameraControl.AXIS_VIEW_FRONT);\n const axisViewTop = cameraControl._isKeyDownForAction(cameraControl.AXIS_VIEW_TOP);\n const axisViewBottom = cameraControl._isKeyDownForAction(cameraControl.AXIS_VIEW_BOTTOM);\n\n if ((!axisViewRight) && (!axisViewBack) && (!axisViewLeft) && (!axisViewFront) && (!axisViewTop) && (!axisViewBottom)) {\n return;\n }\n\n const aabb = scene.aabb;\n const diag = math.getAABB3Diag(aabb);\n\n math.getAABB3Center(aabb, center);\n\n const perspectiveDist = Math.abs(diag / Math.tan(controllers.cameraFlight.fitFOV * math.DEGTORAD));\n const orthoScale = diag * 1.1;\n\n tempCameraTarget.orthoScale = orthoScale;\n\n if (axisViewRight) {\n\n tempCameraTarget.eye.set(math.addVec3(center, math.mulVec3Scalar(camera.worldRight, perspectiveDist, tempVec3a$5), tempVec3d));\n tempCameraTarget.look.set(center);\n tempCameraTarget.up.set(camera.worldUp);\n\n } else if (axisViewBack) {\n\n tempCameraTarget.eye.set(math.addVec3(center, math.mulVec3Scalar(camera.worldForward, perspectiveDist, tempVec3a$5), tempVec3d));\n tempCameraTarget.look.set(center);\n tempCameraTarget.up.set(camera.worldUp);\n\n } else if (axisViewLeft) {\n\n tempCameraTarget.eye.set(math.addVec3(center, math.mulVec3Scalar(camera.worldRight, -perspectiveDist, tempVec3a$5), tempVec3d));\n tempCameraTarget.look.set(center);\n tempCameraTarget.up.set(camera.worldUp);\n\n } else if (axisViewFront) {\n\n tempCameraTarget.eye.set(math.addVec3(center, math.mulVec3Scalar(camera.worldForward, -perspectiveDist, tempVec3a$5), tempVec3d));\n tempCameraTarget.look.set(center);\n tempCameraTarget.up.set(camera.worldUp);\n\n } else if (axisViewTop) {\n\n tempCameraTarget.eye.set(math.addVec3(center, math.mulVec3Scalar(camera.worldUp, perspectiveDist, tempVec3a$5), tempVec3d));\n tempCameraTarget.look.set(center);\n tempCameraTarget.up.set(math.normalizeVec3(math.mulVec3Scalar(camera.worldForward, 1, tempVec3b$2), tempVec3c$1));\n\n } else if (axisViewBottom) {\n\n tempCameraTarget.eye.set(math.addVec3(center, math.mulVec3Scalar(camera.worldUp, -perspectiveDist, tempVec3a$5), tempVec3d));\n tempCameraTarget.look.set(center);\n tempCameraTarget.up.set(math.normalizeVec3(math.mulVec3Scalar(camera.worldForward, -1, tempVec3b$2)));\n }\n\n if ((!configs.firstPerson) && configs.followPointer) {\n controllers.pivotController.setPivotPos(center);\n }\n\n if (controllers.cameraFlight.duration > 0) {\n controllers.cameraFlight.flyTo(tempCameraTarget, () => {\n if (controllers.pivotController.getPivoting() && configs.followPointer) {\n controllers.pivotController.showPivot();\n }\n });\n\n } else {\n controllers.cameraFlight.jumpTo(tempCameraTarget);\n if (controllers.pivotController.getPivoting() && configs.followPointer) {\n controllers.pivotController.showPivot();\n }\n }\n });\n }\n\n reset() {\n }\n\n destroy() {\n this._scene.input.off(this._onSceneKeyDown);\n }\n}\n\n/**\n * @private\n */\nclass MousePickHandler {\n\n constructor(scene, controllers, configs, states, updates) {\n\n this._scene = scene;\n\n const pickController = controllers.pickController;\n const pivotController = controllers.pivotController;\n const cameraControl = controllers.cameraControl;\n\n this._clicks = 0;\n this._timeout = null;\n this._lastPickedEntityId = null;\n\n let leftDown = false;\n let rightDown = false;\n\n const canvas = this._scene.canvas.canvas;\n\n const flyCameraTo = (pickResult) => {\n let pos;\n if (pickResult && pickResult.worldPos) {\n pos = pickResult.worldPos;\n }\n const aabb = pickResult && pickResult.entity ? pickResult.entity.aabb : scene.aabb;\n if (pos) { // Fly to look at point, don't change eye->look dist\n const camera = scene.camera;\n math.subVec3(camera.eye, camera.look, []);\n controllers.cameraFlight.flyTo({\n // look: pos,\n // eye: xeokit.math.addVec3(pos, diff, []),\n // up: camera.up,\n aabb: aabb\n });\n // TODO: Option to back off to fit AABB in view\n } else {// Fly to fit target boundary in view\n controllers.cameraFlight.flyTo({\n aabb: aabb\n });\n }\n };\n\n const tickifiedMouseMoveFn = scene.tickify (\n this._canvasMouseMoveHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n if (leftDown || rightDown) {\n return;\n }\n\n const hoverSubs = cameraControl.hasSubs(\"hover\");\n const hoverEnterSubs = cameraControl.hasSubs(\"hoverEnter\");\n const hoverOutSubs = cameraControl.hasSubs(\"hoverOut\");\n const hoverOffSubs = cameraControl.hasSubs(\"hoverOff\");\n const hoverSurfaceSubs = cameraControl.hasSubs(\"hoverSurface\");\n const hoverSnapOrSurfaceSubs = cameraControl.hasSubs(\"hoverSnapOrSurface\");\n\n if (hoverSubs || hoverEnterSubs || hoverOutSubs || hoverOffSubs || hoverSurfaceSubs || hoverSnapOrSurfaceSubs) {\n\n pickController.pickCursorPos = states.pointerCanvasPos;\n pickController.schedulePickEntity = true;\n pickController.schedulePickSurface = hoverSurfaceSubs;\n pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs;\n\n pickController.update();\n\n if (pickController.pickResult) {\n\n if (pickController.pickResult.entity) {\n const pickedEntityId = pickController.pickResult.entity.id;\n\n if (this._lastPickedEntityId !== pickedEntityId) {\n\n if (this._lastPickedEntityId !== undefined) {\n\n cameraControl.fire(\"hoverOut\", { // Hovered off an entity\n entity: scene.objects[this._lastPickedEntityId]\n }, true);\n }\n\n cameraControl.fire(\"hoverEnter\", pickController.pickResult, true); // Hovering over a new entity\n\n this._lastPickedEntityId = pickedEntityId;\n }\n }\n\n cameraControl.fire(\"hover\", pickController.pickResult, true);\n\n if (pickController.pickResult.worldPos || pickController.pickResult.snappedWorldPos) { // Hovering the surface of an entity\n cameraControl.fire(\"hoverSurface\", pickController.pickResult, true);\n }\n\n } else {\n\n if (this._lastPickedEntityId !== undefined) {\n\n cameraControl.fire(\"hoverOut\", { // Hovered off an entity\n entity: scene.objects[this._lastPickedEntityId]\n }, true);\n\n this._lastPickedEntityId = undefined;\n }\n\n cameraControl.fire(\"hoverOff\", { // Not hovering on any entity\n canvasPos: pickController.pickCursorPos\n }, true);\n }\n }\n }\n );\n\n canvas.addEventListener(\"mousemove\", tickifiedMouseMoveFn);\n\n canvas.addEventListener('mousedown', this._canvasMouseDownHandler = (e) => {\n\n if (e.which === 1) {\n leftDown = true;\n }\n\n if (e.which === 3) {\n rightDown = true;\n }\n\n const leftButtonDown = (e.which === 1);\n\n if (!leftButtonDown) {\n return;\n }\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n // Left mouse button down to start pivoting\n\n states.mouseDownClientX = e.clientX;\n states.mouseDownClientY = e.clientY;\n states.mouseDownCursorX = states.pointerCanvasPos[0];\n states.mouseDownCursorY = states.pointerCanvasPos[1];\n\n if ((!configs.firstPerson) && configs.followPointer) {\n\n pickController.pickCursorPos = states.pointerCanvasPos;\n pickController.schedulePickSurface = true;\n\n pickController.update();\n\n if (e.which === 1) {// Left button\n const pickResult = pickController.pickResult;\n if (pickResult && pickResult.worldPos) {\n pivotController.setPivotPos(pickResult.worldPos);\n pivotController.startPivot();\n } else {\n if (configs.smartPivot) {\n pivotController.setCanvasPivotPos(states.pointerCanvasPos);\n } else {\n pivotController.setPivotPos(scene.camera.look);\n }\n pivotController.startPivot();\n }\n }\n }\n });\n\n document.addEventListener('mouseup', this._documentMouseUpHandler = (e) => {\n\n if (e.which === 1) {\n leftDown = false;\n }\n\n if (e.which === 3) {\n rightDown = false;\n }\n\n if (pivotController.getPivoting()) {\n pivotController.endPivot();\n }\n });\n\n canvas.addEventListener('mouseup', this._canvasMouseUpHandler = (e) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n const leftButtonUp = (e.which === 1);\n\n if (!leftButtonUp) {\n return;\n }\n\n // Left mouse button up to possibly pick or double-pick\n\n pivotController.hidePivot();\n\n if (Math.abs(e.clientX - states.mouseDownClientX) > 3 || Math.abs(e.clientY - states.mouseDownClientY) > 3) {\n return;\n }\n\n const pickedSubs = cameraControl.hasSubs(\"picked\");\n const pickedNothingSubs = cameraControl.hasSubs(\"pickedNothing\");\n const pickedSurfaceSubs = cameraControl.hasSubs(\"pickedSurface\");\n const doublePickedSubs = cameraControl.hasSubs(\"doublePicked\");\n const doublePickedSurfaceSubs = cameraControl.hasSubs(\"doublePickedSurface\");\n const doublePickedNothingSubs = cameraControl.hasSubs(\"doublePickedNothing\");\n\n if ((!configs.doublePickFlyTo) &&\n (!doublePickedSubs) &&\n (!doublePickedSurfaceSubs) &&\n (!doublePickedNothingSubs)) {\n\n // Avoid the single/double click differentiation timeout\n\n if (pickedSubs || pickedNothingSubs || pickedSurfaceSubs) {\n\n pickController.pickCursorPos = states.pointerCanvasPos;\n pickController.schedulePickEntity = true;\n pickController.schedulePickSurface = pickedSurfaceSubs;\n pickController.update();\n\n if (pickController.pickResult) {\n\n cameraControl.fire(\"picked\", pickController.pickResult, true);\n\n if (pickController.pickedSurface) {\n cameraControl.fire(\"pickedSurface\", pickController.pickResult, true);\n }\n } else {\n cameraControl.fire(\"pickedNothing\", {\n canvasPos: states.pointerCanvasPos\n }, true);\n }\n }\n\n this._clicks = 0;\n\n return;\n }\n\n this._clicks++;\n\n if (this._clicks === 1) { // First click\n\n pickController.pickCursorPos = states.pointerCanvasPos;\n pickController.schedulePickEntity = configs.doublePickFlyTo;\n pickController.schedulePickSurface = pickedSurfaceSubs;\n pickController.update();\n\n const firstClickPickResult = pickController.pickResult;\n const firstClickPickSurface = pickController.pickedSurface;\n\n this._timeout = setTimeout(() => {\n\n if (firstClickPickResult) {\n\n cameraControl.fire(\"picked\", firstClickPickResult, true);\n\n if (firstClickPickSurface) {\n\n cameraControl.fire(\"pickedSurface\", firstClickPickResult, true);\n\n if ((!configs.firstPerson) && configs.followPointer) {\n controllers.pivotController.setPivotPos(firstClickPickResult.worldPos);\n if (controllers.pivotController.startPivot()) {\n controllers.pivotController.showPivot();\n }\n }\n }\n } else {\n cameraControl.fire(\"pickedNothing\", {\n canvasPos: states.pointerCanvasPos\n }, true);\n }\n\n this._clicks = 0;\n\n }, configs.doubleClickTimeFrame);\n\n } else { // Second click\n\n if (this._timeout !== null) {\n window.clearTimeout(this._timeout);\n this._timeout = null;\n }\n\n pickController.pickCursorPos = states.pointerCanvasPos;\n pickController.schedulePickEntity = configs.doublePickFlyTo || doublePickedSubs || doublePickedSurfaceSubs;\n pickController.schedulePickSurface = pickController.schedulePickEntity && doublePickedSurfaceSubs;\n pickController.update();\n\n if (pickController.pickResult) {\n\n cameraControl.fire(\"doublePicked\", pickController.pickResult, true);\n\n if (pickController.pickedSurface) {\n cameraControl.fire(\"doublePickedSurface\", pickController.pickResult, true);\n }\n\n if (configs.doublePickFlyTo) {\n\n flyCameraTo(pickController.pickResult);\n\n if ((!configs.firstPerson) && configs.followPointer) {\n\n const pickedEntityAABB = pickController.pickResult.entity.aabb;\n const pickedEntityCenterPos = math.getAABB3Center(pickedEntityAABB);\n\n controllers.pivotController.setPivotPos(pickedEntityCenterPos);\n\n if (controllers.pivotController.startPivot()) {\n controllers.pivotController.showPivot();\n }\n }\n }\n\n } else {\n\n cameraControl.fire(\"doublePickedNothing\", {\n canvasPos: states.pointerCanvasPos\n }, true);\n\n if (configs.doublePickFlyTo) {\n\n flyCameraTo();\n\n if ((!configs.firstPerson) && configs.followPointer) {\n\n const sceneAABB = scene.aabb;\n const sceneCenterPos = math.getAABB3Center(sceneAABB);\n\n controllers.pivotController.setPivotPos(sceneCenterPos);\n\n if (controllers.pivotController.startPivot()) {\n controllers.pivotController.showPivot();\n }\n }\n }\n }\n\n this._clicks = 0;\n }\n }, false);\n }\n\n reset() {\n this._clicks = 0;\n this._lastPickedEntityId = null;\n if (this._timeout) {\n window.clearTimeout(this._timeout);\n this._timeout = null;\n }\n }\n\n destroy() {\n const canvas = this._scene.canvas.canvas;\n canvas.removeEventListener(\"mousemove\", this._canvasMouseMoveHandler);\n canvas.removeEventListener(\"mousedown\", this._canvasMouseDownHandler);\n document.removeEventListener(\"mouseup\", this._documentMouseUpHandler);\n canvas.removeEventListener(\"mouseup\", this._canvasMouseUpHandler);\n if (this._timeout) {\n window.clearTimeout(this._timeout);\n this._timeout = null;\n }\n }\n}\n\n/**\n * @private\n */\nclass KeyboardPanRotateDollyHandler {\n\n constructor(scene, controllers, configs, states, updates) {\n\n this._scene = scene;\n const input = scene.input;\n\n const keyDownMap = [];\n\n const canvas = scene.canvas.canvas;\n\n let mouseMovedSinceLastKeyboardDolly = true;\n\n this._onSceneMouseMove = input.on(\"mousemove\", () => {\n mouseMovedSinceLastKeyboardDolly = true;\n });\n\n this._onSceneKeyDown = input.on(\"keydown\", (keyCode) => {\n if (!(configs.active && configs.pointerEnabled) || (!scene.input.keyboardEnabled)) {\n return;\n }\n if (!states.mouseover) {\n return;\n }\n keyDownMap[keyCode] = true;\n\n if (keyCode === input.KEY_SHIFT) {\n canvas.style.cursor = \"move\";\n }\n });\n\n this._onSceneKeyUp = input.on(\"keyup\", (keyCode) => {\n if (!(configs.active && configs.pointerEnabled) || (!scene.input.keyboardEnabled)) {\n return;\n }\n keyDownMap[keyCode] = false;\n\n if (keyCode === input.KEY_SHIFT) {\n canvas.style.cursor = null;\n }\n\n if (controllers.pivotController.getPivoting()) {\n controllers.pivotController.endPivot();\n }\n });\n\n this._onTick = scene.on(\"tick\", (e) => {\n\n if (!(configs.active && configs.pointerEnabled) || (!scene.input.keyboardEnabled)) {\n return;\n }\n\n if (!states.mouseover) {\n return;\n }\n\n const cameraControl = controllers.cameraControl;\n const elapsedSecs = (e.deltaTime / 1000.0);\n\n //-------------------------------------------------------------------------------------------------\n // Keyboard rotation\n //-------------------------------------------------------------------------------------------------\n\n if (!configs.planView) {\n\n const rotateYPos = cameraControl._isKeyDownForAction(cameraControl.ROTATE_Y_POS, keyDownMap);\n const rotateYNeg = cameraControl._isKeyDownForAction(cameraControl.ROTATE_Y_NEG, keyDownMap);\n const rotateXPos = cameraControl._isKeyDownForAction(cameraControl.ROTATE_X_POS, keyDownMap);\n const rotateXNeg = cameraControl._isKeyDownForAction(cameraControl.ROTATE_X_NEG, keyDownMap);\n\n const orbitDelta = elapsedSecs * configs.keyboardRotationRate;\n\n if (rotateYPos || rotateYNeg || rotateXPos || rotateXNeg) {\n\n if ((!configs.firstPerson) && configs.followPointer) {\n controllers.pivotController.startPivot();\n }\n\n if (rotateYPos) {\n updates.rotateDeltaY += orbitDelta;\n\n } else if (rotateYNeg) {\n updates.rotateDeltaY -= orbitDelta;\n }\n\n if (rotateXPos) {\n updates.rotateDeltaX += orbitDelta;\n\n } else if (rotateXNeg) {\n updates.rotateDeltaX -= orbitDelta;\n }\n\n if ((!configs.firstPerson) && configs.followPointer) {\n controllers.pivotController.startPivot();\n }\n }\n }\n\n //-------------------------------------------------------------------------------------------------\n // Keyboard panning\n //-------------------------------------------------------------------------------------------------\n\n if (!keyDownMap[input.KEY_CTRL] && !keyDownMap[input.KEY_ALT]) {\n\n const dollyBackwards = cameraControl._isKeyDownForAction(cameraControl.DOLLY_BACKWARDS, keyDownMap);\n const dollyForwards = cameraControl._isKeyDownForAction(cameraControl.DOLLY_FORWARDS, keyDownMap);\n\n if (dollyBackwards || dollyForwards) {\n\n const dollyDelta = elapsedSecs * configs.keyboardDollyRate;\n\n if ((!configs.firstPerson) && configs.followPointer) {\n controllers.pivotController.startPivot();\n }\n if (dollyForwards) {\n updates.dollyDelta -= dollyDelta;\n } else if (dollyBackwards) {\n updates.dollyDelta += dollyDelta;\n }\n\n if (mouseMovedSinceLastKeyboardDolly) {\n states.followPointerDirty = true;\n mouseMovedSinceLastKeyboardDolly = false;\n }\n }\n }\n\n const panForwards = cameraControl._isKeyDownForAction(cameraControl.PAN_FORWARDS, keyDownMap);\n const panBackwards = cameraControl._isKeyDownForAction(cameraControl.PAN_BACKWARDS, keyDownMap);\n const panLeft = cameraControl._isKeyDownForAction(cameraControl.PAN_LEFT, keyDownMap);\n const panRight = cameraControl._isKeyDownForAction(cameraControl.PAN_RIGHT, keyDownMap);\n const panUp = cameraControl._isKeyDownForAction(cameraControl.PAN_UP, keyDownMap);\n const panDown = cameraControl._isKeyDownForAction(cameraControl.PAN_DOWN, keyDownMap);\n\n const panDelta = (keyDownMap[input.KEY_ALT] ? 0.3 : 1.0) * elapsedSecs * configs.keyboardPanRate; // ALT for slower pan rate\n\n if (panForwards || panBackwards || panLeft || panRight || panUp || panDown) {\n\n if ((!configs.firstPerson) && configs.followPointer) {\n controllers.pivotController.startPivot();\n }\n\n if (panDown) {\n updates.panDeltaY += panDelta;\n\n } else if (panUp) {\n updates.panDeltaY += -panDelta;\n }\n\n if (panRight) {\n updates.panDeltaX += -panDelta;\n\n } else if (panLeft) {\n updates.panDeltaX += panDelta;\n }\n\n if (panBackwards) {\n updates.panDeltaZ += panDelta;\n\n } else if (panForwards) {\n updates.panDeltaZ += -panDelta;\n }\n }\n });\n }\n\n reset() {\n }\n\n destroy() {\n\n this._scene.off(this._onTick);\n\n this._scene.input.off(this._onSceneMouseMove);\n this._scene.input.off(this._onSceneKeyDown);\n this._scene.input.off(this._onSceneKeyUp);\n }\n}\n\nconst SCALE_DOLLY_EACH_FRAME = 1; // Recalculate dolly speed for eye->target distance on each Nth frame\nconst EPSILON = 0.001;\nconst tempVec3$2 = math.vec3();\n\n/**\n * Handles camera updates on each \"tick\" that were scheduled by the various controllers.\n *\n * @private\n */\nclass CameraUpdater {\n\n constructor(scene, controllers, configs, states, updates) {\n\n this._scene = scene;\n const camera = scene.camera;\n const pickController = controllers.pickController;\n const pivotController = controllers.pivotController;\n const panController = controllers.panController;\n\n let countDown = SCALE_DOLLY_EACH_FRAME; // Decrements on each tick\n let dollyDistFactor = 1.0; // Calculated when countDown is zero\n let followPointerWorldPos = null; // Holds the pointer's World position when configs.followPointer is true\n \n this._onTick = scene.on(\"tick\", () => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n let cursorType = \"default\";\n\n //----------------------------------------------------------------------------------------------------------\n // Dolly decay\n //------------------------------------------------------------------------------------ ----------------------\n\n if (Math.abs(updates.dollyDelta) < EPSILON) {\n updates.dollyDelta = 0;\n }\n\n //----------------------------------------------------------------------------------------------------------\n // Rotation decay\n //----------------------------------------------------------------------------------------------------------\n\n if (Math.abs(updates.rotateDeltaX) < EPSILON) {\n updates.rotateDeltaX = 0;\n }\n\n if (Math.abs(updates.rotateDeltaY) < EPSILON) {\n updates.rotateDeltaY = 0;\n }\n\n if (updates.rotateDeltaX !== 0 || updates.rotateDeltaY !== 0) {\n updates.dollyDelta = 0;\n }\n\n //----------------------------------------------------------------------------------------------------------\n // Dolly speed eye->look scaling\n //\n // If pointer is over an object, then dolly speed is proportional to the distance to that object.\n //\n // If pointer is not over an object, then dolly speed is proportional to the distance to the last\n // object the pointer was over. This is so that we can dolly to structures that may have gaps through\n // which empty background shows, that the pointer may inadvertently be over. In these cases, we don't\n // want dolly speed wildly varying depending on how accurately the user avoids the gaps with the pointer.\n //----------------------------------------------------------------------------------------------------------\n\n if (configs.followPointer) {\n\n if (--countDown <= 0) {\n\n countDown = SCALE_DOLLY_EACH_FRAME;\n\n if (updates.dollyDelta !== 0) {\n if (updates.rotateDeltaY === 0 && updates.rotateDeltaX === 0) {\n\n if (configs.followPointer && states.followPointerDirty) {\n\n pickController.pickCursorPos = states.pointerCanvasPos;\n pickController.schedulePickSurface = true;\n pickController.update();\n\n if (pickController.pickResult && pickController.pickResult.worldPos) {\n followPointerWorldPos = pickController.pickResult.worldPos;\n \n } else {\n dollyDistFactor = 1.0;\n followPointerWorldPos = null;\n }\n\n states.followPointerDirty = false;\n }\n }\n\n if (followPointerWorldPos) {\n const dist = Math.abs(math.lenVec3(math.subVec3(followPointerWorldPos, scene.camera.eye, tempVec3$2)));\n dollyDistFactor = dist / configs.dollyProximityThreshold;\n }\n\n if (dollyDistFactor < configs.dollyMinSpeed) {\n dollyDistFactor = configs.dollyMinSpeed;\n }\n }\n }\n } else {\n dollyDistFactor = 1;\n followPointerWorldPos = null;\n }\n\n const dollyDeltaForDist = (updates.dollyDelta * dollyDistFactor);\n\n //----------------------------------------------------------------------------------------------------------\n // Rotation\n //----------------------------------------------------------------------------------------------------------\n\n if (updates.rotateDeltaY !== 0 || updates.rotateDeltaX !== 0) {\n\n if ((!configs.firstPerson) && configs.followPointer && pivotController.getPivoting()) {\n pivotController.continuePivot(updates.rotateDeltaY, updates.rotateDeltaX);\n pivotController.showPivot();\n\n } else {\n\n if (updates.rotateDeltaX !== 0) {\n if (configs.firstPerson) {\n camera.pitch(-updates.rotateDeltaX);\n } else {\n camera.orbitPitch(updates.rotateDeltaX);\n }\n }\n\n if (updates.rotateDeltaY !== 0) {\n if (configs.firstPerson) {\n camera.yaw(updates.rotateDeltaY);\n } else {\n camera.orbitYaw(updates.rotateDeltaY);\n }\n }\n }\n\n updates.rotateDeltaX *= configs.rotationInertia;\n updates.rotateDeltaY *= configs.rotationInertia;\n\n cursorType = \"grabbing\";\n }\n\n //----------------------------------------------------------------------------------------------------------\n // Panning\n //----------------------------------------------------------------------------------------------------------\n\n if (Math.abs(updates.panDeltaX) < EPSILON) {\n updates.panDeltaX = 0;\n }\n\n if (Math.abs(updates.panDeltaY) < EPSILON) {\n updates.panDeltaY = 0;\n }\n\n if (Math.abs(updates.panDeltaZ) < EPSILON) {\n updates.panDeltaZ = 0;\n }\n\n if (updates.panDeltaX !== 0 || updates.panDeltaY !== 0 || updates.panDeltaZ !== 0) {\n\n const vec = math.vec3();\n\n vec[0] = updates.panDeltaX;\n vec[1] = updates.panDeltaY;\n vec[2] = updates.panDeltaZ;\n\n let verticalEye;\n let verticalLook;\n\n if (configs.constrainVertical) {\n\n if (camera.xUp) {\n verticalEye = camera.eye[0];\n verticalLook = camera.look[0];\n } else if (camera.yUp) {\n verticalEye = camera.eye[1];\n verticalLook = camera.look[1];\n } else if (camera.zUp) {\n verticalEye = camera.eye[2];\n verticalLook = camera.look[2];\n }\n\n camera.pan(vec);\n\n const eye = camera.eye;\n const look = camera.look;\n\n if (camera.xUp) {\n eye[0] = verticalEye;\n look[0] = verticalLook;\n } else if (camera.yUp) {\n eye[1] = verticalEye;\n look[1] = verticalLook;\n } else if (camera.zUp) {\n eye[2] = verticalEye;\n look[2] = verticalLook;\n }\n\n camera.eye = eye;\n camera.look = look;\n\n } else {\n camera.pan(vec);\n }\n\n cursorType = \"grabbing\";\n }\n\n updates.panDeltaX *= configs.panInertia;\n updates.panDeltaY *= configs.panInertia;\n updates.panDeltaZ *= configs.panInertia;\n\n //----------------------------------------------------------------------------------------------------------\n // Dollying\n //----------------------------------------------------------------------------------------------------------\n\n if (dollyDeltaForDist !== 0) {\n\n if (dollyDeltaForDist < 0) {\n cursorType = \"zoom-in\";\n } else {\n cursorType = \"zoom-out\";\n }\n\n if (configs.firstPerson) {\n\n let verticalEye;\n let verticalLook;\n\n if (configs.constrainVertical) {\n if (camera.xUp) {\n verticalEye = camera.eye[0];\n verticalLook = camera.look[0];\n } else if (camera.yUp) {\n verticalEye = camera.eye[1];\n verticalLook = camera.look[1];\n } else if (camera.zUp) {\n verticalEye = camera.eye[2];\n verticalLook = camera.look[2];\n }\n }\n\n if (configs.followPointer) {\n const dolliedThroughSurface = panController.dollyToCanvasPos(followPointerWorldPos, states.pointerCanvasPos, -dollyDeltaForDist);\n if (dolliedThroughSurface) {\n states.followPointerDirty = true;\n }\n } else {\n camera.pan([0, 0, dollyDeltaForDist]);\n camera.ortho.scale = camera.ortho.scale - dollyDeltaForDist;\n }\n\n if (configs.constrainVertical) {\n const eye = camera.eye;\n const look = camera.look;\n if (camera.xUp) {\n eye[0] = verticalEye;\n look[0] = verticalLook;\n } else if (camera.yUp) {\n eye[1] = verticalEye;\n look[1] = verticalLook;\n } else if (camera.zUp) {\n eye[2] = verticalEye;\n look[2] = verticalLook;\n }\n camera.eye = eye;\n camera.look = look;\n }\n\n } else if (configs.planView) {\n\n if (configs.followPointer) {\n const dolliedThroughSurface = panController.dollyToCanvasPos(followPointerWorldPos, states.pointerCanvasPos, -dollyDeltaForDist);\n if (dolliedThroughSurface) {\n states.followPointerDirty = true;\n }\n } else {\n camera.ortho.scale = camera.ortho.scale + dollyDeltaForDist;\n camera.zoom(dollyDeltaForDist);\n }\n\n } else { // Orbiting\n\n if (configs.followPointer) {\n const dolliedThroughSurface = panController.dollyToCanvasPos(followPointerWorldPos, states.pointerCanvasPos, -dollyDeltaForDist);\n if (dolliedThroughSurface) {\n states.followPointerDirty = true;\n }\n } else {\n camera.ortho.scale = camera.ortho.scale + dollyDeltaForDist;\n camera.zoom(dollyDeltaForDist);\n }\n }\n\n updates.dollyDelta *= configs.dollyInertia;\n }\n\n pickController.fireEvents();\n\n document.body.style.cursor = cursorType;\n });\n }\n\n\n destroy() {\n this._scene.off(this._onTick);\n }\n}\n\n/**\n * @private\n */\nclass MouseMiscHandler {\n\n constructor(scene, controllers, configs, states, updates) {\n\n this._scene = scene;\n\n const canvas = this._scene.canvas.canvas;\n\n canvas.addEventListener(\"mouseenter\", this._mouseEnterHandler = () => {\n states.mouseover = true;\n });\n\n canvas.addEventListener(\"mouseleave\", this._mouseLeaveHandler = () => {\n states.mouseover = false;\n canvas.style.cursor = null;\n });\n\n document.addEventListener(\"mousemove\", this._mouseMoveHandler = (e) => {\n getCanvasPosFromEvent$2(e, canvas, states.pointerCanvasPos);\n });\n\n canvas.addEventListener(\"mousedown\", this._mouseDownHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n getCanvasPosFromEvent$2(e, canvas, states.pointerCanvasPos);\n states.mouseover = true;\n });\n\n canvas.addEventListener(\"mouseup\", this._mouseUpHandler = (e) => {\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n });\n }\n\n reset() {\n }\n\n destroy() {\n\n const canvas = this._scene.canvas.canvas;\n\n document.removeEventListener(\"mousemove\", this._mouseMoveHandler);\n canvas.removeEventListener(\"mouseenter\", this._mouseEnterHandler);\n canvas.removeEventListener(\"mouseleave\", this._mouseLeaveHandler);\n canvas.removeEventListener(\"mousedown\", this._mouseDownHandler);\n canvas.removeEventListener(\"mouseup\", this._mouseUpHandler);\n }\n}\n\nfunction getCanvasPosFromEvent$2(event, canvas, canvasPos) {\n if (!event) {\n event = window.event;\n canvasPos[0] = event.x;\n canvasPos[1] = event.y;\n } else {\n const { left, top } = canvas.getBoundingClientRect();\n canvasPos[0] = event.clientX - left;\n canvasPos[1] = event.clientY - top;\n }\n return canvasPos;\n}\n\nconst getCanvasPosFromEvent$1 = function (event, canvasPos) {\n if (!event) {\n event = window.event;\n canvasPos[0] = event.x;\n canvasPos[1] = event.y;\n } else {\n let element = event.target;\n let totalOffsetLeft = 0;\n let totalOffsetTop = 0;\n while (element.offsetParent) {\n totalOffsetLeft += element.offsetLeft;\n totalOffsetTop += element.offsetTop;\n element = element.offsetParent;\n }\n canvasPos[0] = event.pageX - totalOffsetLeft;\n canvasPos[1] = event.pageY - totalOffsetTop;\n }\n return canvasPos;\n};\n\n/**\n * @private\n */\nclass TouchPanRotateAndDollyHandler {\n\n constructor(scene, controllers, configs, states, updates) {\n\n this._scene = scene;\n\n const pickController = controllers.pickController;\n const pivotController = controllers.pivotController;\n\n const tapStartCanvasPos = math.vec2();\n const tapCanvasPos0 = math.vec2();\n const tapCanvasPos1 = math.vec2();\n const touch0Vec = math.vec2();\n\n const lastCanvasTouchPosList = [];\n const canvas = this._scene.canvas.canvas;\n\n let numTouches = 0;\n let waitForTick = false;\n\n this._onTick = scene.on(\"tick\", () => {\n waitForTick = false;\n });\n\n canvas.addEventListener(\"touchstart\", this._canvasTouchStartHandler = (event) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n event.preventDefault();\n\n const touches = event.touches;\n const changedTouches = event.changedTouches;\n\n states.touchStartTime = Date.now();\n\n if (touches.length === 1 && changedTouches.length === 1) {\n\n getCanvasPosFromEvent$1(touches[0], tapStartCanvasPos);\n\n if (configs.followPointer) {\n\n pickController.pickCursorPos = tapStartCanvasPos;\n pickController.schedulePickSurface = true;\n pickController.update();\n\n if (!configs.planView) {\n\n if (pickController.picked && pickController.pickedSurface && pickController.pickResult && pickController.pickResult.worldPos) {\n\n pivotController.setPivotPos(pickController.pickResult.worldPos);\n\n if (!configs.firstPerson && pivotController.startPivot()) {\n pivotController.showPivot();\n }\n\n } else {\n\n if (configs.smartPivot) {\n pivotController.setCanvasPivotPos(states.pointerCanvasPos);\n } else {\n pivotController.setPivotPos(scene.camera.look);\n }\n\n if (!configs.firstPerson && pivotController.startPivot()) {\n pivotController.showPivot();\n }\n }\n }\n }\n\n }\n\n while (lastCanvasTouchPosList.length < touches.length) {\n lastCanvasTouchPosList.push(math.vec2());\n }\n\n for (let i = 0, len = touches.length; i < len; ++i) {\n getCanvasPosFromEvent$1(touches[i], lastCanvasTouchPosList[i]);\n }\n\n numTouches = touches.length;\n });\n\n canvas.addEventListener(\"touchend\", this._canvasTouchEndHandler = () => {\n if (pivotController.getPivoting()) {\n pivotController.endPivot();\n }\n });\n\n canvas.addEventListener(\"touchmove\", this._canvasTouchMoveHandler = (event) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\n if (waitForTick) {\n // Limit changes detection to one per frame\n return;\n }\n\n waitForTick = true;\n\n // Scaling drag-rotate to canvas boundary\n\n const canvasBoundary = scene.canvas.boundary;\n const canvasWidth = canvasBoundary[2];\n const canvasHeight = canvasBoundary[3];\n\n const touches = event.touches;\n\n if (event.touches.length !== numTouches) {\n // Two fingers were pressed, then one of them is removed\n // We don't want to rotate in this case (weird behavior)\n return;\n }\n\n if (numTouches === 1) {\n\n getCanvasPosFromEvent$1(touches[0], tapCanvasPos0);\n\n //-----------------------------------------------------------------------------------------------\n // Drag rotation\n //-----------------------------------------------------------------------------------------------\n\n math.subVec2(tapCanvasPos0, lastCanvasTouchPosList[0], touch0Vec);\n\n const xPanDelta = touch0Vec[0];\n const yPanDelta = touch0Vec[1];\n\n if (states.longTouchTimeout !== null && (Math.abs(xPanDelta) > configs.longTapRadius || Math.abs(yPanDelta) > configs.longTapRadius)) {\n clearTimeout(states.longTouchTimeout);\n states.longTouchTimeout = null;\n }\n\n if (configs.planView) { // No rotating in plan-view mode\n\n const camera = scene.camera;\n\n // We use only canvasHeight here so that aspect ratio does not distort speed\n\n if (camera.projection === \"perspective\") {\n\n const depth = Math.abs(scene.camera.eyeLookDist);\n const targetDistance = depth * Math.tan((camera.perspective.fov / 2) * Math.PI / 180.0);\n\n updates.panDeltaX += (xPanDelta * targetDistance / canvasHeight) * configs.touchPanRate;\n updates.panDeltaY += (yPanDelta * targetDistance / canvasHeight) * configs.touchPanRate;\n\n } else {\n\n updates.panDeltaX += 0.5 * camera.ortho.scale * (xPanDelta / canvasHeight) * configs.touchPanRate;\n updates.panDeltaY += 0.5 * camera.ortho.scale * (yPanDelta / canvasHeight) * configs.touchPanRate;\n }\n\n } else {\n // if (!absorbTinyFirstDrag) {\n updates.rotateDeltaY -= (xPanDelta / canvasWidth) * (configs.dragRotationRate * 1.0); // Full horizontal rotation\n updates.rotateDeltaX += (yPanDelta / canvasHeight) * (configs.dragRotationRate * 1.5); // Half vertical rotation\n // } else {\n // firstDragDeltaY -= (xPanDelta / canvasWidth) * (configs.dragRotationRate * 1.0); // Full horizontal rotation\n // firstDragDeltaX += (yPanDelta / canvasHeight) * (configs.dragRotationRate * 1.5); // Half vertical rotation\n // if (Math.abs(firstDragDeltaX) > 5 || Math.abs(firstDragDeltaY) > 5) {\n // updates.rotateDeltaX += firstDragDeltaX;\n // updates.rotateDeltaY += firstDragDeltaY;\n // firstDragDeltaX = 0;\n // firstDragDeltaY = 0;\n // absorbTinyFirstDrag = false;\n // }\n // }\n }\n\n } else if (numTouches === 2) {\n\n const touch0 = touches[0];\n const touch1 = touches[1];\n\n getCanvasPosFromEvent$1(touch0, tapCanvasPos0);\n getCanvasPosFromEvent$1(touch1, tapCanvasPos1);\n\n const lastMiddleTouch = math.geometricMeanVec2(lastCanvasTouchPosList[0], lastCanvasTouchPosList[1]);\n const currentMiddleTouch = math.geometricMeanVec2(tapCanvasPos0, tapCanvasPos1);\n\n const touchDelta = math.vec2();\n\n math.subVec2(lastMiddleTouch, currentMiddleTouch, touchDelta);\n\n const xPanDelta = touchDelta[0];\n const yPanDelta = touchDelta[1];\n\n const camera = scene.camera;\n\n // Dollying\n\n const d1 = math.distVec2([touch0.pageX, touch0.pageY], [touch1.pageX, touch1.pageY]);\n const d2 = math.distVec2(lastCanvasTouchPosList[0], lastCanvasTouchPosList[1]);\n\n const dollyDelta = (d2 - d1) * configs.touchDollyRate;\n\n updates.dollyDelta = dollyDelta;\n\n if (Math.abs(dollyDelta) < 1.0) {\n\n // We use only canvasHeight here so that aspect ratio does not distort speed\n\n if (camera.projection === \"perspective\") {\n const pickedWorldPos = pickController.pickResult ? pickController.pickResult.worldPos : scene.center;\n\n const depth = Math.abs(math.lenVec3(math.subVec3(pickedWorldPos, scene.camera.eye, [])));\n const targetDistance = depth * Math.tan((camera.perspective.fov / 2) * Math.PI / 180.0);\n\n updates.panDeltaX -= (xPanDelta * targetDistance / canvasHeight) * configs.touchPanRate;\n updates.panDeltaY -= (yPanDelta * targetDistance / canvasHeight) * configs.touchPanRate;\n\n } else {\n\n updates.panDeltaX -= 0.5 * camera.ortho.scale * (xPanDelta / canvasHeight) * configs.touchPanRate;\n updates.panDeltaY -= 0.5 * camera.ortho.scale * (yPanDelta / canvasHeight) * configs.touchPanRate;\n }\n }\n\n\n states.pointerCanvasPos = currentMiddleTouch;\n }\n\n for (let i = 0; i < numTouches; ++i) {\n getCanvasPosFromEvent$1(touches[i], lastCanvasTouchPosList[i]);\n }\n });\n }\n\n reset() {\n }\n\n destroy() {\n const canvas = this._scene.canvas.canvas;\n canvas.removeEventListener(\"touchstart\", this._canvasTouchStartHandler);\n canvas.removeEventListener(\"touchend\", this._canvasTouchEndHandler);\n canvas.removeEventListener(\"touchmove\", this._canvasTouchMoveHandler);\n this._scene.off(this._onTick);\n }\n}\n\nconst TAP_INTERVAL = 150;\nconst DBL_TAP_INTERVAL = 325;\nconst TAP_DISTANCE_THRESHOLD = 4;\n\nconst getCanvasPosFromEvent = function (event, canvasPos) {\n if (!event) {\n event = window.event;\n canvasPos[0] = event.x;\n canvasPos[1] = event.y;\n } else {\n let element = event.target;\n let totalOffsetLeft = 0;\n let totalOffsetTop = 0;\n while (element.offsetParent) {\n totalOffsetLeft += element.offsetLeft;\n totalOffsetTop += element.offsetTop;\n element = element.offsetParent;\n }\n canvasPos[0] = event.pageX - totalOffsetLeft;\n canvasPos[1] = event.pageY - totalOffsetTop;\n }\n return canvasPos;\n};\n\n/**\n * @private\n */\nclass TouchPickHandler {\n\n constructor(scene, controllers, configs, states, updates) {\n\n this._scene = scene;\n\n const pickController = controllers.pickController;\n const cameraControl = controllers.cameraControl;\n\n let touchStartTime;\n const activeTouches = [];\n const tapStartPos = new Float32Array(2);\n let tapStartTime = -1;\n let lastTapTime = -1;\n\n const canvas = this._scene.canvas.canvas;\n\n const flyCameraTo = (pickResult) => {\n let pos;\n if (pickResult && pickResult.worldPos) {\n pos = pickResult.worldPos;\n }\n const aabb = pickResult ? pickResult.entity.aabb : scene.aabb;\n if (pos) { // Fly to look at point, don't change eye->look dist\n const camera = scene.camera;\n math.subVec3(camera.eye, camera.look, []);\n controllers.cameraFlight.flyTo({\n aabb: aabb\n });\n // TODO: Option to back off to fit AABB in view\n } else {// Fly to fit target boundary in view\n controllers.cameraFlight.flyTo({\n aabb: aabb\n });\n }\n };\n\n canvas.addEventListener(\"touchstart\", this._canvasTouchStartHandler = (e) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n if (states.longTouchTimeout !== null) {\n clearTimeout(states.longTouchTimeout);\n states.longTouchTimeout = null;\n }\n\n const touches = e.touches;\n const changedTouches = e.changedTouches;\n\n touchStartTime = Date.now();\n\n if (touches.length === 1 && changedTouches.length === 1) {\n tapStartTime = touchStartTime;\n\n getCanvasPosFromEvent(touches[0], tapStartPos);\n\n const rightClickClientX = tapStartPos[0];\n const rightClickClientY = tapStartPos[1];\n\n const rightClickPageX = touches[0].pageX;\n const rightClickPageY = touches[0].pageY;\n\n states.longTouchTimeout = setTimeout(() => {\n controllers.cameraControl.fire(\"rightClick\", { // For context menus\n pagePos: [Math.round(rightClickPageX), Math.round(rightClickPageY)],\n canvasPos: [Math.round(rightClickClientX), Math.round(rightClickClientY)],\n event: e\n }, true);\n\n states.longTouchTimeout = null;\n }, configs.longTapTimeout);\n\n } else {\n tapStartTime = -1;\n }\n\n while (activeTouches.length < touches.length) {\n activeTouches.push(new Float32Array(2));\n }\n\n for (let i = 0, len = touches.length; i < len; ++i) {\n getCanvasPosFromEvent(touches[i], activeTouches[i]);\n }\n\n activeTouches.length = touches.length;\n\n }, {passive: true});\n\n\n canvas.addEventListener(\"touchend\", this._canvasTouchEndHandler = (e) => {\n\n if (!(configs.active && configs.pointerEnabled)) {\n return;\n }\n\n const currentTime = Date.now();\n const touches = e.touches;\n const changedTouches = e.changedTouches;\n\n const pickedSurfaceSubs = cameraControl.hasSubs(\"pickedSurface\");\n\n if (states.longTouchTimeout !== null) {\n clearTimeout(states.longTouchTimeout);\n states.longTouchTimeout = null;\n }\n\n // process tap\n\n if (touches.length === 0 && changedTouches.length === 1) {\n\n if (tapStartTime > -1 && currentTime - tapStartTime < TAP_INTERVAL) {\n\n if (lastTapTime > -1 && tapStartTime - lastTapTime < DBL_TAP_INTERVAL) {\n\n // Double-tap\n\n getCanvasPosFromEvent(changedTouches[0], pickController.pickCursorPos);\n pickController.schedulePickEntity = true;\n pickController.schedulePickSurface = pickedSurfaceSubs;\n\n pickController.update();\n\n if (pickController.pickResult) {\n\n pickController.pickResult.touchInput = true;\n\n cameraControl.fire(\"doublePicked\", pickController.pickResult);\n\n if (pickController.pickedSurface) {\n cameraControl.fire(\"doublePickedSurface\", pickController.pickResult);\n }\n\n if (configs.doublePickFlyTo) {\n flyCameraTo(pickController.pickResult);\n }\n } else {\n cameraControl.fire(\"doublePickedNothing\");\n if (configs.doublePickFlyTo) {\n flyCameraTo();\n }\n }\n\n lastTapTime = -1;\n\n } else if (math.distVec2(activeTouches[0], tapStartPos) < TAP_DISTANCE_THRESHOLD) {\n\n // Single-tap\n\n getCanvasPosFromEvent(changedTouches[0], pickController.pickCursorPos);\n pickController.schedulePickEntity = true;\n pickController.schedulePickSurface = pickedSurfaceSubs;\n\n pickController.update();\n\n if (pickController.pickResult) {\n\n pickController.pickResult.touchInput = true;\n\n cameraControl.fire(\"picked\", pickController.pickResult);\n\n if (pickController.pickedSurface) {\n cameraControl.fire(\"pickedSurface\", pickController.pickResult);\n }\n\n } else {\n cameraControl.fire(\"pickedNothing\");\n }\n\n lastTapTime = currentTime;\n }\n\n tapStartTime = -1;\n }\n }\n\n activeTouches.length = touches.length;\n\n for (let i = 0, len = touches.length; i < len; ++i) {\n activeTouches[i][0] = touches[i].pageX;\n activeTouches[i][1] = touches[i].pageY;\n }\n\n // e.stopPropagation();\n\n }, {passive: true});\n\n }\n\n reset() {\n // TODO\n // tapStartTime = -1;\n // lastTapTime = -1;\n\n }\n\n destroy() {\n const canvas = this._scene.canvas.canvas;\n canvas.removeEventListener(\"touchstart\", this._canvasTouchStartHandler);\n canvas.removeEventListener(\"touchend\", this._canvasTouchEndHandler);\n }\n}\n\nconst DEFAULT_SNAP_PICK_RADIUS = 30;\nconst DEFAULT_SNAP_VERTEX = true;\nconst DEFAULT_SNAP_EDGE = true;\n\n/**\n * @desc Controls the {@link Camera} with user input, and fires events when the user interacts with pickable {@link Entity}s.\n *\n * # Contents\n *\n * * [Overview](#overview)\n * * [Examples](#examples)\n * * [Orbit Mode](#orbit-mode)\n * + [Following the Pointer in Orbit Mode](#--following-the-pointer-in-orbit-mode--)\n * + [Showing the Pivot Position](#--showing-the-pivot-position--)\n * + [Axis-Aligned Views in Orbit Mode](#--axis-aligned-views-in-orbit-mode--)\n * + [View-Fitting Entitys in Orbit Mode](#--view-fitting-entitys-in-orbit-mode--)\n * * [First-Person Mode](#first-person-mode)\n * + [Following the Pointer in First-Person Mode](#--following-the-pointer-in-first-person-mode--)\n * + [Constraining Vertical Position in First-Person Mode](#--constraining-vertical-position-in-first-person-mode--)\n * + [Axis-Aligned Views in First-Person Mode](#--axis-aligned-views-in-first-person-mode--)\n * + [View-Fitting Entitys in First-Person Mode](#--view-fitting-entitys-in-first-person-mode--)\n * * [Plan-View Mode](#plan-view-mode)\n * + [Following the Pointer in Plan-View Mode](#--following-the-pointer-in-plan-view-mode--)\n * + [Axis-Aligned Views in Plan-View Mode](#--axis-aligned-views-in-plan-view-mode--)\n * * [CameraControl Events](#cameracontrol-events)\n * + [\"hover\"](#---hover---)\n * + [\"hoverOff\"](#---hoveroff---)\n * + [\"hoverEnter\"](#---hoverenter---)\n * + [\"hoverOut\"](#---hoverout---)\n * + [\"picked\"](#---picked---)\n * + [\"pickedSurface\"](#---pickedsurface---)\n * + [\"pickedNothing\"](#---pickednothing---)\n * + [\"doublePicked\"](#---doublepicked---)\n * + [\"doublePickedSurface\"](#---doublepickedsurface---)\n * + [\"doublePickedNothing\"](#---doublepickednothing---)\n * + [\"rightClick\"](#---rightclick---)\n * * [Custom Keyboard Mappings](#custom-keyboard-mappings)\n *\n *

\n *\n * # Overview\n *\n * * Each {@link Viewer} has a ````CameraControl````, located at {@link Viewer#cameraControl}.\n * * {@link CameraControl#navMode} selects the navigation mode:\n * * ````\"orbit\"```` rotates the {@link Camera} position about the target.\n * * ````\"firstPerson\"```` rotates the World about the Camera position.\n * * ````\"planView\"```` never rotates, but still allows to pan and dolly, typically for an axis-aligned view.\n * * {@link CameraControl#followPointer} makes the Camera follow the mouse or touch pointer.\n * * {@link CameraControl#constrainVertical} locks the Camera to its current height when in first-person mode.\n * * ````CameraControl```` fires pick events when we hover, click or tap on an {@link Entity}.\n *

\n *\n * # Examples\n *\n * * [Orbit Navigation - Duplex Model](https://xeokit.github.io/xeokit-sdk/examples/index.html#CameraControl_orbit_Duplex)\n * * [Orbit Navigation - Holter Tower Model](https://xeokit.github.io/xeokit-sdk/examples/index.html#CameraControl_orbit_HolterTower)\n * * [First-Person Navigation - Duplex Model](https://xeokit.github.io/xeokit-sdk/examples/index.html#CameraControl_firstPerson_Duplex)\n * * [First-Person Navigation - Holter Tower Model](https://xeokit.github.io/xeokit-sdk/examples/index.html#CameraControl_firstPerson_HolterTower)\n * * [Plan-view Navigation - Schependomlaan Model](https://xeokit.github.io/xeokit-sdk/examples/index.html#CameraControl_planView_Schependomlaan)\n * * [Custom Keyboard Mapping](https://xeokit.github.io/xeokit-sdk/examples/index.html#CameraControl_keyMap)\n *

\n *\n * # Orbit Mode\n *\n * In orbit mode, ````CameraControl```` orbits the {@link Camera} about the target.\n *\n * To enable orbit mode:\n *\n * ````javascript\n * const cameraControl = myViewer.cameraControl;\n * cameraControl.navMode = \"orbit\";\n * ````\n *\n * Then orbit by:\n *\n * * left-dragging the mouse,\n * * tap-dragging the touch pad, and\n * * pressing arrow keys, or ````Q```` and ````E```` on a QWERTY keyboard, or ````A```` and ````E```` on an AZERTY keyboard.\n *

\n *\n * Dolly forwards and backwards by:\n *\n * * spinning the mouse wheel,\n * * pinching on the touch pad, and\n * * pressing the ````+```` and ````-```` keys, or ````W```` and ````S```` on a QWERTY keyboard, or ````Z```` and ````S```` for AZERTY.\n *

\n *\n * Pan horizontally and vertically by:\n *\n * * right-dragging the mouse,\n * * left-dragging the mouse with the SHIFT key down,\n * * tap-dragging the touch pad with SHIFT down,\n * * pressing the ````A````, ````D````, ````Z```` and ````X```` keys on a QWERTY keyboard, and\n * * pressing the ````Q````, ````D````, ````W```` and ````X```` keys on an AZERTY keyboard,\n *

\n *\n * ## Following the Pointer in Orbit Mode\n *\n * When {@link CameraControl#followPointer} is ````true````in orbiting mode, the mouse or touch pointer will dynamically\n * indicate the target that the {@link Camera} will orbit, as well as dolly to and from.\n *\n * Lets ensure that we're in orbit mode, then enable the {@link Camera} to follow the pointer:\n *\n * ````javascript\n * cameraControl.navMode = \"orbit\";\n * cameraControl.followPointer = true;\n * ````\n *\n * ## Smart Pivoting\n *\n * TODO\n *\n * ## Showing the Pivot Position\n *\n * We can configure {@link CameraControl#pivotElement} with an HTML element to indicate the current\n * pivot position. The indicator will appear momentarily each time we move the {@link Camera} while in orbit mode with\n * {@link CameraControl#followPointer} set ````true````.\n *\n * First we'll define some CSS to style our pivot indicator as a black dot with a white border:\n *\n * ````css\n * .camera-pivot-marker {\n * color: #ffffff;\n * position: absolute;\n * width: 25px;\n * height: 25px;\n * border-radius: 15px;\n * border: 2px solid #ebebeb;\n * background: black;\n * visibility: hidden;\n * box-shadow: 5px 5px 15px 1px #000000;\n * z-index: 10000;\n * pointer-events: none;\n * }\n * ````\n *\n * Then we'll attach our pivot indicator's HTML element to the ````CameraControl````:\n *\n * ````javascript\n * const pivotElement = document.createRange().createContextualFragment(\"
\").firstChild;\n *\n * document.body.appendChild(pivotElement);\n *\n * cameraControl.pivotElement = pivotElement;\n * ````\n *\n * ## Axis-Aligned Views in Orbit Mode\n *\n * In orbit mode, we can use keys 1-6 to position the {@link Camera} to look at the center of the {@link Scene} from along each of the\n * six World-space axis. Pressing one of these keys will fly the {@link Camera} to the corresponding axis-aligned view.\n *\n * ## View-Fitting Entitys in Orbit Mode\n *\n * When {@link CameraControl#doublePickFlyTo} is ````true````, we can left-double-click or\n * double-tap (ie. \"double-pick\") an {@link Entity} to fit it to view. This will cause the {@link Camera}\n * to fly to that Entity. Our target then becomes the center of that Entity. If we are currently pivoting,\n * then our pivot position is then also set to the Entity center.\n *\n * Disable that behaviour by setting {@link CameraControl#doublePickFlyTo} ````false````.\n *\n * # First-Person Mode\n *\n * In first-person mode, ````CameraControl```` rotates the World about the {@link Camera} position.\n *\n * To enable first-person mode:\n *\n * ````javascript\n * cameraControl.navMode = \"firstPerson\";\n * ````\n *\n * Then rotate by:\n *\n * * left-dragging the mouse,\n * * tap-dragging the touch pad,\n * * pressing arrow keys, or ````Q```` and ````E```` on a QWERTY keyboard, or ````A```` and ````E```` on an AZERTY keyboard.\n *

\n *\n * Dolly forwards and backwards by:\n *\n * * spinning the mouse wheel,\n * * pinching on the touch pad, and\n * * pressing the ````+```` and ````-```` keys, or ````W```` and ````S```` on a QWERTY keyboard, or ````Z```` and ````S```` for AZERTY.\n *

\n *\n * Pan left, right, up and down by:\n *\n * * left-dragging or right-dragging the mouse, and\n * * tap-dragging the touch pad with SHIFT down.\n *\n * Pan forwards, backwards, left, right, up and down by pressing the ````WSADZX```` keys on a QWERTY keyboard,\n * or ````WSQDWX```` keys on an AZERTY keyboard.\n *

\n *\n * ## Following the Pointer in First-Person Mode\n *\n * When {@link CameraControl#followPointer} is ````true```` in first-person mode, the mouse or touch pointer will dynamically\n * indicate the target to which the {@link Camera} will dolly to and from. In first-person mode, however, the World will always rotate\n * about the {@link Camera} position.\n *\n * Lets ensure that we're in first-person mode, then enable the {@link Camera} to follow the pointer:\n *\n * ````javascript\n * cameraControl.navMode = \"firstPerson\";\n * cameraControl.followPointer = true;\n * ````\n *\n * When the pointer is over empty space, the target will remain the last object that the pointer was over.\n *\n * ## Constraining Vertical Position in First-Person Mode\n *\n * In first-person mode, we can lock the {@link Camera} to its current position on the vertical World axis, which is useful for walk-through navigation:\n *\n * ````javascript\n * cameraControl.constrainVertical = true;\n * ````\n *\n * ## Axis-Aligned Views in First-Person Mode\n *\n * In first-person mode we can use keys 1-6 to position the {@link Camera} to look at the center of\n * the {@link Scene} from along each of the six World-space axis. Pressing one of these keys will fly the {@link Camera} to the\n * corresponding axis-aligned view.\n *\n * ## View-Fitting Entitys in First-Person Mode\n *\n * As in orbit mode, when in first-person mode and {@link CameraControl#doublePickFlyTo} is ````true````, we can double-click\n * or double-tap an {@link Entity} (ie. \"double-picking\") to fit it in view. This will cause the {@link Camera} to fly to\n * that Entity. Our target then becomes the center of that Entity.\n *\n * Disable that behaviour by setting {@link CameraControl#doublePickFlyTo} ````false````.\n *\n * # Plan-View Mode\n *\n * In plan-view mode, ````CameraControl```` pans and rotates the {@link Camera}, without rotating it.\n *\n * To enable plan-view mode:\n *\n * ````javascript\n * cameraControl.navMode = \"planView\";\n * ````\n *\n * Dolly forwards and backwards by:\n *\n * * spinning the mouse wheel,\n * * pinching on the touch pad, and\n * * pressing the ````+```` and ````-```` keys.\n *\n *
\n * Pan left, right, up and down by:\n *\n * * left-dragging or right-dragging the mouse, and\n * * tap-dragging the touch pad with SHIFT down.\n *\n * Pan forwards, backwards, left, right, up and down by pressing the ````WSADZX```` keys on a QWERTY keyboard,\n * or ````WSQDWX```` keys on an AZERTY keyboard.\n *

\n *\n * ## Following the Pointer in Plan-View Mode\n *\n * When {@link CameraControl#followPointer} is ````true```` in plan-view mode, the mouse or touch pointer will dynamically\n * indicate the target to which the {@link Camera} will dolly to and from. In plan-view mode, however, the {@link Camera} cannot rotate.\n *\n * Lets ensure that we're in plan-view mode, then enable the {@link Camera} to follow the pointer:\n *\n * ````javascript\n * cameraControl.navMode = \"planView\";\n * cameraControl.followPointer = true; // Default\n * ````\n *\n * When the pointer is over empty space, the target will remain the last object that the pointer was over.\n *\n * ## Axis-Aligned Views in Plan-View Mode\n *\n * As in orbit and first-person modes, in plan-view mode we can use keys 1-6 to position the {@link Camera} to look at the center of\n * the {@link Scene} from along each of the six World-space axis. Pressing one of these keys will fly the {@link Camera} to the\n * corresponding axis-aligned view.\n *\n * # CameraControl Events\n *\n * ````CameraControl```` fires events as we interact with {@link Entity}s using mouse or touch input.\n *\n * The following examples demonstrate how to subscribe to those events.\n *\n * The first example shows how to save a handle to a subscription, which we can later use to unsubscribe.\n *\n * ## \"hover\"\n *\n * Event fired when the pointer moves while hovering over an Entity.\n *\n * ````javascript\n * const onHover = cameraControl.on(\"hover\", (e) => {\n * const entity = e.entity; // Entity\n * const canvasPos = e.canvasPos; // 2D canvas position\n * });\n * ````\n *\n * To unsubscribe from the event:\n *\n * ````javascript\n * cameraControl.off(onHover);\n * ````\n *\n * ## \"hoverOff\"\n *\n * Event fired when the pointer moves while hovering over empty space.\n *\n * ````javascript\n * cameraControl.on(\"hoverOff\", (e) => {\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## \"hoverEnter\"\n *\n * Event fired when the pointer moves onto an Entity.\n *\n * ````javascript\n * cameraControl.on(\"hoverEnter\", (e) => {\n * const entity = e.entity;\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## \"hoverOut\"\n *\n * Event fired when the pointer moves off an Entity.\n *\n * ````javascript\n * cameraControl.on(\"hoverOut\", (e) => {\n * const entity = e.entity;\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## \"picked\"\n *\n * Event fired when we left-click or tap on an Entity.\n *\n * ````javascript\n * cameraControl.on(\"picked\", (e) => {\n * const entity = e.entity;\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## \"pickedSurface\"\n *\n * Event fired when we left-click or tap on the surface of an Entity.\n *\n * ````javascript\n * cameraControl.on(\"picked\", (e) => {\n * const entity = e.entity;\n * const canvasPos = e.canvasPos;\n * const worldPos = e.worldPos; // 3D World-space position\n * const viewPos = e.viewPos; // 3D View-space position\n * const worldNormal = e.worldNormal; // 3D World-space normal vector\n * });\n * ````\n *\n * ## \"pickedNothing\"\n *\n * Event fired when we left-click or tap on empty space.\n *\n * ````javascript\n * cameraControl.on(\"pickedNothing\", (e) => {\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## \"doublePicked\"\n *\n * Event fired wwhen we left-double-click or double-tap on an Entity.\n *\n * ````javascript\n * cameraControl.on(\"doublePicked\", (e) => {\n * const entity = e.entity;\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## \"doublePickedSurface\"\n *\n * Event fired when we left-double-click or double-tap on the surface of an Entity.\n *\n * ````javascript\n * cameraControl.on(\"doublePickedSurface\", (e) => {\n * const entity = e.entity;\n * const canvasPos = e.canvasPos;\n * const worldPos = e.worldPos;\n * const viewPos = e.viewPos;\n * const worldNormal = e.worldNormal;\n * });\n * ````\n *\n * ## \"doublePickedNothing\"\n *\n * Event fired when we left-double-click or double-tap on empty space.\n *\n * ````javascript\n * cameraControl.on(\"doublePickedNothing\", (e) => {\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## \"rightClick\"\n *\n * Event fired when we right-click on the canvas.\n *\n * ````javascript\n * cameraControl.on(\"rightClick\", (e) => {\n * const event = e.event; // Mouse event\n * const canvasPos = e.canvasPos;\n * });\n * ````\n *\n * ## Custom Keyboard Mappings\n *\n * We can customize````CameraControl```` key bindings as shown below.\n *\n * In this example, we'll just set the default bindings for a QWERTY keyboard.\n *\n * ````javascript\n * const input = myViewer.scene.input;\n *\n * cameraControl.navMode = \"orbit\";\n * cameraControl.followPointer = true;\n *\n * const keyMap = {};\n *\n * keyMap[cameraControl.PAN_LEFT] = [input.KEY_A];\n * keyMap[cameraControl.PAN_RIGHT] = [input.KEY_D];\n * keyMap[cameraControl.PAN_UP] = [input.KEY_Z];\n * keyMap[cameraControl.PAN_DOWN] = [input.KEY_X];\n * keyMap[cameraControl.DOLLY_FORWARDS] = [input.KEY_W, input.KEY_ADD];\n * keyMap[cameraControl.DOLLY_BACKWARDS] = [input.KEY_S, input.KEY_SUBTRACT];\n * keyMap[cameraControl.ROTATE_X_POS] = [input.KEY_DOWN_ARROW];\n * keyMap[cameraControl.ROTATE_X_NEG] = [input.KEY_UP_ARROW];\n * keyMap[cameraControl.ROTATE_Y_POS] = [input.KEY_LEFT_ARROW];\n * keyMap[cameraControl.ROTATE_Y_NEG] = [input.KEY_RIGHT_ARROW];\n * keyMap[cameraControl.AXIS_VIEW_RIGHT] = [input.KEY_NUM_1];\n * keyMap[cameraControl.AXIS_VIEW_BACK] = [input.KEY_NUM_2];\n * keyMap[cameraControl.AXIS_VIEW_LEFT] = [input.KEY_NUM_3];\n * keyMap[cameraControl.AXIS_VIEW_FRONT] = [input.KEY_NUM_4];\n * keyMap[cameraControl.AXIS_VIEW_TOP] = [input.KEY_NUM_5];\n * keyMap[cameraControl.AXIS_VIEW_BOTTOM] = [input.KEY_NUM_6];\n *\n * cameraControl.keyMap = keyMap;\n * ````\n *\n * We can also just configure default bindings for a specified keyboard layout, like this:\n *\n * ````javascript\n * cameraControl.keyMap = \"qwerty\";\n * ````\n *\n * Then, ````CameraControl```` will internally set {@link CameraControl#keyMap} to the default key map for the QWERTY\n * layout (which is the same set of mappings we set in the previous example). In other words, if we subsequently\n * read {@link CameraControl#keyMap}, it will now be a key map, instead of the \"qwerty\" string value we set it to.\n *\n * Supported layouts are, so far:\n *\n * * ````\"qwerty\"````\n * * ````\"azerty\"````\n */\nclass CameraControl extends Component {\n\n /**\n * @private\n * @constructor\n */\n constructor(owner, cfg = {}) {\n\n super(owner, cfg);\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.PAN_LEFT = 0;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.PAN_RIGHT = 1;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.PAN_UP = 2;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.PAN_DOWN = 3;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.PAN_FORWARDS = 4;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.PAN_BACKWARDS = 5;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.ROTATE_X_POS = 6;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.ROTATE_X_NEG = 7;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.ROTATE_Y_POS = 8;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.ROTATE_Y_NEG = 9;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.DOLLY_FORWARDS = 10;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.DOLLY_BACKWARDS = 11;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.AXIS_VIEW_RIGHT = 12;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.AXIS_VIEW_BACK = 13;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.AXIS_VIEW_LEFT = 14;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.AXIS_VIEW_FRONT = 15;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.AXIS_VIEW_TOP = 16;\n\n /**\n * Identifies the XX action.\n * @final\n * @type {Number}\n */\n this.AXIS_VIEW_BOTTOM = 17;\n\n this._keyMap = {}; // Maps key codes to the above actions\n\n this.scene.canvas.canvas.oncontextmenu = (e) => {\n e.preventDefault();\n };\n\n // User-settable CameraControl configurations\n\n this._configs = {\n\n // Private\n\n longTapTimeout: 600, // Millisecs\n longTapRadius: 5, // Pixels\n\n // General\n\n active: true,\n keyboardLayout: \"qwerty\",\n navMode: \"orbit\",\n planView: false,\n firstPerson: false,\n followPointer: true,\n doublePickFlyTo: true,\n panRightClick: true,\n showPivot: false,\n pointerEnabled: true,\n constrainVertical: false,\n smartPivot: false,\n doubleClickTimeFrame: 250,\n \n snapToVertex: DEFAULT_SNAP_VERTEX,\n snapToEdge: DEFAULT_SNAP_EDGE,\n snapRadius: DEFAULT_SNAP_PICK_RADIUS,\n\n // Rotation\n\n dragRotationRate: 360.0,\n keyboardRotationRate: 90.0,\n rotationInertia: 0.0,\n\n // Panning\n\n keyboardPanRate: 1.0,\n touchPanRate: 1.0,\n panInertia: 0.5,\n\n // Dollying\n\n keyboardDollyRate: 10,\n mouseWheelDollyRate: 100,\n touchDollyRate: 0.2,\n dollyInertia: 0,\n dollyProximityThreshold: 30.0,\n dollyMinSpeed: 0.04\n };\n\n // Current runtime state of the CameraControl\n\n this._states = {\n pointerCanvasPos: math.vec2(),\n mouseover: false,\n followPointerDirty: true,\n mouseDownClientX: 0,\n mouseDownClientY: 0,\n mouseDownCursorX: 0,\n mouseDownCursorY: 0,\n touchStartTime: null,\n activeTouches: [],\n tapStartPos: math.vec2(),\n tapStartTime: -1,\n lastTapTime: -1,\n longTouchTimeout: null\n };\n\n // Updates for CameraUpdater to process on next Scene \"tick\" event\n\n this._updates = {\n rotateDeltaX: 0,\n rotateDeltaY: 0,\n panDeltaX: 0,\n panDeltaY: 0,\n panDeltaZ: 0,\n dollyDelta: 0\n };\n\n // Controllers to assist input event handlers with controlling the Camera\n\n const scene = this.scene;\n\n this._controllers = {\n cameraControl: this,\n pickController: new PickController(this, this._configs),\n pivotController: new PivotController(scene, this._configs),\n panController: new PanController(scene),\n cameraFlight: new CameraFlightAnimation(this, {\n duration: 0.5\n })\n };\n\n // Input event handlers\n\n this._handlers = [\n new MouseMiscHandler(this.scene, this._controllers, this._configs, this._states, this._updates),\n new TouchPanRotateAndDollyHandler(this.scene, this._controllers, this._configs, this._states, this._updates),\n new MousePanRotateDollyHandler(this.scene, this._controllers, this._configs, this._states, this._updates),\n new KeyboardAxisViewHandler(this.scene, this._controllers, this._configs, this._states, this._updates),\n new MousePickHandler(this.scene, this._controllers, this._configs, this._states, this._updates),\n new TouchPickHandler(this.scene, this._controllers, this._configs, this._states, this._updates),\n new KeyboardPanRotateDollyHandler(this.scene, this._controllers, this._configs, this._states, this._updates)\n ];\n\n // Applies scheduled updates to the Camera on each Scene \"tick\" event\n\n this._cameraUpdater = new CameraUpdater(this.scene, this._controllers, this._configs, this._states, this._updates);\n\n // Set initial user configurations\n\n this.navMode = cfg.navMode;\n if (cfg.planView) {\n this.planView = cfg.planView;\n }\n this.constrainVertical = cfg.constrainVertical;\n if (cfg.keyboardLayout) {\n this.keyboardLayout = cfg.keyboardLayout; // Deprecated\n } else {\n this.keyMap = cfg.keyMap;\n }\n this.doublePickFlyTo = cfg.doublePickFlyTo;\n this.panRightClick = cfg.panRightClick;\n this.active = cfg.active;\n this.followPointer = cfg.followPointer;\n this.rotationInertia = cfg.rotationInertia;\n this.keyboardPanRate = cfg.keyboardPanRate;\n this.touchPanRate = cfg.touchPanRate;\n this.keyboardRotationRate = cfg.keyboardRotationRate;\n this.dragRotationRate = cfg.dragRotationRate;\n this.touchDollyRate = cfg.touchDollyRate;\n this.dollyInertia = cfg.dollyInertia;\n this.dollyProximityThreshold = cfg.dollyProximityThreshold;\n this.dollyMinSpeed = cfg.dollyMinSpeed;\n this.panInertia = cfg.panInertia;\n this.pointerEnabled = true;\n this.keyboardDollyRate = cfg.keyboardDollyRate;\n this.mouseWheelDollyRate = cfg.mouseWheelDollyRate;\n }\n\n /**\n * Sets custom mappings of keys to ````CameraControl```` actions.\n *\n * See class docs for usage.\n *\n * @param {{Number:Number}|String} value Either a set of new key mappings, or a string to select a keyboard layout,\n * which causes ````CameraControl```` to use the default key mappings for that layout.\n */\n set keyMap(value) {\n value = value || \"qwerty\";\n if (utils.isString(value)) {\n const input = this.scene.input;\n const keyMap = {};\n\n switch (value) {\n\n default:\n this.error(\"Unsupported value for 'keyMap': \" + value + \" defaulting to 'qwerty'\");\n // Intentional fall-through to \"qwerty\"\n case \"qwerty\":\n keyMap[this.PAN_LEFT] = [input.KEY_A];\n keyMap[this.PAN_RIGHT] = [input.KEY_D];\n keyMap[this.PAN_UP] = [input.KEY_Z];\n keyMap[this.PAN_DOWN] = [input.KEY_X];\n keyMap[this.PAN_BACKWARDS] = [];\n keyMap[this.PAN_FORWARDS] = [];\n keyMap[this.DOLLY_FORWARDS] = [input.KEY_W, input.KEY_ADD];\n keyMap[this.DOLLY_BACKWARDS] = [input.KEY_S, input.KEY_SUBTRACT];\n keyMap[this.ROTATE_X_POS] = [input.KEY_DOWN_ARROW];\n keyMap[this.ROTATE_X_NEG] = [input.KEY_UP_ARROW];\n keyMap[this.ROTATE_Y_POS] = [input.KEY_Q, input.KEY_LEFT_ARROW];\n keyMap[this.ROTATE_Y_NEG] = [input.KEY_E, input.KEY_RIGHT_ARROW];\n keyMap[this.AXIS_VIEW_RIGHT] = [input.KEY_NUM_1];\n keyMap[this.AXIS_VIEW_BACK] = [input.KEY_NUM_2];\n keyMap[this.AXIS_VIEW_LEFT] = [input.KEY_NUM_3];\n keyMap[this.AXIS_VIEW_FRONT] = [input.KEY_NUM_4];\n keyMap[this.AXIS_VIEW_TOP] = [input.KEY_NUM_5];\n keyMap[this.AXIS_VIEW_BOTTOM] = [input.KEY_NUM_6];\n break;\n\n case \"azerty\":\n keyMap[this.PAN_LEFT] = [input.KEY_Q];\n keyMap[this.PAN_RIGHT] = [input.KEY_D];\n keyMap[this.PAN_UP] = [input.KEY_W];\n keyMap[this.PAN_DOWN] = [input.KEY_X];\n keyMap[this.PAN_BACKWARDS] = [];\n keyMap[this.PAN_FORWARDS] = [];\n keyMap[this.DOLLY_FORWARDS] = [input.KEY_Z, input.KEY_ADD];\n keyMap[this.DOLLY_BACKWARDS] = [input.KEY_S, input.KEY_SUBTRACT];\n keyMap[this.ROTATE_X_POS] = [input.KEY_DOWN_ARROW];\n keyMap[this.ROTATE_X_NEG] = [input.KEY_UP_ARROW];\n keyMap[this.ROTATE_Y_POS] = [input.KEY_A, input.KEY_LEFT_ARROW];\n keyMap[this.ROTATE_Y_NEG] = [input.KEY_E, input.KEY_RIGHT_ARROW];\n keyMap[this.AXIS_VIEW_RIGHT] = [input.KEY_NUM_1];\n keyMap[this.AXIS_VIEW_BACK] = [input.KEY_NUM_2];\n keyMap[this.AXIS_VIEW_LEFT] = [input.KEY_NUM_3];\n keyMap[this.AXIS_VIEW_FRONT] = [input.KEY_NUM_4];\n keyMap[this.AXIS_VIEW_TOP] = [input.KEY_NUM_5];\n keyMap[this.AXIS_VIEW_BOTTOM] = [input.KEY_NUM_6];\n break;\n }\n\n this._keyMap = keyMap;\n } else {\n const keyMap = value;\n this._keyMap = keyMap;\n }\n }\n\n /**\n * Gets custom mappings of keys to {@link CameraControl} actions.\n *\n * @returns {{Number:Number}} Current key mappings.\n */\n get keyMap() {\n return this._keyMap;\n }\n\n /**\n * Returns true if any keys configured for the given action are down.\n * @param action\n * @param keyDownMap\n * @private\n */\n _isKeyDownForAction(action, keyDownMap) {\n const keys = this._keyMap[action];\n if (!keys) {\n return false;\n }\n if (!keyDownMap) {\n keyDownMap = this.scene.input.keyDown;\n }\n for (let i = 0, len = keys.length; i < len; i++) {\n const key = keys[i];\n if (keyDownMap[key]) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Sets the HTMl element to represent the pivot point when {@link CameraControl#followPointer} is true.\n *\n * See class comments for an example.\n *\n * @param {HTMLElement} element HTML element representing the pivot point.\n */\n set pivotElement(element) {\n this._controllers.pivotController.setPivotElement(element);\n }\n\n /**\n * Sets if this ````CameraControl```` is active or not.\n *\n * When inactive, the ````CameraControl```` will not react to input.\n *\n * Default is ````true````.\n *\n * @param {Boolean} value Set ````true```` to activate this ````CameraControl````.\n */\n set active(value) {\n value = value !== false;\n this._configs.active = value;\n this._handlers[1]._active = value;\n this._handlers[5]._active = value;\n }\n\n /**\n * Gets if this ````CameraControl```` is active or not.\n *\n * When inactive, the ````CameraControl```` will not react to input.\n *\n * Default is ````true````.\n *\n * @returns {Boolean} Returns ````true```` if this ````CameraControl```` is active.\n */\n get active() {\n return this._configs.active;\n }\n\n /**\n * Sets whether the pointer snap to vertex.\n *\n * @param {boolean} snapToVertex\n */\n set snapToVertex(snapToVertex) {\n this._configs.snapToVertex = !!snapToVertex;\n }\n\n /**\n * Gets whether the pointer snap to vertex.\n *\n * @returns {boolean}\n */\n get snapToVertex() {\n return this._configs.snapToVertex;\n }\n\n /**\n * Sets whether the pointer snap to edge.\n *\n * @param {boolean} snapToEdge\n */\n set snapToEdge(snapToEdge) {\n this._configs.snapToEdge = !!snapToEdge;\n }\n\n /**\n * Gets whether the pointer snap to edge.\n *\n * @returns {boolean}\n */\n get snapToEdge() {\n return this._configs.snapToEdge;\n }\n\n /**\n * Sets the current snap radius for \"hoverSnapOrSurface\" events, to specify whether the radius\n * within which the pointer snaps to the nearest vertex or the nearest edge.\n *\n * Default value is 30 pixels.\n *\n * @param {Number} snapRadius The snap radius.\n */\n set snapRadius(snapRadius) {\n snapRadius = snapRadius || DEFAULT_SNAP_PICK_RADIUS;\n this._configs.snapRadius = snapRadius;\n }\n\n /**\n * Gets the current snap radius.\n *\n * @returns {Number} The snap radius.\n */\n get snapRadius() {\n return this._configs.snapRadius;\n }\n \n /**\n * Sets the current navigation mode.\n *\n * Accepted values are:\n *\n * * \"orbit\" - rotation orbits about the current target or pivot point,\n * * \"firstPerson\" - rotation is about the current eye position,\n * * \"planView\" - rotation is disabled.\n *\n * See class comments for more info.\n *\n * @param {String} navMode The navigation mode: \"orbit\", \"firstPerson\" or \"planView\".\n */\n set navMode(navMode) {\n navMode = navMode || \"orbit\";\n if (navMode !== \"firstPerson\" && navMode !== \"orbit\" && navMode !== \"planView\") {\n this.error(\"Unsupported value for navMode: \" + navMode + \" - supported values are 'orbit', 'firstPerson' and 'planView' - defaulting to 'orbit'\");\n navMode = \"orbit\";\n }\n this._configs.firstPerson = (navMode === \"firstPerson\");\n this._configs.planView = (navMode === \"planView\");\n if (this._configs.firstPerson || this._configs.planView) {\n this._controllers.pivotController.hidePivot();\n this._controllers.pivotController.endPivot();\n }\n this._configs.navMode = navMode;\n }\n\n /**\n * Gets the current navigation mode.\n *\n * @returns {String} The navigation mode: \"orbit\", \"firstPerson\" or \"planView\".\n */\n get navMode() {\n return this._configs.navMode;\n }\n\n /**\n * Sets whether mouse and touch input is enabled.\n *\n * Default is ````true````.\n *\n * Disabling mouse and touch input on ````CameraControl```` is useful when we want to temporarily use mouse or\n * touch input to interact with some other 3D control, without disturbing the {@link Camera}.\n *\n * @param {Boolean} value Set ````true```` to enable mouse and touch input.\n */\n set pointerEnabled(value) {\n this._reset();\n this._configs.pointerEnabled = !!value;\n }\n\n _reset() {\n for (let i = 0, len = this._handlers.length; i < len; i++) {\n const handler = this._handlers[i];\n if (handler.reset) {\n handler.reset();\n }\n }\n\n this._updates.panDeltaX = 0;\n this._updates.panDeltaY = 0;\n this._updates.rotateDeltaX = 0;\n this._updates.rotateDeltaY = 0;\n this._updates.dolyDelta = 0;\n }\n\n /**\n * Gets whether mouse and touch input is enabled.\n *\n * Default is ````true````.\n *\n * Disabling mouse and touch input on ````CameraControl```` is desirable when we want to temporarily use mouse or\n * touch input to interact with some other 3D control, without interfering with the {@link Camera}.\n *\n * @returns {Boolean} Returns ````true```` if mouse and touch input is enabled.\n */\n get pointerEnabled() {\n return this._configs.pointerEnabled;\n }\n\n /**\n * Sets whether the {@link Camera} follows the mouse/touch pointer.\n *\n * In orbiting mode, the Camera will orbit about the pointer, and will dolly to and from the pointer.\n *\n * In fly-to mode, the Camera will dolly to and from the pointer, however the World will always rotate about the Camera position.\n *\n * In plan-view mode, the Camera will dolly to and from the pointer, however the Camera will not rotate.\n *\n * Default is ````true````.\n *\n * See class comments for more info.\n *\n * @param {Boolean} value Set ````true```` to enable the Camera to follow the pointer.\n */\n set followPointer(value) {\n this._configs.followPointer = (value !== false);\n }\n\n /**\n * Sets whether the {@link Camera} follows the mouse/touch pointer.\n *\n * In orbiting mode, the Camera will orbit about the pointer, and will dolly to and from the pointer.\n *\n * In fly-to mode, the Camera will dolly to and from the pointer, however the World will always rotate about the Camera position.\n *\n * In plan-view mode, the Camera will dolly to and from the pointer, however the Camera will not rotate.\n *\n * Default is ````true````.\n *\n * See class comments for more info.\n *\n * @returns {Boolean} Returns ````true```` if the Camera follows the pointer.\n */\n get followPointer() {\n return this._configs.followPointer;\n }\n\n /**\n * Sets the current World-space 3D target position.\n *\n * Only applies when {@link CameraControl#followPointer} is ````true````.\n *\n * @param {Number[]} worldPos The new World-space 3D target position.\n */\n set pivotPos(worldPos) {\n this._controllers.pivotController.setPivotPos(worldPos);\n }\n\n /**\n * Gets the current World-space 3D pivot position.\n *\n * Only applies when {@link CameraControl#followPointer} is ````true````.\n *\n * @return {Number[]} worldPos The current World-space 3D pivot position.\n */\n get pivotPos() {\n return this._controllers.pivotController.getPivotPos();\n }\n\n /**\n * @deprecated\n * @param {Boolean} value Set ````true```` to enable dolly-to-pointer behaviour.\n */\n set dollyToPointer(value) {\n this.warn(\"dollyToPointer property is deprecated - replaced with followPointer\");\n this.followPointer = value;\n }\n\n /**\n * @deprecated\n * @returns {Boolean} Returns ````true```` if dolly-to-pointer behaviour is enabled.\n */\n get dollyToPointer() {\n this.warn(\"dollyToPointer property is deprecated - replaced with followPointer\");\n return this.followPointer;\n }\n\n /**\n * @deprecated\n * @param {Boolean} value Set ````true```` to enable dolly-to-pointer behaviour.\n */\n set panToPointer(value) {\n this.warn(\"panToPointer property is deprecated - replaced with followPointer\");\n }\n\n /**\n * @deprecated\n * @returns {Boolean} Returns ````true```` if dolly-to-pointer behaviour is enabled.\n */\n get panToPointer() {\n this.warn(\"panToPointer property is deprecated - replaced with followPointer\");\n return false;\n }\n\n /**\n * Sets whether this ````CameraControl```` is in plan-view mode.\n *\n * When in plan-view mode, rotation is disabled.\n *\n * Default is ````false````.\n *\n * Deprecated - use {@link CameraControl#navMode} instead.\n *\n * @param {Boolean} value Set ````true```` to enable plan-view mode.\n * @deprecated\n */\n set planView(value) {\n this._configs.planView = !!value;\n this._configs.firstPerson = false;\n if (this._configs.planView) {\n this._controllers.pivotController.hidePivot();\n this._controllers.pivotController.endPivot();\n }\n this.warn(\"planView property is deprecated - replaced with navMode\");\n }\n\n /**\n * Gets whether this ````CameraControl```` is in plan-view mode.\n *\n * When in plan-view mode, rotation is disabled.\n *\n * Default is ````false````.\n *\n * Deprecated - use {@link CameraControl#navMode} instead.\n *\n * @returns {Boolean} Returns ````true```` if plan-view mode is enabled.\n * @deprecated\n */\n get planView() {\n this.warn(\"planView property is deprecated - replaced with navMode\");\n return this._configs.planView;\n }\n\n /**\n * Sets whether this ````CameraControl```` is in first-person mode.\n *\n * In \"first person\" mode (disabled by default) the look position rotates about the eye position. Otherwise, {@link Camera#eye} rotates about {@link Camera#look}.\n *\n * Default is ````false````.\n *\n * Deprecated - use {@link CameraControl#navMode} instead.\n *\n * @param {Boolean} value Set ````true```` to enable first-person mode.\n * @deprecated\n */\n set firstPerson(value) {\n this.warn(\"firstPerson property is deprecated - replaced with navMode\");\n this._configs.firstPerson = !!value;\n this._configs.planView = false;\n if (this._configs.firstPerson) {\n this._controllers.pivotController.hidePivot();\n this._controllers.pivotController.endPivot();\n }\n }\n\n /**\n * Gets whether this ````CameraControl```` is in first-person mode.\n *\n * In \"first person\" mode (disabled by default) the look position rotates about the eye position. Otherwise, {@link Camera#eye} rotates about {@link Camera#look}.\n *\n * Default is ````false````.\n *\n * Deprecated - use {@link CameraControl#navMode} instead.\n *\n * @returns {Boolean} Returns ````true```` if first-person mode is enabled.\n * @deprecated\n */\n get firstPerson() {\n this.warn(\"firstPerson property is deprecated - replaced with navMode\");\n return this._configs.firstPerson;\n }\n\n /**\n * Sets whether to vertically constrain the {@link Camera} position for first-person navigation.\n *\n * When set ````true````, this constrains {@link Camera#eye} to its current vertical position.\n *\n * Only applies when {@link CameraControl#navMode} is ````\"firstPerson\"````.\n *\n * Default is ````false````.\n *\n * @param {Boolean} value Set ````true```` to vertically constrain the Camera.\n */\n set constrainVertical(value) {\n this._configs.constrainVertical = !!value;\n }\n\n /**\n * Gets whether to vertically constrain the {@link Camera} position for first-person navigation.\n *\n * When set ````true````, this constrains {@link Camera#eye} to its current vertical position.\n *\n * Only applies when {@link CameraControl#navMode} is ````\"firstPerson\"````.\n *\n * Default is ````false````.\n *\n * @returns {Boolean} ````true```` when Camera is vertically constrained.\n */\n get constrainVertical() {\n return this._configs.constrainVertical;\n }\n\n /**\n * Sets whether double-picking an {@link Entity} causes the {@link Camera} to fly to its boundary.\n *\n * Default is ````false````.\n *\n * @param {Boolean} value Set ````true```` to enable double-pick-fly-to mode.\n */\n set doublePickFlyTo(value) {\n this._configs.doublePickFlyTo = value !== false;\n }\n\n /**\n * Gets whether double-picking an {@link Entity} causes the {@link Camera} to fly to its boundary.\n *\n * Default is ````false````.\n *\n * @returns {Boolean} Returns ````true```` when double-pick-fly-to mode is enabled.\n */\n get doublePickFlyTo() {\n return this._configs.doublePickFlyTo;\n }\n\n /**\n * Sets whether either right-clicking (true) or middle-clicking (false) pans the {@link Camera}.\n *\n * Default is ````true````.\n *\n * @param {Boolean} value Set ````false```` to disable pan on right-click.\n */\n set panRightClick(value) {\n this._configs.panRightClick = value !== false;\n }\n\n /**\n * Gets whether right-clicking pans the {@link Camera}.\n *\n * Default is ````true````.\n *\n * @returns {Boolean} Returns ````false```` when pan on right-click is disabled.\n */\n get panRightClick() {\n return this._configs.panRightClick;\n }\n\n /**\n * Sets a factor in range ````[0..1]```` indicating how much the {@link Camera} keeps moving after you finish rotating it.\n *\n * A value of ````0.0```` causes it to immediately stop, ````0.5```` causes its movement to decay 50% on each tick,\n * while ````1.0```` causes no decay, allowing it continue moving, by the current rate of rotation.\n *\n * You may choose an inertia of zero when you want be able to precisely rotate the Camera,\n * without interference from inertia. Zero inertia can also mean that less frames are rendered while\n * you are rotating the Camera.\n *\n * Default is ````0.0````.\n *\n * Does not apply when {@link CameraControl#navMode} is ````\"planView\"````, which disallows rotation.\n *\n * @param {Number} rotationInertia New inertial factor.\n */\n set rotationInertia(rotationInertia) {\n this._configs.rotationInertia = (rotationInertia !== undefined && rotationInertia !== null) ? rotationInertia : 0.0;\n }\n\n /**\n * Gets the rotation inertia factor.\n *\n * Default is ````0.0````.\n *\n * Does not apply when {@link CameraControl#navMode} is ````\"planView\"````, which disallows rotation.\n *\n * @returns {Number} The inertia factor.\n */\n get rotationInertia() {\n return this._configs.rotationInertia;\n }\n\n /**\n * Sets how much the {@link Camera} pans each second with keyboard input.\n *\n * Default is ````5.0````, to pan the Camera ````5.0```` World-space units every second that\n * a panning key is depressed. See the ````CameraControl```` class documentation for which keys control\n * panning.\n *\n * Panning direction is aligned to our Camera's orientation. When we pan horizontally, we pan\n * to our left and right, when we pan vertically, we pan upwards and downwards, and when we pan forwards\n * and backwards, we pan along the direction the Camera is pointing.\n *\n * Unlike dollying when {@link followPointer} is ````true````, panning does not follow the pointer.\n *\n * @param {Number} keyboardPanRate The new keyboard pan rate.\n */\n set keyboardPanRate(keyboardPanRate) {\n this._configs.keyboardPanRate = (keyboardPanRate !== null && keyboardPanRate !== undefined) ? keyboardPanRate : 5.0;\n }\n\n\n /**\n * Sets how fast the camera pans on touch panning\n *\n * @param {Number} touchPanRate The new touch pan rate.\n */\n set touchPanRate(touchPanRate) {\n this._configs.touchPanRate = (touchPanRate !== null && touchPanRate !== undefined) ? touchPanRate : 1.0;\n }\n\n /**\n * Gets how fast the {@link Camera} pans on touch panning\n *\n * Default is ````1.0````.\n *\n * @returns {Number} The current touch pan rate.\n */\n get touchPanRate() {\n return this._configs.touchPanRate;\n }\n\n /**\n * Gets how much the {@link Camera} pans each second with keyboard input.\n *\n * Default is ````5.0````.\n *\n * @returns {Number} The current keyboard pan rate.\n */\n get keyboardPanRate() {\n return this._configs.keyboardPanRate;\n }\n\n /**\n * Sets how many degrees per second the {@link Camera} rotates/orbits with keyboard input.\n *\n * Default is ````90.0````, to rotate/orbit the Camera ````90.0```` degrees every second that\n * a rotation key is depressed. See the ````CameraControl```` class documentation for which keys control\n * rotation/orbit.\n *\n * @param {Number} keyboardRotationRate The new keyboard rotation rate.\n */\n set keyboardRotationRate(keyboardRotationRate) {\n this._configs.keyboardRotationRate = (keyboardRotationRate !== null && keyboardRotationRate !== undefined) ? keyboardRotationRate : 90.0;\n }\n\n /**\n * Sets how many degrees per second the {@link Camera} rotates/orbits with keyboard input.\n *\n * Default is ````90.0````.\n *\n * @returns {Number} The current keyboard rotation rate.\n */\n get keyboardRotationRate() {\n return this._configs.keyboardRotationRate;\n }\n\n /**\n * Sets the current drag rotation rate.\n *\n * This configures how many degrees the {@link Camera} rotates/orbits for a full sweep of the canvas by mouse or touch dragging.\n *\n * For example, a value of ````360.0```` indicates that the ````Camera```` rotates/orbits ````360.0```` degrees horizontally\n * when we sweep the entire width of the canvas.\n *\n * ````CameraControl```` makes vertical rotation half as sensitive as horizontal rotation, so that we don't tend to\n * flip upside-down. Therefore, a value of ````360.0```` rotates/orbits the ````Camera```` through ````180.0```` degrees\n * vertically when we sweep the entire height of the canvas.\n *\n * Default is ````360.0````.\n *\n * @param {Number} dragRotationRate The new drag rotation rate.\n */\n set dragRotationRate(dragRotationRate) {\n this._configs.dragRotationRate = (dragRotationRate !== null && dragRotationRate !== undefined) ? dragRotationRate : 360.0;\n }\n\n /**\n * Gets the current drag rotation rate.\n *\n * Default is ````360.0````.\n *\n * @returns {Number} The current drag rotation rate.\n */\n get dragRotationRate() {\n return this._configs.dragRotationRate;\n }\n\n /**\n * Sets how much the {@link Camera} dollys each second with keyboard input.\n *\n * Default is ````15.0````, to dolly the {@link Camera} ````15.0```` World-space units per second while we hold down\n * the ````+```` and ````-```` keys.\n *\n * @param {Number} keyboardDollyRate The new keyboard dolly rate.\n */\n set keyboardDollyRate(keyboardDollyRate) {\n this._configs.keyboardDollyRate = (keyboardDollyRate !== null && keyboardDollyRate !== undefined) ? keyboardDollyRate : 15.0;\n }\n\n /**\n * Gets how much the {@link Camera} dollys each second with keyboard input.\n *\n * Default is ````15.0````.\n *\n * @returns {Number} The current keyboard dolly rate.\n */\n get keyboardDollyRate() {\n return this._configs.keyboardDollyRate;\n }\n\n /**\n * Sets how much the {@link Camera} dollys with touch input.\n *\n * Default is ````0.2````\n *\n * @param {Number} touchDollyRate The new touch dolly rate.\n */\n set touchDollyRate(touchDollyRate) {\n this._configs.touchDollyRate = (touchDollyRate !== null && touchDollyRate !== undefined) ? touchDollyRate : 0.2;\n }\n\n /**\n * Gets how much the {@link Camera} dollys each second with touch input.\n *\n * Default is ````0.2````.\n *\n * @returns {Number} The current touch dolly rate.\n */\n get touchDollyRate() {\n return this._configs.touchDollyRate;\n }\n\n /**\n * Sets how much the {@link Camera} dollys each second while the mouse wheel is spinning.\n *\n * Default is ````100.0````, to dolly the {@link Camera} ````10.0```` World-space units per second as we spin\n * the mouse wheel.\n *\n * @param {Number} mouseWheelDollyRate The new mouse wheel dolly rate.\n */\n set mouseWheelDollyRate(mouseWheelDollyRate) {\n this._configs.mouseWheelDollyRate = (mouseWheelDollyRate !== null && mouseWheelDollyRate !== undefined) ? mouseWheelDollyRate : 100.0;\n }\n\n /**\n * Gets how much the {@link Camera} dollys each second while the mouse wheel is spinning.\n *\n * Default is ````100.0````.\n *\n * @returns {Number} The current mouseWheel dolly rate.\n */\n get mouseWheelDollyRate() {\n return this._configs.mouseWheelDollyRate;\n }\n\n /**\n * Sets the dolly inertia factor.\n *\n * This factor configures how much the {@link Camera} keeps moving after you finish dollying it.\n *\n * This factor is a value in range ````[0..1]````. A value of ````0.0```` causes dollying to immediately stop,\n * ````0.5```` causes dollying to decay 50% on each animation frame, while ````1.0```` causes no decay, which allows dollying\n * to continue until further input stops it.\n *\n * You might set ````dollyInertia```` to zero when you want be able to precisely position or rotate the Camera,\n * without interference from inertia. This also means that xeokit renders less frames while dollying the Camera,\n * which can improve rendering performance.\n *\n * Default is ````0````.\n *\n * @param {Number} dollyInertia New dolly inertia factor.\n */\n set dollyInertia(dollyInertia) {\n this._configs.dollyInertia = (dollyInertia !== undefined && dollyInertia !== null) ? dollyInertia : 0;\n }\n\n /**\n * Gets the dolly inertia factor.\n *\n * Default is ````0````.\n *\n * @returns {Number} The current dolly inertia factor.\n */\n get dollyInertia() {\n return this._configs.dollyInertia;\n }\n\n /**\n * Sets the proximity to the closest object below which dolly speed decreases, and above which dolly speed increases.\n *\n * Default is ````35.0````.\n *\n * @param {Number} dollyProximityThreshold New dolly proximity threshold.\n */\n set dollyProximityThreshold(dollyProximityThreshold) {\n this._configs.dollyProximityThreshold = (dollyProximityThreshold !== undefined && dollyProximityThreshold !== null) ? dollyProximityThreshold : 35.0;\n }\n\n /**\n * Gets the proximity to the closest object below which dolly speed decreases, and above which dolly speed increases.\n *\n * Default is ````35.0````.\n *\n * @returns {Number} The current dolly proximity threshold.\n */\n get dollyProximityThreshold() {\n return this._configs.dollyProximityThreshold;\n }\n\n /**\n * Sets the minimum dolly speed.\n *\n * Default is ````0.04````.\n *\n * @param {Number} dollyMinSpeed New dolly minimum speed.\n */\n set dollyMinSpeed(dollyMinSpeed) {\n this._configs.dollyMinSpeed = (dollyMinSpeed !== undefined && dollyMinSpeed !== null) ? dollyMinSpeed : 0.04;\n }\n\n /**\n * Gets the minimum dolly speed.\n *\n * Default is ````0.04````.\n *\n * @returns {Number} The current minimum dolly speed.\n */\n get dollyMinSpeed() {\n return this._configs.dollyMinSpeed;\n }\n\n /**\n * Sets the pan inertia factor.\n *\n * This factor configures how much the {@link Camera} keeps moving after you finish panning it.\n *\n * This factor is a value in range ````[0..1]````. A value of ````0.0```` causes panning to immediately stop,\n * ````0.5```` causes panning to decay 50% on each animation frame, while ````1.0```` causes no decay, which allows panning\n * to continue until further input stops it.\n *\n * You might set ````panInertia```` to zero when you want be able to precisely position or rotate the Camera,\n * without interference from inertia. This also means that xeokit renders less frames while panning the Camera,\n * wich can improve rendering performance.\n *\n * Default is ````0.5````.\n *\n * @param {Number} panInertia New pan inertia factor.\n */\n set panInertia(panInertia) {\n this._configs.panInertia = (panInertia !== undefined && panInertia !== null) ? panInertia : 0.5;\n }\n\n /**\n * Gets the pan inertia factor.\n *\n * Default is ````0.5````.\n *\n * @returns {Number} The current pan inertia factor.\n */\n get panInertia() {\n return this._configs.panInertia;\n }\n\n /**\n * Sets the keyboard layout.\n *\n * Supported layouts are:\n *\n * * ````\"qwerty\"```` (default)\n * * ````\"azerty\"````\n *\n * @deprecated\n * @param {String} value Selects the keyboard layout.\n */\n set keyboardLayout(value) {\n // this.warn(\"keyboardLayout property is deprecated - use keyMap property instead\");\n value = value || \"qwerty\";\n if (value !== \"qwerty\" && value !== \"azerty\") {\n this.error(\"Unsupported value for keyboardLayout - defaulting to 'qwerty'\");\n value = \"qwerty\";\n }\n this._configs.keyboardLayout = value;\n this.keyMap = this._configs.keyboardLayout;\n }\n\n /**\n * Gets the keyboard layout.\n *\n * Supported layouts are:\n *\n * * ````\"qwerty\"```` (default)\n * * ````\"azerty\"````\n *\n * @deprecated\n * @returns {String} The current keyboard layout.\n */\n get keyboardLayout() {\n return this._configs.keyboardLayout;\n }\n\n /**\n * Sets a sphere as the representation of the pivot position.\n *\n * @param {Object} [cfg] Sphere configuration.\n * @param {String} [cfg.size=1] Optional size factor of the sphere. Defaults to 1.\n * @param {String} [cfg.material=PhongMaterial] Optional size factor of the sphere. Defaults to a red opaque material.\n */\n enablePivotSphere(cfg = {}) {\n this._controllers.pivotController.enablePivotSphere(cfg);\n }\n\n /**\n * Remove the sphere as the representation of the pivot position.\n *\n */\n disablePivotSphere() {\n this._controllers.pivotController.disablePivotSphere();\n }\n \n /**\n * Sets whether smart default pivoting is enabled.\n *\n * When ````true````, we'll pivot by default about the 3D position of the mouse/touch pointer on an\n * imaginary sphere that's centered at {@link Camera#eye} and sized to the {@link Scene} boundary.\n *\n * When ````false````, we'll pivot by default about {@link Camera#look}.\n *\n * Default is ````false````.\n *\n * @param {Boolean} enabled Set ````true```` to pivot by default about the selected point on the virtual sphere, or ````false```` to pivot by default about {@link Camera#look}.\n */\n set smartPivot(enabled) {\n this._configs.smartPivot = (enabled !== false);\n }\n\n /**\n * Gets whether smart default pivoting is enabled.\n *\n * When ````true````, we'll pivot by default about the 3D position of the mouse/touch pointer on an\n * imaginary sphere that's centered at {@link Camera#eye} and sized to the {@link Scene} boundary.\n *\n * When ````false````, we'll pivot by default about {@link Camera#look}.\n *\n * Default is ````false````.\n *\n * @returns {Boolean} Returns ````true```` when pivoting by default about the selected point on the virtual sphere, or ````false```` when pivoting by default about {@link Camera#look}.\n */\n get smartPivot() {\n return this._configs.smartPivot;\n }\n\n /**\n * Sets the double click time frame length in milliseconds.\n * \n * If two mouse click events occur within this time frame, it is considered a double click. \n * \n * Default is ````250````\n * \n * @param {Number} value New double click time frame.\n */\n set doubleClickTimeFrame(value) {\n this._configs.doubleClickTimeFrame = (value !== undefined && value !== null) ? value : 250;\n }\n\n /**\n * Gets the double click time frame length in milliseconds.\n * \n * Default is ````250````\n * \n * @param {Number} value Current double click time frame.\n */\n get doubleClickTimeFrame() {\n return this._configs.doubleClickTimeFrame;\n }\n\n /**\n * Destroys this ````CameraControl````.\n * @private\n */\n destroy() {\n this._destroyHandlers();\n this._destroyControllers();\n this._cameraUpdater.destroy();\n super.destroy();\n }\n\n _destroyHandlers() {\n for (let i = 0, len = this._handlers.length; i < len; i++) {\n const handler = this._handlers[i];\n if (handler.destroy) {\n handler.destroy();\n }\n }\n }\n\n _destroyControllers() {\n for (let i = 0, len = this._controllers.length; i < len; i++) {\n const controller = this._controllers[i];\n if (controller.destroy) {\n controller.destroy();\n }\n }\n }\n}\n\n/**\n * @desc A property within a {@link PropertySet}.\n *\n * @class Property\n */\nclass Property {\n\n /**\n * @private\n */\n constructor(name, value, type, valueType, description) {\n\n /**\n * The name of this property.\n *\n * @property name\n * @type {String}\n */\n this.name = name;\n\n /**\n * The type of this property.\n *\n * @property type\n * @type {Number|String}\n */\n this.type = type;\n\n /**\n * The value of this property.\n *\n * @property value\n * @type {*}\n */\n this.value = value;\n\n /**\n * The type of this property's value.\n *\n * @property valueType\n * @type {Number|String}\n */\n this.valueType = valueType;\n\n /**\n * Informative text to explain the property.\n *\n * @property name\n * @type {String}\n */\n this.description = description;\n }\n}\n\n/**\n * @desc A set of properties associated with one or more {@link MetaObject}s.\n *\n * A PropertySet is created within {@link MetaScene#createMetaModel} and belongs to a {@link MetaModel}.\n *\n * Each PropertySet is registered by {@link PropertySet#id} in {@link MetaScene#propertySets} and {@link MetaModel#propertySets}.\n *\n * @class PropertySet\n */\nclass PropertySet {\n\n /**\n * @private\n */\n constructor(params) {\n\n /**\n * Globally-unique ID for this PropertySet.\n *\n * PropertySet instances are registered by this ID in {@link MetaScene#propertySets} and {@link MetaModel#propertySets}.\n *\n * @property id\n * @type {String}\n */\n this.id = params.id;\n\n /**\n * ID of the corresponding object within the originating system, if any.\n *\n * @type {String}\n * @abstract\n */\n this.originalSystemId = params.originalSystemId;\n\n /**\n * The MetaModels that share this PropertySet.\n * @type {MetaModel[]}\n */\n this.metaModels = [];\n\n /**\n * Human-readable name of this PropertySet.\n *\n * @property name\n * @type {String}\n */\n this.name = params.name;\n\n /**\n * Type of this PropertySet.\n *\n * @property type\n * @type {String}\n */\n this.type = params.type;\n\n /**\n * Properties within this PropertySet.\n *\n * @property properties\n * @type {Property[]}\n */\n this.properties = [];\n\n if (params.properties) {\n const properties = params.properties;\n for (let i = 0, len = properties.length; i < len; i++) {\n const property = properties[i];\n this.properties.push(new Property(property.name, property.value, property.type, property.valueType, property.description));\n }\n }\n }\n}\n\n/**\n * @desc Metadata corresponding to an {@link Entity} that represents an object.\n *\n * An {@link Entity} represents an object when {@link Entity#isObject} is ````true````\n *\n * A MetaObject corresponds to an {@link Entity} by having the same {@link MetaObject#id} as the {@link Entity#id}.\n *\n * A MetaObject is created within {@link MetaScene#createMetaModel} and belongs to a {@link MetaModel}.\n *\n * Each MetaObject is registered by {@link MetaObject#id} in {@link MetaScene#metaObjects}.\n *\n * A {@link MetaModel} represents its object structure with a tree of MetaObjects, with {@link MetaModel#rootMetaObject} referencing\n * the root MetaObject.\n *\n * @class MetaObject\n */\nclass MetaObject {\n\n /**\n * @private\n */\n constructor(params) {\n\n /**\n * The MetaModels that share this MetaObject.\n * @type {MetaModel[]}\n */\n this.metaModels = [];\n\n /**\n * Globally-unique ID.\n *\n * MetaObject instances are registered by this ID in {@link MetaScene#metaObjects}.\n *\n * @property id\n * @type {String|Number}\n */\n this.id = params.id;\n\n /**\n * ID of the parent MetaObject.\n * @type {String|Number}\n */\n this.parentId = params.parentId;\n\n /**\n * The parent MetaObject.\n * @type {MetaObject | null}\n */\n this.parent = null;\n\n /**\n * ID of the corresponding object within the originating system, if any.\n *\n * @type {String}\n * @abstract\n */\n this.originalSystemId = params.originalSystemId;\n\n /**\n * Human-readable name.\n *\n * @property name\n * @type {String}\n */\n this.name = params.name;\n\n /**\n * Type - often an IFC product type.\n *\n * @property type\n * @type {String}\n */\n this.type = params.type;\n\n /**\n * IDs of PropertySets associated with this MetaObject.\n * @type {[]|*}\n */\n this.propertySetIds = params.propertySetIds;\n\n /**\n * The {@link PropertySet}s associated with this MetaObject.\n *\n * @property propertySets\n * @type {PropertySet[]}\n */\n this.propertySets = [];\n\n /**\n * The attributes of this MetaObject.\n * @type {{}}\n */\n this.attributes = params.attributes || {};\n\n if (params.external !== undefined && params.external !== null) {\n \n /**\n * External application-specific metadata\n *\n * Undefined when there are is no external application-specific metadata.\n *\n * @property external\n * @type {*}\n */\n this.external = params.external;\n }\n }\n\n /**\n * Backwards compatibility with the object belonging to a single MetaModel.\n * \n * @property metaModel\n * @type {MetaModel|null}\n **/\n get metaModel() {\n if (this.metaModels.length == 1) {\n return this.metaModels[0];\n }\n\n return null;\n }\n\n /**\n * Gets the {@link MetaObject#id}s of the {@link MetaObject}s within the subtree.\n *\n * @returns {String[]} Array of {@link MetaObject#id}s.\n */\n getObjectIDsInSubtree() {\n const objectIds = [];\n\n function visit(metaObject) {\n if (!metaObject) {\n return;\n }\n objectIds.push(metaObject.id);\n const children = metaObject.children;\n if (children) {\n for (var i = 0, len = children.length; i < len; i++) {\n visit(children[i]);\n }\n }\n }\n\n visit(this);\n return objectIds;\n }\n\n\n /**\n * Iterates over the {@link MetaObject}s within the subtree.\n *\n * @param {Function} callback Callback fired at each {@link MetaObject}.\n */\n withMetaObjectsInSubtree(callback) {\n\n function visit(metaObject) {\n if (!metaObject) {\n return;\n }\n callback(metaObject);\n const children = metaObject.children;\n if (children) {\n for (var i = 0, len = children.length; i < len; i++) {\n visit(children[i]);\n }\n }\n }\n\n visit(this);\n }\n\n /**\n * Gets the {@link MetaObject#id}s of the {@link MetaObject}s within the subtree that have the given {@link MetaObject#type}s.\n *\n * @param {String[]} types {@link MetaObject#type} values.\n * @returns {String[]} Array of {@link MetaObject#id}s.\n */\n getObjectIDsInSubtreeByType(types) {\n const mask = {};\n for (var i = 0, len = types.length; i < len; i++) {\n mask[types[i]] = types[i];\n }\n const objectIds = [];\n\n function visit(metaObject) {\n if (!metaObject) {\n return;\n }\n if (mask[metaObject.type]) {\n objectIds.push(metaObject.id);\n }\n const children = metaObject.children;\n if (children) {\n for (var i = 0, len = children.length; i < len; i++) {\n visit(children[i]);\n }\n }\n }\n\n visit(this);\n return objectIds;\n }\n\n /**\n * Returns properties of this MeteObject as JSON.\n *\n * @returns {{id: (String|Number), type: String, name: String, parent: (String|Number|Undefined)}}\n */\n getJSON() {\n var json = {\n id: this.id,\n type: this.type,\n name: this.name\n };\n if (this.parent) {\n json.parent = this.parent.id;\n }\n return json;\n }\n}\n\n/**\n * @desc Metadata corresponding to an {@link Entity} that represents a model.\n *\n * An {@link Entity} represents a model when {@link Entity#isModel} is ````true````\n *\n * A MetaModel corresponds to an {@link Entity} by having the same {@link MetaModel#id} as the {@link Entity#id}.\n *\n * A MetaModel is created by {@link MetaScene#createMetaModel} and belongs to a {@link MetaScene}.\n *\n * Each MetaModel is registered by {@link MetaModel#id} in {@link MetaScene#metaModels}.\n *\n * A {@link MetaModel} represents its object structure with a tree of {@link MetaObject}s, with {@link MetaModel#rootMetaObject} referencing the root {@link MetaObject}.\n *\n * @class MetaModel\n */\nclass MetaModel {\n\n /**\n * Creates a new, unfinalized MetaModel.\n *\n * * The MetaModel is immediately registered by {@link MetaModel#id} in {@link MetaScene#metaModels}, even though it's not yet populated.\n * * The MetaModel then needs to be populated with one or more calls to {@link metaModel#loadData}.\n * * As we populate it, the MetaModel will create {@link MetaObject}s and {@link PropertySet}s in itself, and in the MetaScene.\n * * When populated, call {@link MetaModel#finalize} to finish it off, which causes MetaScene to fire a \"metaModelCreated\" event.\n */\n constructor(params) {\n\n /**\n * Globally-unique ID.\n *\n * MetaModels are registered by ID in {@link MetaScene#metaModels}.\n *\n * When this MetaModel corresponds to an {@link Entity} then this ID will match the {@link Entity#id}.\n *\n * @property id\n * @type {String|Number}\n */\n this.id = params.id;\n\n /**\n * The project ID\n * @property projectId\n * @type {String|Number}\n */\n this.projectId = params.projectId;\n\n /**\n * The revision ID, if available.\n *\n * Will be undefined if not available.\n *\n * @property revisionId\n * @type {String|Number}\n */\n this.revisionId = params.revisionId;\n\n /**\n * The model author, if available.\n *\n * Will be undefined if not available.\n *\n * @property author\n * @type {String}\n */\n this.author = params.author;\n\n /**\n * The date the model was created, if available.\n *\n * Will be undefined if not available.\n *\n * @property createdAt\n * @type {String}\n */\n this.createdAt = params.createdAt;\n\n /**\n * The application that created the model, if available.\n *\n * Will be undefined if not available.\n *\n * @property creatingApplication\n * @type {String}\n */\n this.creatingApplication = params.creatingApplication;\n\n /**\n * The model schema version, if available.\n *\n * Will be undefined if not available.\n *\n * @property schema\n * @type {String}\n */\n this.schema = params.schema;\n\n /**\n * Metadata on the {@link Scene}.\n *\n * @property metaScene\n * @type {MetaScene}\n */\n this.metaScene = params.metaScene;\n\n /**\n * The {@link PropertySet}s in this MetaModel.\n *\n * @property propertySets\n * @type {PropertySet[]}\n */\n this.propertySets = [];\n\n /**\n * The root {@link MetaObject}s in this MetaModel's composition structure hierarchy.\n *\n * @property rootMetaObject\n * @type {MetaObject[]}\n */\n this.rootMetaObjects = [];\n\n /**\n * The {@link MetaObject}s in this MetaModel, each mapped to its ID.\n *\n * @property metaObjects\n * @type {MetaObject[]}\n */\n this.metaObjects = [];\n\n /**\n * Connectivity graph.\n * @type {{}}\n */\n this.graph = params.graph || {};\n\n this.metaScene.metaModels[this.id] = this;\n\n /**\n * True when this MetaModel has been finalized.\n * @type {boolean}\n */\n this.finalized = false;\n }\n\n /**\n * Backwards compatibility with the model having a single root MetaObject.\n *\n * @property rootMetaObject\n * @type {MetaObject|null}\n */\n get rootMetaObject() {\n if (this.rootMetaObjects.length == 1) {\n return this.rootMetaObjects[0];\n }\n return null;\n }\n\n /**\n * Load metamodel data into this MetaModel.\n * @param metaModelData\n */\n loadData(metaModelData, options = {}) {\n\n if (this.finalized) {\n throw \"MetaScene already finalized - can't add more data\";\n }\n\n this._globalizeIDs(metaModelData, options);\n\n const metaScene = this.metaScene;\n const propertyLookup = metaModelData.properties;\n\n // Create global Property Sets\n\n if (metaModelData.propertySets) {\n for (let i = 0, len = metaModelData.propertySets.length; i < len; i++) {\n const propertySetData = metaModelData.propertySets[i];\n if (!propertySetData.properties) { // HACK: https://github.com/Creoox/creoox-ifc2gltfcxconverter/issues/8\n propertySetData.properties = [];\n }\n let propertySet = metaScene.propertySets[propertySetData.id];\n if (!propertySet) {\n if (propertyLookup) {\n this._decompressProperties(propertyLookup, propertySetData.properties);\n }\n propertySet = new PropertySet({\n id: propertySetData.id,\n originalSystemId: propertySetData.originalSystemId || propertySetData.id,\n type: propertySetData.type,\n name: propertySetData.name,\n properties: propertySetData.properties\n });\n metaScene.propertySets[propertySet.id] = propertySet;\n }\n propertySet.metaModels.push(this);\n this.propertySets.push(propertySet);\n }\n }\n\n if (metaModelData.metaObjects) {\n for (let i = 0, len = metaModelData.metaObjects.length; i < len; i++) {\n const metaObjectData = metaModelData.metaObjects[i];\n const id = metaObjectData.id;\n let metaObject = metaScene.metaObjects[id];\n if (!metaObject) {\n const type = metaObjectData.type;\n const originalSystemId = metaObjectData.originalSystemId;\n const propertySetIds = metaObjectData.propertySets || metaObjectData.propertySetIds;\n metaObject = new MetaObject({\n id,\n originalSystemId,\n parentId: metaObjectData.parent,\n type,\n name: metaObjectData.name,\n attributes: metaObjectData.attributes,\n propertySetIds,\n external: metaObjectData.external,\n });\n this.metaScene.metaObjects[id] = metaObject;\n metaObject.metaModels = [];\n }\n this.metaObjects.push(metaObject);\n if (!metaObjectData.parent) {\n this.rootMetaObjects.push(metaObject);\n metaScene.rootMetaObjects[id] = metaObject;\n }\n }\n }\n }\n\n _decompressProperties(propertyLookup, properties) {\n for (let i = 0, len = properties.length; i < len; i++) {\n const property = properties[i];\n if (Number.isInteger(property)) {\n const lookupProperty = propertyLookup[property];\n if (lookupProperty) {\n properties[i] = lookupProperty;\n }\n }\n }\n }\n\n finalize() {\n\n if (this.finalized) {\n throw \"MetaScene already finalized - can't re-finalize\";\n }\n\n // Re-link MetaScene's entire MetaObject parent/child hierarchy\n\n const metaScene = this.metaScene;\n\n for (let objectId in metaScene.metaObjects) {\n const metaObject = metaScene.metaObjects[objectId];\n if (metaObject.children) {\n metaObject.children = [];\n }\n\n // Re-link each MetaObject's property sets\n\n if (metaObject.propertySets) {\n metaObject.propertySets = [];\n }\n if (metaObject.propertySetIds) {\n for (let i = 0, len = metaObject.propertySetIds.length; i < len; i++) {\n const propertySetId = metaObject.propertySetIds[i];\n const propertySet = metaScene.propertySets[propertySetId];\n metaObject.propertySets.push(propertySet);\n }\n }\n }\n\n for (let objectId in metaScene.metaObjects) {\n const metaObject = metaScene.metaObjects[objectId];\n if (metaObject.parentId) {\n const parentMetaObject = metaScene.metaObjects[metaObject.parentId];\n if (parentMetaObject) {\n metaObject.parent = parentMetaObject;\n (parentMetaObject.children || (parentMetaObject.children = [])).push(metaObject);\n }\n }\n }\n\n // Relink MetaObjects to their MetaModels\n\n for (let objectId in metaScene.metaObjects) {\n const metaObject = metaScene.metaObjects[objectId];\n metaObject.metaModels = [];\n }\n\n for (let modelId in metaScene.metaModels) {\n const metaModel = metaScene.metaModels[modelId];\n for (let i = 0, len = metaModel.metaObjects.length; i < len; i++) {\n const metaObject = metaModel.metaObjects[i];\n metaObject.metaModels.push(metaModel);\n }\n }\n\n // Rebuild MetaScene's MetaObjects-by-type lookup\n\n metaScene.metaObjectsByType = {};\n for (let objectId in metaScene.metaObjects) {\n const metaObject = metaScene.metaObjects[objectId];\n const type = metaObject.type;\n (metaScene.metaObjectsByType[type] || (metaScene.metaObjectsByType[type] = {}))[objectId] = metaObject;\n }\n\n this.finalized = true;\n\n this.metaScene.fire(\"metaModelCreated\", this.id);\n }\n\n /**\n * Gets this MetaModel as JSON.\n * @returns {{schema: (String|string|*), createdAt: (String|string|*), metaObjects: *[], author: (String|string|*), id: (String|Number|string|number|*), creatingApplication: (String|string|*), projectId: (String|Number|string|number|*), propertySets: *[]}}\n */\n getJSON() {\n const json = {\n id: this.id,\n projectId: this.projectId,\n author: this.author,\n createdAt: this.createdAt,\n schema: this.schema,\n creatingApplication: this.creatingApplication,\n metaObjects: [],\n propertySets: []\n };\n for (let i = 0, len = this.metaObjects.length; i < len; i++) {\n const metaObject = this.metaObjects[i];\n const metaObjectCfg = {\n id: metaObject.id,\n originalSystemId: metaObject.originalSystemId,\n extId: metaObject.extId,\n type: metaObject.type,\n name: metaObject.name\n };\n if (metaObject.parent) {\n metaObjectCfg.parent = metaObject.parent.id;\n }\n if (metaObject.attributes) {\n metaObjectCfg.attributes = metaObject.attributes;\n }\n if (metaObject.propertySetIds) {\n metaObjectCfg.propertySetIds = metaObject.propertySetIds;\n }\n json.metaObjects.push(metaObjectCfg);\n }\n for (let i = 0, len = this.propertySets.length; i < len; i++) {\n const propertySet = this.propertySets[i];\n const propertySetCfg = {\n id: propertySet.id,\n originalSystemId: propertySet.originalSystemId,\n extId: propertySet.extId,\n type: propertySet.type,\n name: propertySet.name,\n propertyies: []\n };\n for (let j = 0, lenj = propertySet.properties.length; j < lenj; j++) {\n const property = propertySet.properties[j];\n const propertyCfg = {\n id: property.id,\n description: property.description,\n type: property.type,\n name: property.name,\n value: property.value,\n valueType: property.valueType\n };\n propertySetCfg.properties.push(propertyCfg);\n }\n json.propertySets.push(propertySetCfg);\n }\n return json;\n }\n\n _globalizeIDs(metaModelData, options) {\n\n const globalize = !!options.globalizeObjectIds;\n\n if (metaModelData.metaObjects) {\n for (let i = 0, len = metaModelData.metaObjects.length; i < len; i++) {\n const metaObjectData = metaModelData.metaObjects[i];\n\n // Globalize MetaObject IDs and parent IDs\n\n metaObjectData.originalSystemId = metaObjectData.id;\n if (metaObjectData.parent) {\n metaObjectData.originalParentSystemId = metaObjectData.parent;\n }\n if (globalize) {\n metaObjectData.id = math.globalizeObjectId(this.id, metaObjectData.id);\n if (metaObjectData.parent) {\n metaObjectData.parent = math.globalizeObjectId(this.id, metaObjectData.parent);\n }\n }\n\n // Globalize MetaObject property set IDs\n\n if (globalize) {\n const propertySetIds = metaObjectData.propertySetIds;\n if (propertySetIds) {\n const propertySetGlobalIds = [];\n for (let j = 0, lenj = propertySetIds.length; j < lenj; j++) {\n propertySetGlobalIds.push(math.globalizeObjectId(this.id, propertySetIds[j]));\n }\n metaObjectData.propertySetIds = propertySetGlobalIds;\n metaObjectData.originalSystemPropertySetIds = propertySetIds;\n }\n } else {\n metaObjectData.originalSystemPropertySetIds = metaObjectData.propertySetIds;\n }\n }\n }\n\n // Globalize global PropertySet IDs\n\n if (metaModelData.propertySets) {\n for (let i = 0, len = metaModelData.propertySets.length; i < len; i++) {\n const propertySet = metaModelData.propertySets[i];\n propertySet.originalSystemId = propertySet.id;\n if (globalize) {\n propertySet.id = math.globalizeObjectId(this.id, propertySet.id);\n }\n }\n }\n }\n}\n\n/**\n * @desc Metadata corresponding to a {@link Scene}.\n *\n * * Located in {@link Viewer#metaScene}.\n * * Contains {@link MetaModel}s and {@link MetaObject}s.\n * * [Scene graph example with metadata](http://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneGraph_metadata)\n */\nclass MetaScene {\n\n /**\n * @private\n */\n constructor(viewer, scene) {\n\n /**\n * The {@link Viewer}.\n * @property viewer\n * @type {Viewer}\n */\n this.viewer = viewer;\n\n /**\n * The {@link Scene}.\n * @property scene\n * @type {Scene}\n */\n this.scene = scene;\n\n /**\n * The {@link MetaModel}s belonging to this MetaScene, each mapped to its {@link MetaModel#modelId}.\n *\n * @type {{String:MetaModel}}\n */\n this.metaModels = {};\n\n /**\n * The {@link PropertySet}s belonging to this MetaScene, each mapped to its {@link PropertySet#id}.\n *\n * @type {{String:PropertySet}}\n */\n this.propertySets = {};\n\n /**\n * The {@link MetaObject}s belonging to this MetaScene, each mapped to its {@link MetaObject#id}.\n *\n * @type {{String:MetaObject}}\n */\n this.metaObjects = {};\n\n /**\n * The {@link MetaObject}s belonging to this MetaScene, each mapped to its {@link MetaObject#type}.\n *\n * @type {{String:MetaObject}}\n */\n this.metaObjectsByType = {};\n\n /**\n * The root {@link MetaObject}s belonging to this MetaScene, each mapped to its {@link MetaObject#id}.\n *\n * @type {{String:MetaObject}}\n */\n this.rootMetaObjects = {};\n\n /**\n * Subscriptions to events sent with {@link fire}.\n * @private\n */\n this._eventSubs = {};\n }\n\n /**\n * Subscribes to an event fired at this Viewer.\n *\n * @param {String} event The event\n * @param {Function} callback Callback fired on the event\n */\n on(event, callback) {\n let subs = this._eventSubs[event];\n if (!subs) {\n subs = [];\n this._eventSubs[event] = subs;\n }\n subs.push(callback);\n }\n\n /**\n * Fires an event at this Viewer.\n *\n * @param {String} event Event name\n * @param {Object} value Event parameters\n */\n fire(event, value) {\n const subs = this._eventSubs[event];\n if (subs) {\n for (let i = 0, len = subs.length; i < len; i++) {\n subs[i](value);\n }\n }\n }\n\n /**\n * Unsubscribes from an event fired at this Viewer.\n * @param event\n */\n off(event) { // TODO\n\n }\n\n /**\n * Creates a {@link MetaModel} in this MetaScene.\n *\n * The MetaModel will contain a hierarchy of {@link MetaObject}s, created from the\n * meta objects in ````metaModelData````.\n *\n * The meta object hierarchy in ````metaModelData```` is expected to be non-cyclic, with a single root. If the meta\n * objects are cyclic, then this method will log an error and attempt to recover by creating a dummy root MetaObject\n * of type \"Model\" and connecting all other MetaObjects as its direct children. If the meta objects contain multiple\n * roots, then this method similarly attempts to recover by creating a dummy root MetaObject of type \"Model\" and\n * connecting all the root MetaObjects as its children.\n *\n * @param {String} modelId ID for the new {@link MetaModel}, which will have {@link MetaModel#id} set to this value.\n * @param {Object} metaModelData Data for the {@link MetaModel}.\n * @param {Object} [options] Options for creating the {@link MetaModel}.\n * @param {Object} [options.includeTypes] When provided, only create {@link MetaObject}s with types in this list.\n * @param {Object} [options.excludeTypes] When provided, never create {@link MetaObject}s with types in this list.\n * @param {Boolean} [options.globalizeObjectIds=false] Whether to globalize each {@link MetaObject#id}. Set this ````true```` when you need to load multiple instances of the same meta model, to avoid ID clashes between the meta objects in the different instances.\n * @returns {MetaModel} The new MetaModel.\n */\n createMetaModel(modelId, metaModelData, options = {}) {\n\n const metaModel = new MetaModel({ // Registers MetaModel in #metaModels\n metaScene: this,\n id: modelId,\n projectId: metaModelData.projectId || \"none\",\n revisionId: metaModelData.revisionId || \"none\",\n author: metaModelData.author || \"none\",\n createdAt: metaModelData.createdAt || \"none\",\n creatingApplication: metaModelData.creatingApplication || \"none\",\n schema: metaModelData.schema || \"none\",\n propertySets: []\n });\n\n metaModel.loadData(metaModelData);\n\n metaModel.finalize();\n\n return metaModel;\n }\n\n /**\n * Removes a {@link MetaModel} from this MetaScene.\n *\n * Fires a \"metaModelDestroyed\" event with the value of the {@link MetaModel#id}.\n *\n * @param {String} metaModelId ID of the target {@link MetaModel}.\n */\n destroyMetaModel(metaModelId) {\n\n const metaModel = this.metaModels[metaModelId];\n if (!metaModel) {\n return;\n }\n\n // Remove global PropertySets\n\n if (metaModel.propertySets) {\n for (let i = 0, len = metaModel.propertySets.length; i < len; i++) {\n const propertySet = metaModel.propertySets[i];\n if (propertySet.metaModels.length === 1 && propertySet.metaModels[0].id === metaModelId) { // Property set owned only by this model, delete\n delete this.propertySets[propertySet.id];\n } else {\n const newMetaModels = [];\n for (let j = 0, lenj = propertySet.metaModels.length; j < lenj; j++) {\n if (propertySet.metaModels[j].id !== metaModelId) {\n newMetaModels.push(propertySet.metaModels[j]);\n }\n }\n propertySet.metaModels = newMetaModels;\n }\n }\n }\n\n // Remove MetaObjects\n\n if (metaModel.metaObjects) {\n for (let i = 0, len = metaModel.metaObjects.length; i < len; i++) {\n const metaObject = metaModel.metaObjects[i];\n metaObject.type;\n const id = metaObject.id;\n if (metaObject.metaModels.length === 1&& metaObject.metaModels[0].id === metaModelId) { // MetaObject owned only by this model, delete\n delete this.metaObjects[id];\n if (!metaObject.parent) {\n delete this.rootMetaObjects[id];\n }\n }\n }\n }\n\n // Re-link entire MetaObject parent/child hierarchy\n\n for (let objectId in this.metaObjects) {\n const metaObject = this.metaObjects[objectId];\n if (metaObject.children) {\n metaObject.children = [];\n }\n\n // Re-link each MetaObject's property sets\n\n if (metaObject.propertySets) {\n metaObject.propertySets = [];\n }\n if (metaObject.propertySetIds) {\n for (let i = 0, len = metaObject.propertySetIds.length; i < len; i++) {\n const propertySetId = metaObject.propertySetIds[i];\n const propertySet = this.propertySets[propertySetId];\n metaObject.propertySets.push(propertySet);\n }\n }\n }\n\n this.metaObjectsByType = {};\n\n for (let objectId in this.metaObjects) {\n const metaObject = this.metaObjects[objectId];\n const type = metaObject.type;\n if (metaObject.children) {\n metaObject.children = null;\n }\n (this.metaObjectsByType[type] || (this.metaObjectsByType[type] = {}))[objectId] = metaObject;\n }\n\n for (let objectId in this.metaObjects) {\n const metaObject = this.metaObjects[objectId];\n if (metaObject.parentId) {\n const parentMetaObject = this.metaObjects[metaObject.parentId];\n if (parentMetaObject) {\n metaObject.parent = parentMetaObject;\n (parentMetaObject.children || (parentMetaObject.children = [])).push(metaObject);\n }\n }\n }\n\n delete this.metaModels[metaModelId];\n\n // Relink MetaObjects to their MetaModels\n\n for (let objectId in this.metaObjects) {\n const metaObject = this.metaObjects[objectId];\n metaObject.metaModels = [];\n }\n\n for (let modelId in this.metaModels) {\n const metaModel = this.metaModels[modelId];\n for (let i = 0, len = metaModel.metaObjects.length; i < len; i++) {\n const metaObject = metaModel.metaObjects[i];\n metaObject.metaModels.push(metaModel);\n }\n }\n\n this.fire(\"metaModelDestroyed\", metaModelId);\n }\n\n /**\n * Gets the {@link MetaObject#id}s of the {@link MetaObject}s that have the given {@link MetaObject#type}.\n *\n * @param {String} type The type.\n * @returns {String[]} Array of {@link MetaObject#id}s.\n */\n getObjectIDsByType(type) {\n const metaObjects = this.metaObjectsByType[type];\n return metaObjects ? Object.keys(metaObjects) : [];\n }\n\n /**\n * Gets the {@link MetaObject#id}s of the {@link MetaObject}s within the given subtree.\n *\n * @param {String} id ID of the root {@link MetaObject} of the given subtree.\n * @param {String[]} [includeTypes] Optional list of types to include.\n * @param {String[]} [excludeTypes] Optional list of types to exclude.\n * @returns {String[]} Array of {@link MetaObject#id}s.\n */\n getObjectIDsInSubtree(id, includeTypes, excludeTypes) {\n\n const list = [];\n const metaObject = this.metaObjects[id];\n const includeMask = (includeTypes && includeTypes.length > 0) ? arrayToMap(includeTypes) : null;\n const excludeMask = (excludeTypes && excludeTypes.length > 0) ? arrayToMap(excludeTypes) : null;\n\n const visit = (metaObject) => {\n if (!metaObject) {\n return;\n }\n var include = true;\n if (excludeMask && excludeMask[metaObject.type]) {\n include = false;\n } else if (includeMask && (!includeMask[metaObject.type])) {\n include = false;\n }\n if (include) {\n list.push(metaObject.id);\n }\n const children = metaObject.children;\n if (children) {\n for (var i = 0, len = children.length; i < len; i++) {\n visit(children[i]);\n }\n }\n };\n\n visit(metaObject);\n return list;\n }\n\n /**\n * Iterates over the {@link MetaObject}s within the subtree.\n *\n * @param {String} id ID of root {@link MetaObject}.\n * @param {Function} callback Callback fired at each {@link MetaObject}.\n */\n withMetaObjectsInSubtree(id, callback) {\n const metaObject = this.metaObjects[id];\n if (!metaObject) {\n return;\n }\n metaObject.withMetaObjectsInSubtree(callback);\n }\n}\n\nfunction arrayToMap(array) {\n const map = {};\n for (var i = 0, len = array.length; i < len; i++) {\n map[array[i]] = true;\n }\n return map;\n}\n\n/*!\n * html2canvas 1.4.1 \n * Copyright (c) 2022 Niklas von Hertzen \n * Released under MIT License\n */\n/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nfunction __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nvar __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n };\r\n return __assign.apply(this, arguments);\r\n};\r\n\r\nfunction __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nfunction __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nfunction __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || from);\r\n}\n\nvar Bounds = /** @class */ (function () {\n function Bounds(left, top, width, height) {\n this.left = left;\n this.top = top;\n this.width = width;\n this.height = height;\n }\n Bounds.prototype.add = function (x, y, w, h) {\n return new Bounds(this.left + x, this.top + y, this.width + w, this.height + h);\n };\n Bounds.fromClientRect = function (context, clientRect) {\n return new Bounds(clientRect.left + context.windowBounds.left, clientRect.top + context.windowBounds.top, clientRect.width, clientRect.height);\n };\n Bounds.fromDOMRectList = function (context, domRectList) {\n var domRect = Array.from(domRectList).find(function (rect) { return rect.width !== 0; });\n return domRect\n ? new Bounds(domRect.left + context.windowBounds.left, domRect.top + context.windowBounds.top, domRect.width, domRect.height)\n : Bounds.EMPTY;\n };\n Bounds.EMPTY = new Bounds(0, 0, 0, 0);\n return Bounds;\n}());\nvar parseBounds = function (context, node) {\n return Bounds.fromClientRect(context, node.getBoundingClientRect());\n};\nvar parseDocumentSize = function (document) {\n var body = document.body;\n var documentElement = document.documentElement;\n if (!body || !documentElement) {\n throw new Error(\"Unable to get document size\");\n }\n var width = Math.max(Math.max(body.scrollWidth, documentElement.scrollWidth), Math.max(body.offsetWidth, documentElement.offsetWidth), Math.max(body.clientWidth, documentElement.clientWidth));\n var height = Math.max(Math.max(body.scrollHeight, documentElement.scrollHeight), Math.max(body.offsetHeight, documentElement.offsetHeight), Math.max(body.clientHeight, documentElement.clientHeight));\n return new Bounds(0, 0, width, height);\n};\n\n/*\n * css-line-break 2.1.0 \n * Copyright (c) 2022 Niklas von Hertzen \n * Released under MIT License\n */\nvar toCodePoints$1 = function (str) {\n var codePoints = [];\n var i = 0;\n var length = str.length;\n while (i < length) {\n var value = str.charCodeAt(i++);\n if (value >= 0xd800 && value <= 0xdbff && i < length) {\n var extra = str.charCodeAt(i++);\n if ((extra & 0xfc00) === 0xdc00) {\n codePoints.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);\n }\n else {\n codePoints.push(value);\n i--;\n }\n }\n else {\n codePoints.push(value);\n }\n }\n return codePoints;\n};\nvar fromCodePoint$1 = function () {\n var codePoints = [];\n for (var _i = 0; _i < arguments.length; _i++) {\n codePoints[_i] = arguments[_i];\n }\n if (String.fromCodePoint) {\n return String.fromCodePoint.apply(String, codePoints);\n }\n var length = codePoints.length;\n if (!length) {\n return '';\n }\n var codeUnits = [];\n var index = -1;\n var result = '';\n while (++index < length) {\n var codePoint = codePoints[index];\n if (codePoint <= 0xffff) {\n codeUnits.push(codePoint);\n }\n else {\n codePoint -= 0x10000;\n codeUnits.push((codePoint >> 10) + 0xd800, (codePoint % 0x400) + 0xdc00);\n }\n if (index + 1 === length || codeUnits.length > 0x4000) {\n result += String.fromCharCode.apply(String, codeUnits);\n codeUnits.length = 0;\n }\n }\n return result;\n};\nvar chars$2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n// Use a lookup table to find the index.\nvar lookup$2 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);\nfor (var i$2 = 0; i$2 < chars$2.length; i$2++) {\n lookup$2[chars$2.charCodeAt(i$2)] = i$2;\n}\n\n/*\n * utrie 1.0.2 \n * Copyright (c) 2022 Niklas von Hertzen \n * Released under MIT License\n */\nvar chars$1$1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n// Use a lookup table to find the index.\nvar lookup$1$1 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);\nfor (var i$1$1 = 0; i$1$1 < chars$1$1.length; i$1$1++) {\n lookup$1$1[chars$1$1.charCodeAt(i$1$1)] = i$1$1;\n}\nvar decode$1$1 = function (base64) {\n var bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;\n if (base64[base64.length - 1] === '=') {\n bufferLength--;\n if (base64[base64.length - 2] === '=') {\n bufferLength--;\n }\n }\n var buffer = typeof ArrayBuffer !== 'undefined' &&\n typeof Uint8Array !== 'undefined' &&\n typeof Uint8Array.prototype.slice !== 'undefined'\n ? new ArrayBuffer(bufferLength)\n : new Array(bufferLength);\n var bytes = Array.isArray(buffer) ? buffer : new Uint8Array(buffer);\n for (i = 0; i < len; i += 4) {\n encoded1 = lookup$1$1[base64.charCodeAt(i)];\n encoded2 = lookup$1$1[base64.charCodeAt(i + 1)];\n encoded3 = lookup$1$1[base64.charCodeAt(i + 2)];\n encoded4 = lookup$1$1[base64.charCodeAt(i + 3)];\n bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);\n bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);\n bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);\n }\n return buffer;\n};\nvar polyUint16Array$1 = function (buffer) {\n var length = buffer.length;\n var bytes = [];\n for (var i = 0; i < length; i += 2) {\n bytes.push((buffer[i + 1] << 8) | buffer[i]);\n }\n return bytes;\n};\nvar polyUint32Array$1 = function (buffer) {\n var length = buffer.length;\n var bytes = [];\n for (var i = 0; i < length; i += 4) {\n bytes.push((buffer[i + 3] << 24) | (buffer[i + 2] << 16) | (buffer[i + 1] << 8) | buffer[i]);\n }\n return bytes;\n};\n\n/** Shift size for getting the index-2 table offset. */\nvar UTRIE2_SHIFT_2$1 = 5;\n/** Shift size for getting the index-1 table offset. */\nvar UTRIE2_SHIFT_1$1 = 6 + 5;\n/**\n * Shift size for shifting left the index array values.\n * Increases possible data size with 16-bit index values at the cost\n * of compactability.\n * This requires data blocks to be aligned by UTRIE2_DATA_GRANULARITY.\n */\nvar UTRIE2_INDEX_SHIFT$1 = 2;\n/**\n * Difference between the two shift sizes,\n * for getting an index-1 offset from an index-2 offset. 6=11-5\n */\nvar UTRIE2_SHIFT_1_2$1 = UTRIE2_SHIFT_1$1 - UTRIE2_SHIFT_2$1;\n/**\n * The part of the index-2 table for U+D800..U+DBFF stores values for\n * lead surrogate code _units_ not code _points_.\n * Values for lead surrogate code _points_ are indexed with this portion of the table.\n * Length=32=0x20=0x400>>UTRIE2_SHIFT_2. (There are 1024=0x400 lead surrogates.)\n */\nvar UTRIE2_LSCP_INDEX_2_OFFSET$1 = 0x10000 >> UTRIE2_SHIFT_2$1;\n/** Number of entries in a data block. 32=0x20 */\nvar UTRIE2_DATA_BLOCK_LENGTH$1 = 1 << UTRIE2_SHIFT_2$1;\n/** Mask for getting the lower bits for the in-data-block offset. */\nvar UTRIE2_DATA_MASK$1 = UTRIE2_DATA_BLOCK_LENGTH$1 - 1;\nvar UTRIE2_LSCP_INDEX_2_LENGTH$1 = 0x400 >> UTRIE2_SHIFT_2$1;\n/** Count the lengths of both BMP pieces. 2080=0x820 */\nvar UTRIE2_INDEX_2_BMP_LENGTH$1 = UTRIE2_LSCP_INDEX_2_OFFSET$1 + UTRIE2_LSCP_INDEX_2_LENGTH$1;\n/**\n * The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820.\n * Length 32=0x20 for lead bytes C0..DF, regardless of UTRIE2_SHIFT_2.\n */\nvar UTRIE2_UTF8_2B_INDEX_2_OFFSET$1 = UTRIE2_INDEX_2_BMP_LENGTH$1;\nvar UTRIE2_UTF8_2B_INDEX_2_LENGTH$1 = 0x800 >> 6; /* U+0800 is the first code point after 2-byte UTF-8 */\n/**\n * The index-1 table, only used for supplementary code points, at offset 2112=0x840.\n * Variable length, for code points up to highStart, where the last single-value range starts.\n * Maximum length 512=0x200=0x100000>>UTRIE2_SHIFT_1.\n * (For 0x100000 supplementary code points U+10000..U+10ffff.)\n *\n * The part of the index-2 table for supplementary code points starts\n * after this index-1 table.\n *\n * Both the index-1 table and the following part of the index-2 table\n * are omitted completely if there is only BMP data.\n */\nvar UTRIE2_INDEX_1_OFFSET$1 = UTRIE2_UTF8_2B_INDEX_2_OFFSET$1 + UTRIE2_UTF8_2B_INDEX_2_LENGTH$1;\n/**\n * Number of index-1 entries for the BMP. 32=0x20\n * This part of the index-1 table is omitted from the serialized form.\n */\nvar UTRIE2_OMITTED_BMP_INDEX_1_LENGTH$1 = 0x10000 >> UTRIE2_SHIFT_1$1;\n/** Number of entries in an index-2 block. 64=0x40 */\nvar UTRIE2_INDEX_2_BLOCK_LENGTH$1 = 1 << UTRIE2_SHIFT_1_2$1;\n/** Mask for getting the lower bits for the in-index-2-block offset. */\nvar UTRIE2_INDEX_2_MASK$1 = UTRIE2_INDEX_2_BLOCK_LENGTH$1 - 1;\nvar slice16$1 = function (view, start, end) {\n if (view.slice) {\n return view.slice(start, end);\n }\n return new Uint16Array(Array.prototype.slice.call(view, start, end));\n};\nvar slice32$1 = function (view, start, end) {\n if (view.slice) {\n return view.slice(start, end);\n }\n return new Uint32Array(Array.prototype.slice.call(view, start, end));\n};\nvar createTrieFromBase64$1 = function (base64, _byteLength) {\n var buffer = decode$1$1(base64);\n var view32 = Array.isArray(buffer) ? polyUint32Array$1(buffer) : new Uint32Array(buffer);\n var view16 = Array.isArray(buffer) ? polyUint16Array$1(buffer) : new Uint16Array(buffer);\n var headerLength = 24;\n var index = slice16$1(view16, headerLength / 2, view32[4] / 2);\n var data = view32[5] === 2\n ? slice16$1(view16, (headerLength + view32[4]) / 2)\n : slice32$1(view32, Math.ceil((headerLength + view32[4]) / 4));\n return new Trie$1(view32[0], view32[1], view32[2], view32[3], index, data);\n};\nvar Trie$1 = /** @class */ (function () {\n function Trie(initialValue, errorValue, highStart, highValueIndex, index, data) {\n this.initialValue = initialValue;\n this.errorValue = errorValue;\n this.highStart = highStart;\n this.highValueIndex = highValueIndex;\n this.index = index;\n this.data = data;\n }\n /**\n * Get the value for a code point as stored in the Trie.\n *\n * @param codePoint the code point\n * @return the value\n */\n Trie.prototype.get = function (codePoint) {\n var ix;\n if (codePoint >= 0) {\n if (codePoint < 0x0d800 || (codePoint > 0x0dbff && codePoint <= 0x0ffff)) {\n // Ordinary BMP code point, excluding leading surrogates.\n // BMP uses a single level lookup. BMP index starts at offset 0 in the Trie2 index.\n // 16 bit data is stored in the index array itself.\n ix = this.index[codePoint >> UTRIE2_SHIFT_2$1];\n ix = (ix << UTRIE2_INDEX_SHIFT$1) + (codePoint & UTRIE2_DATA_MASK$1);\n return this.data[ix];\n }\n if (codePoint <= 0xffff) {\n // Lead Surrogate Code Point. A Separate index section is stored for\n // lead surrogate code units and code points.\n // The main index has the code unit data.\n // For this function, we need the code point data.\n // Note: this expression could be refactored for slightly improved efficiency, but\n // surrogate code points will be so rare in practice that it's not worth it.\n ix = this.index[UTRIE2_LSCP_INDEX_2_OFFSET$1 + ((codePoint - 0xd800) >> UTRIE2_SHIFT_2$1)];\n ix = (ix << UTRIE2_INDEX_SHIFT$1) + (codePoint & UTRIE2_DATA_MASK$1);\n return this.data[ix];\n }\n if (codePoint < this.highStart) {\n // Supplemental code point, use two-level lookup.\n ix = UTRIE2_INDEX_1_OFFSET$1 - UTRIE2_OMITTED_BMP_INDEX_1_LENGTH$1 + (codePoint >> UTRIE2_SHIFT_1$1);\n ix = this.index[ix];\n ix += (codePoint >> UTRIE2_SHIFT_2$1) & UTRIE2_INDEX_2_MASK$1;\n ix = this.index[ix];\n ix = (ix << UTRIE2_INDEX_SHIFT$1) + (codePoint & UTRIE2_DATA_MASK$1);\n return this.data[ix];\n }\n if (codePoint <= 0x10ffff) {\n return this.data[this.highValueIndex];\n }\n }\n // Fall through. The code point is outside of the legal range of 0..0x10ffff.\n return this.errorValue;\n };\n return Trie;\n}());\n\n/*\n * base64-arraybuffer 1.0.2 \n * Copyright (c) 2022 Niklas von Hertzen \n * Released under MIT License\n */\nvar chars$3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n// Use a lookup table to find the index.\nvar lookup$3 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);\nfor (var i$3 = 0; i$3 < chars$3.length; i$3++) {\n lookup$3[chars$3.charCodeAt(i$3)] = i$3;\n}\n\nvar base64$1 = 'KwAAAAAAAAAACA4AUD0AADAgAAACAAAAAAAIABAAGABAAEgAUABYAGAAaABgAGgAYgBqAF8AZwBgAGgAcQB5AHUAfQCFAI0AlQCdAKIAqgCyALoAYABoAGAAaABgAGgAwgDKAGAAaADGAM4A0wDbAOEA6QDxAPkAAQEJAQ8BFwF1AH0AHAEkASwBNAE6AUIBQQFJAVEBWQFhAWgBcAF4ATAAgAGGAY4BlQGXAZ8BpwGvAbUBvQHFAc0B0wHbAeMB6wHxAfkBAQIJAvEBEQIZAiECKQIxAjgCQAJGAk4CVgJeAmQCbAJ0AnwCgQKJApECmQKgAqgCsAK4ArwCxAIwAMwC0wLbAjAA4wLrAvMC+AIAAwcDDwMwABcDHQMlAy0DNQN1AD0DQQNJA0kDSQNRA1EDVwNZA1kDdQB1AGEDdQBpA20DdQN1AHsDdQCBA4kDkQN1AHUAmQOhA3UAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AKYDrgN1AHUAtgO+A8YDzgPWAxcD3gPjA+sD8wN1AHUA+wMDBAkEdQANBBUEHQQlBCoEFwMyBDgEYABABBcDSARQBFgEYARoBDAAcAQzAXgEgASIBJAEdQCXBHUAnwSnBK4EtgS6BMIEyAR1AHUAdQB1AHUAdQCVANAEYABgAGAAYABgAGAAYABgANgEYADcBOQEYADsBPQE/AQEBQwFFAUcBSQFLAU0BWQEPAVEBUsFUwVbBWAAYgVgAGoFcgV6BYIFigWRBWAAmQWfBaYFYABgAGAAYABgAKoFYACxBbAFuQW6BcEFwQXHBcEFwQXPBdMF2wXjBeoF8gX6BQIGCgYSBhoGIgYqBjIGOgZgAD4GRgZMBmAAUwZaBmAAYABgAGAAYABgAGAAYABgAGAAYABgAGIGYABpBnAGYABgAGAAYABgAGAAYABgAGAAYAB4Bn8GhQZgAGAAYAB1AHcDFQSLBmAAYABgAJMGdQA9A3UAmwajBqsGqwaVALMGuwbDBjAAywbSBtIG1QbSBtIG0gbSBtIG0gbdBuMG6wbzBvsGAwcLBxMHAwcbByMHJwcsBywHMQcsB9IGOAdAB0gHTgfSBkgHVgfSBtIG0gbSBtIG0gbSBtIG0gbSBiwHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAdgAGAALAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAdbB2MHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsB2kH0gZwB64EdQB1AHUAdQB1AHUAdQB1AHUHfQdgAIUHjQd1AHUAlQedB2AAYAClB6sHYACzB7YHvgfGB3UAzgfWBzMB3gfmB1EB7gf1B/0HlQENAQUIDQh1ABUIHQglCBcDLQg1CD0IRQhNCEEDUwh1AHUAdQBbCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIcAh3CHoIMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIgggwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAALAcsBywHLAcsBywHLAcsBywHLAcsB4oILAcsB44I0gaWCJ4Ipgh1AHUAqgiyCHUAdQB1AHUAdQB1AHUAdQB1AHUAtwh8AXUAvwh1AMUIyQjRCNkI4AjoCHUAdQB1AO4I9gj+CAYJDgkTCS0HGwkjCYIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiAAIAAAAFAAYABgAGIAXwBgAHEAdQBFAJUAogCyAKAAYABgAEIA4ABGANMA4QDxAMEBDwE1AFwBLAE6AQEBUQF4QkhCmEKoQrhCgAHIQsAB0MLAAcABwAHAAeDC6ABoAHDCwMMAAcABwAHAAdDDGMMAAcAB6MM4wwjDWMNow3jDaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAEjDqABWw6bDqABpg6gAaABoAHcDvwOPA+gAaABfA/8DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DpcPAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcAB9cPKwkyCToJMAB1AHUAdQBCCUoJTQl1AFUJXAljCWcJawkwADAAMAAwAHMJdQB2CX4JdQCECYoJjgmWCXUAngkwAGAAYABxAHUApgn3A64JtAl1ALkJdQDACTAAMAAwADAAdQB1AHUAdQB1AHUAdQB1AHUAowYNBMUIMAAwADAAMADICcsJ0wnZCRUE4QkwAOkJ8An4CTAAMAB1AAAKvwh1AAgKDwoXCh8KdQAwACcKLgp1ADYKqAmICT4KRgowADAAdQB1AE4KMAB1AFYKdQBeCnUAZQowADAAMAAwADAAMAAwADAAMAAVBHUAbQowADAAdQC5CXUKMAAwAHwBxAijBogEMgF9CoQKiASMCpQKmgqIBKIKqgquCogEDQG2Cr4KxgrLCjAAMADTCtsKCgHjCusK8Qr5CgELMAAwADAAMAB1AIsECQsRC3UANAEZCzAAMAAwADAAMAB1ACELKQswAHUANAExCzkLdQBBC0kLMABRC1kLMAAwADAAMAAwADAAdQBhCzAAMAAwAGAAYABpC3ELdwt/CzAAMACHC4sLkwubC58Lpwt1AK4Ltgt1APsDMAAwADAAMAAwADAAMAAwAL4LwwvLC9IL1wvdCzAAMADlC+kL8Qv5C/8LSQswADAAMAAwADAAMAAwADAAMAAHDDAAMAAwADAAMAAODBYMHgx1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1ACYMMAAwADAAdQB1AHUALgx1AHUAdQB1AHUAdQA2DDAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AD4MdQBGDHUAdQB1AHUAdQB1AEkMdQB1AHUAdQB1AFAMMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQBYDHUAdQB1AF8MMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUA+wMVBGcMMAAwAHwBbwx1AHcMfwyHDI8MMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAYABgAJcMMAAwADAAdQB1AJ8MlQClDDAAMACtDCwHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsB7UMLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AA0EMAC9DDAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAsBywHLAcsBywHLAcsBywHLQcwAMEMyAwsBywHLAcsBywHLAcsBywHLAcsBywHzAwwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAHUAdQB1ANQM2QzhDDAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMABgAGAAYABgAGAAYABgAOkMYADxDGAA+AwADQYNYABhCWAAYAAODTAAMAAwADAAFg1gAGAAHg37AzAAMAAwADAAYABgACYNYAAsDTQNPA1gAEMNPg1LDWAAYABgAGAAYABgAGAAYABgAGAAUg1aDYsGVglhDV0NcQBnDW0NdQ15DWAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAlQCBDZUAiA2PDZcNMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAnw2nDTAAMAAwADAAMAAwAHUArw23DTAAMAAwADAAMAAwADAAMAAwADAAMAB1AL8NMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAB1AHUAdQB1AHUAdQDHDTAAYABgAM8NMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAA1w11ANwNMAAwAD0B5A0wADAAMAAwADAAMADsDfQN/A0EDgwOFA4wABsOMAAwADAAMAAwADAAMAAwANIG0gbSBtIG0gbSBtIG0gYjDigOwQUuDsEFMw7SBjoO0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGQg5KDlIOVg7SBtIGXg5lDm0OdQ7SBtIGfQ6EDooOjQ6UDtIGmg6hDtIG0gaoDqwO0ga0DrwO0gZgAGAAYADEDmAAYAAkBtIGzA5gANIOYADaDokO0gbSBt8O5w7SBu8O0gb1DvwO0gZgAGAAxA7SBtIG0gbSBtIGYABgAGAAYAAED2AAsAUMD9IG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGFA8sBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAccD9IGLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHJA8sBywHLAcsBywHLAccDywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywPLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAc0D9IG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAccD9IG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGFA8sBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHPA/SBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gYUD0QPlQCVAJUAMAAwADAAMACVAJUAlQCVAJUAlQCVAEwPMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAA//8EAAQABAAEAAQABAAEAAQABAANAAMAAQABAAIABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQACgATABcAHgAbABoAHgAXABYAEgAeABsAGAAPABgAHABLAEsASwBLAEsASwBLAEsASwBLABgAGAAeAB4AHgATAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQABYAGwASAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAWAA0AEQAeAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAFAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAJABYAGgAbABsAGwAeAB0AHQAeAE8AFwAeAA0AHgAeABoAGwBPAE8ADgBQAB0AHQAdAE8ATwAXAE8ATwBPABYAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAFAAUABQAFAAUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AHgAeAFAATwBAAE8ATwBPAEAATwBQAFAATwBQAB4AHgAeAB4AHgAeAB0AHQAdAB0AHgAdAB4ADgBQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgBQAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAJAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAkACQAJAAkACQAJAAkABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgAeAFAAHgAeAB4AKwArAFAAUABQAFAAGABQACsAKwArACsAHgAeAFAAHgBQAFAAUAArAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAUAAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAYAA0AKwArAB4AHgAbACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQADQAEAB4ABAAEAB4ABAAEABMABAArACsAKwArACsAKwArACsAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAKwArACsAKwBWAFYAVgBWAB4AHgArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AGgAaABoAGAAYAB4AHgAEAAQABAAEAAQABAAEAAQABAAEAAQAEwAEACsAEwATAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABLAEsASwBLAEsASwBLAEsASwBLABoAGQAZAB4AUABQAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQABMAUAAEAAQABAAEAAQABAAEAB4AHgAEAAQABAAEAAQABABQAFAABAAEAB4ABAAEAAQABABQAFAASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUAAeAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAFAABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQAUABQAB4AHgAYABMAUAArACsABAAbABsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAFAABAAEAAQABAAEAFAABAAEAAQAUAAEAAQABAAEAAQAKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAArACsAHgArAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAUAAEAAQABAAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAABAAEAA0ADQBLAEsASwBLAEsASwBLAEsASwBLAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAArAFAAUABQAFAAUABQAFAAUAArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUAArACsAKwBQAFAAUABQACsAKwAEAFAABAAEAAQABAAEAAQABAArACsABAAEACsAKwAEAAQABABQACsAKwArACsAKwArACsAKwAEACsAKwArACsAUABQACsAUABQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAFAAUAAaABoAUABQAFAAUABQAEwAHgAbAFAAHgAEACsAKwAEAAQABAArAFAAUABQAFAAUABQACsAKwArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQACsAUABQACsAKwAEACsABAAEAAQABAAEACsAKwArACsABAAEACsAKwAEAAQABAArACsAKwAEACsAKwArACsAKwArACsAUABQAFAAUAArAFAAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLAAQABABQAFAAUAAEAB4AKwArACsAKwArACsAKwArACsAKwAEAAQABAArAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQAFAAUABQACsAKwAEAFAABAAEAAQABAAEAAQABAAEACsABAAEAAQAKwAEAAQABAArACsAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAB4AGwArACsAKwArACsAKwArAFAABAAEAAQABAAEAAQAKwAEAAQABAArAFAAUABQAFAAUABQAFAAUAArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAArACsABAAEACsAKwAEAAQABAArACsAKwArACsAKwArAAQABAAEACsAKwArACsAUABQACsAUABQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAB4AUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArAAQAUAArAFAAUABQAFAAUABQACsAKwArAFAAUABQACsAUABQAFAAUAArACsAKwBQAFAAKwBQACsAUABQACsAKwArAFAAUAArACsAKwBQAFAAUAArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArAAQABAAEAAQABAArACsAKwAEAAQABAArAAQABAAEAAQAKwArAFAAKwArACsAKwArACsABAArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAHgAeAB4AHgAeAB4AGwAeACsAKwArACsAKwAEAAQABAAEAAQAUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAUAAEAAQABAAEAAQABAAEACsABAAEAAQAKwAEAAQABAAEACsAKwArACsAKwArACsABAAEACsAUABQAFAAKwArACsAKwArAFAAUAAEAAQAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAKwAOAFAAUABQAFAAUABQAFAAHgBQAAQABAAEAA4AUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAKwArAAQAUAAEAAQABAAEAAQABAAEACsABAAEAAQAKwAEAAQABAAEACsAKwArACsAKwArACsABAAEACsAKwArACsAKwArACsAUAArAFAAUAAEAAQAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwBQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAFAABAAEAAQABAAEAAQABAArAAQABAAEACsABAAEAAQABABQAB4AKwArACsAKwBQAFAAUAAEAFAAUABQAFAAUABQAFAAUABQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAFAAUABQAFAAUABQAFAAUABQABoAUABQAFAAUABQAFAAKwAEAAQABAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQACsAUAArACsAUABQAFAAUABQAFAAUAArACsAKwAEACsAKwArACsABAAEAAQABAAEAAQAKwAEACsABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArAAQABAAeACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAAqAFwAXAAqACoAKgAqACoAKgAqACsAKwArACsAGwBcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAeAEsASwBLAEsASwBLAEsASwBLAEsADQANACsAKwArACsAKwBcAFwAKwBcACsAXABcAFwAXABcACsAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACsAXAArAFwAXABcAFwAXABcAFwAXABcAFwAKgBcAFwAKgAqACoAKgAqACoAKgAqACoAXAArACsAXABcAFwAXABcACsAXAArACoAKgAqACoAKgAqACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwBcAFwAXABcAFAADgAOAA4ADgAeAA4ADgAJAA4ADgANAAkAEwATABMAEwATAAkAHgATAB4AHgAeAAQABAAeAB4AHgAeAB4AHgBLAEsASwBLAEsASwBLAEsASwBLAFAAUABQAFAAUABQAFAAUABQAFAADQAEAB4ABAAeAAQAFgARABYAEQAEAAQAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQADQAEAAQABAAEAAQADQAEAAQAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArAA0ADQAeAB4AHgAeAB4AHgAEAB4AHgAeAB4AHgAeACsAHgAeAA4ADgANAA4AHgAeAB4AHgAeAAkACQArACsAKwArACsAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgBcAEsASwBLAEsASwBLAEsASwBLAEsADQANAB4AHgAeAB4AXABcAFwAXABcAFwAKgAqACoAKgBcAFwAXABcACoAKgAqAFwAKgAqACoAXABcACoAKgAqACoAKgAqACoAXABcAFwAKgAqACoAKgBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAqACoAKgAqAFwAKgBLAEsASwBLAEsASwBLAEsASwBLACoAKgAqACoAKgAqAFAAUABQAFAAUABQACsAUAArACsAKwArACsAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgBQAFAAUABQAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUAArACsAUABQAFAAUABQAFAAUAArAFAAKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAKwBQACsAUABQAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsABAAEAAQAHgANAB4AHgAeAB4AHgAeAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUAArACsADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAANAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAWABEAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAA0ADQANAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAANAA0AKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUAArAAQABAArACsAKwArACsAKwArACsAKwArACsAKwBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqAA0ADQAVAFwADQAeAA0AGwBcACoAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwAeAB4AEwATAA0ADQAOAB4AEwATAB4ABAAEAAQACQArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUAAEAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAHgArACsAKwATABMASwBLAEsASwBLAEsASwBLAEsASwBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAArACsAXABcAFwAXABcACsAKwArACsAKwArACsAKwArACsAKwBcAFwAXABcAFwAXABcAFwAXABcAFwAXAArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAXAArACsAKwAqACoAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAArACsAHgAeAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAqACoAKwAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKwArAAQASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArACoAKgAqACoAKgAqACoAXAAqACoAKgAqACoAKgArACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABABQAFAAUABQAFAAUABQACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwANAA0AHgANAA0ADQANAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAEAAQAHgAeAB4AHgAeAB4AHgAeAB4AKwArACsABAAEAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwAeAB4AHgAeAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArAA0ADQANAA0ADQBLAEsASwBLAEsASwBLAEsASwBLACsAKwArAFAAUABQAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAA0ADQBQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUAAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArAAQABAAEAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAAQAUABQAFAAUABQAFAABABQAFAABAAEAAQAUAArACsAKwArACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsABAAEAAQABAAEAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAKwBQACsAUAArAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAB4AHgAeAB4AHgAeAB4AHgBQAB4AHgAeAFAAUABQACsAHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQACsAKwAeAB4AHgAeAB4AHgArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAFAAUABQACsAHgAeAB4AHgAeAB4AHgAOAB4AKwANAA0ADQANAA0ADQANAAkADQANAA0ACAAEAAsABAAEAA0ACQANAA0ADAAdAB0AHgAXABcAFgAXABcAFwAWABcAHQAdAB4AHgAUABQAFAANAAEAAQAEAAQABAAEAAQACQAaABoAGgAaABoAGgAaABoAHgAXABcAHQAVABUAHgAeAB4AHgAeAB4AGAAWABEAFQAVABUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ADQAeAA0ADQANAA0AHgANAA0ADQAHAB4AHgAeAB4AKwAEAAQABAAEAAQABAAEAAQABAAEAFAAUAArACsATwBQAFAAUABQAFAAHgAeAB4AFgARAE8AUABPAE8ATwBPAFAAUABQAFAAUAAeAB4AHgAWABEAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArABsAGwAbABsAGwAbABsAGgAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGgAbABsAGwAbABoAGwAbABoAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAHgAeAFAAGgAeAB0AHgBQAB4AGgAeAB4AHgAeAB4AHgAeAB4AHgBPAB4AUAAbAB4AHgBQAFAAUABQAFAAHgAeAB4AHQAdAB4AUAAeAFAAHgBQAB4AUABPAFAAUAAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAHgBQAFAAUABQAE8ATwBQAFAAUABQAFAATwBQAFAATwBQAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAFAAUABQAFAATwBPAE8ATwBPAE8ATwBPAE8ATwBQAFAAUABQAFAAUABQAFAAUAAeAB4AUABQAFAAUABPAB4AHgArACsAKwArAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHQAdAB4AHgAeAB0AHQAeAB4AHQAeAB4AHgAdAB4AHQAbABsAHgAdAB4AHgAeAB4AHQAeAB4AHQAdAB0AHQAeAB4AHQAeAB0AHgAdAB0AHQAdAB0AHQAeAB0AHgAeAB4AHgAeAB0AHQAdAB0AHgAeAB4AHgAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHgAeAB0AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHgAeAB0AHQAdAB0AHgAeAB0AHQAeAB4AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHQAeAB4AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAeAB4AHgAdAB4AHgAeAB4AHgAeAB4AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABYAEQAWABEAHgAeAB4AHgAeAB4AHQAeAB4AHgAeAB4AHgAeACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAWABEAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAFAAHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB4AHgAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAeAB4AHQAdAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHQAeAB0AHQAdAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB0AHQAeAB4AHQAdAB4AHgAeAB4AHQAdAB4AHgAeAB4AHQAdAB0AHgAeAB0AHgAeAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlAB4AHQAdAB4AHgAdAB4AHgAeAB4AHQAdAB4AHgAeAB4AJQAlAB0AHQAlAB4AJQAlACUAIAAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAeAB4AHgAeAB0AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHgAdAB0AHQAeAB0AJQAdAB0AHgAdAB0AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAdAB0AHQAdACUAHgAlACUAJQAdACUAJQAdAB0AHQAlACUAHQAdACUAHQAdACUAJQAlAB4AHQAeAB4AHgAeAB0AHQAlAB0AHQAdAB0AHQAdACUAJQAlACUAJQAdACUAJQAgACUAHQAdACUAJQAlACUAJQAlACUAJQAeAB4AHgAlACUAIAAgACAAIAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AFwAXABcAFwAXABcAHgATABMAJQAeAB4AHgAWABEAFgARABYAEQAWABEAFgARABYAEQAWABEATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABYAEQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAWABEAFgARABYAEQAWABEAFgARAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFgARABYAEQAWABEAFgARABYAEQAWABEAFgARABYAEQAWABEAFgARABYAEQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAWABEAFgARAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFgARAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAEAAQABAAeAB4AKwArACsAKwArABMADQANAA0AUAATAA0AUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAUAANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAA0ADQANAA0ADQANAA0ADQAeAA0AFgANAB4AHgAXABcAHgAeABcAFwAWABEAFgARABYAEQAWABEADQANAA0ADQATAFAADQANAB4ADQANAB4AHgAeAB4AHgAMAAwADQANAA0AHgANAA0AFgANAA0ADQANAA0ADQANAA0AHgANAB4ADQANAB4AHgAeACsAKwArACsAKwArACsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAKwArACsAKwArACsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArAA0AEQARACUAJQBHAFcAVwAWABEAFgARABYAEQAWABEAFgARACUAJQAWABEAFgARABYAEQAWABEAFQAWABEAEQAlAFcAVwBXAFcAVwBXAFcAVwBXAAQABAAEAAQABAAEACUAVwBXAFcAVwA2ACUAJQBXAFcAVwBHAEcAJQAlACUAKwBRAFcAUQBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFEAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBRAFcAUQBXAFEAVwBXAFcAVwBXAFcAUQBXAFcAVwBXAFcAVwBRAFEAKwArAAQABAAVABUARwBHAFcAFQBRAFcAUQBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBRAFcAVwBXAFcAVwBXAFEAUQBXAFcAVwBXABUAUQBHAEcAVwArACsAKwArACsAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwAlACUAVwBXAFcAVwAlACUAJQAlACUAJQAlACUAJQAlACsAKwArACsAKwArACsAKwArACsAKwArAFEAUQBRAFEAUQBRAFEAUQBRAFEAUQBRAFEAUQBRAFEAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBPAE8ATwBPAE8ATwBPAE8AJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAEcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAADQATAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABLAEsASwBLAEsASwBLAEsASwBLAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAABAAEAAQABAAeAAQABAAEAAQABAAEAAQABAAEAAQAHgBQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUABQAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAeAA0ADQANAA0ADQArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAB4AHgAeAB4AHgAeAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AUABQAFAAUABQAFAAUABQAFAAUABQAAQAUABQAFAABABQAFAAUABQAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAeAB4AHgAeAAQAKwArACsAUABQAFAAUABQAFAAHgAeABoAHgArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAADgAOABMAEwArACsAKwArACsAKwArACsABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwANAA0ASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAFAAUAAeAB4AHgBQAA4AUABQAAQAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAA0ADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArAB4AWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYACsAKwArAAQAHgAeAB4AHgAeAB4ADQANAA0AHgAeAB4AHgArAFAASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArAB4AHgBcAFwAXABcAFwAKgBcAFwAXABcAFwAXABcAFwAXABcAEsASwBLAEsASwBLAEsASwBLAEsAXABcAFwAXABcACsAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArAFAAUABQAAQAUABQAFAAUABQAFAAUABQAAQABAArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAHgANAA0ADQBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAXAAqACoAKgBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAAqAFwAKgAqACoAXABcACoAKgBcAFwAXABcAFwAKgAqAFwAKgBcACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcACoAKgBQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAA0ADQBQAFAAUAAEAAQAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUAArACsAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQADQAEAAQAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAVABVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBUAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVACsAKwArACsAKwArACsAKwArACsAKwArAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAKwArACsAKwBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAKwArACsAKwAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAKwArACsAKwArAFYABABWAFYAVgBWAFYAVgBWAFYAVgBWAB4AVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgArAFYAVgBWAFYAVgArAFYAKwBWAFYAKwBWAFYAKwBWAFYAVgBWAFYAVgBWAFYAVgBWAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAEQAWAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUAAaAB4AKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAGAARABEAGAAYABMAEwAWABEAFAArACsAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACUAJQAlACUAJQAWABEAFgARABYAEQAWABEAFgARABYAEQAlACUAFgARACUAJQAlACUAJQAlACUAEQAlABEAKwAVABUAEwATACUAFgARABYAEQAWABEAJQAlACUAJQAlACUAJQAlACsAJQAbABoAJQArACsAKwArAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAcAKwATACUAJQAbABoAJQAlABYAEQAlACUAEQAlABEAJQBXAFcAVwBXAFcAVwBXAFcAVwBXABUAFQAlACUAJQATACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXABYAJQARACUAJQAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwAWACUAEQAlABYAEQARABYAEQARABUAVwBRAFEAUQBRAFEAUQBRAFEAUQBRAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAEcARwArACsAVwBXAFcAVwBXAFcAKwArAFcAVwBXAFcAVwBXACsAKwBXAFcAVwBXAFcAVwArACsAVwBXAFcAKwArACsAGgAbACUAJQAlABsAGwArAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwAEAAQABAAQAB0AKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsADQANAA0AKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAAQAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAA0AUABQAFAAUAArACsAKwArAFAAUABQAFAAUABQAFAAUAANAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwAeACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAKwArAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUAArACsAKwBQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwANAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAUABQAFAAUABQAAQABAAEACsABAAEACsAKwArACsAKwAEAAQABAAEAFAAUABQAFAAKwBQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEACsAKwArACsABABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAA0ADQANAA0ADQANAA0ADQAeACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAArACsAKwArAFAAUABQAFAAUAANAA0ADQANAA0ADQAUACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsADQANAA0ADQANAA0ADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAAQABAAEAAQAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUAArAAQABAANACsAKwBQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAB4AHgAeAB4AHgArACsAKwArACsAKwAEAAQABAAEAAQABAAEAA0ADQAeAB4AHgAeAB4AKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwAeACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsASwBLAEsASwBLAEsASwBLAEsASwANAA0ADQANAFAABAAEAFAAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAeAA4AUAArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAADQANAB4ADQAEAAQABAAEAB4ABAAEAEsASwBLAEsASwBLAEsASwBLAEsAUAAOAFAADQANAA0AKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAANAA0AHgANAA0AHgAEACsAUABQAFAAUABQAFAAUAArAFAAKwBQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAA0AKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsABAAEAAQABAArAFAAUABQAFAAUABQAFAAUAArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQAFAAUABQACsABAAEAFAABAAEAAQABAAEAAQABAArACsABAAEACsAKwAEAAQABAArACsAUAArACsAKwArACsAKwAEACsAKwArACsAKwBQAFAAUABQAFAABAAEACsAKwAEAAQABAAEAAQABAAEACsAKwArAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAAQABABQAFAAUABQAA0ADQANAA0AHgBLAEsASwBLAEsASwBLAEsASwBLAA0ADQArAB4ABABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAAQABAAEAFAAUAAeAFAAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAArACsABAAEAAQABAAEAAQABAAEAAQADgANAA0AEwATAB4AHgAeAA0ADQANAA0ADQANAA0ADQANAA0ADQANAA0ADQANAFAAUABQAFAABAAEACsAKwAEAA0ADQAeAFAAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAFAAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKwArACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBcAFwADQANAA0AKgBQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAKwArAFAAKwArAFAAUABQAFAAUABQAFAAUAArAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQAKwAEAAQAKwArAAQABAAEAAQAUAAEAFAABAAEAA0ADQANACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAArACsABAAEAAQABAAEAAQABABQAA4AUAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAFAABAAEAAQABAAOAB4ADQANAA0ADQAOAB4ABAArACsAKwArACsAKwArACsAUAAEAAQABAAEAAQABAAEAAQABAAEAAQAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAA0ADQANAFAADgAOAA4ADQANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEACsABAAEAAQABAAEAAQABAAEAFAADQANAA0ADQANACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwAOABMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAArACsAKwAEACsABAAEACsABAAEAAQABAAEAAQABABQAAQAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAKwBQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQAKwAEAAQAKwAEAAQABAAEAAQAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAaABoAGgAaAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsADQANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAASABIAEgAQwBDAEMAUABQAFAAUABDAFAAUABQAEgAQwBIAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAASABDAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwAJAAkACQAJAAkACQAJABYAEQArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABIAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwANAA0AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEAAQABAANACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAA0ADQANAB4AHgAeAB4AHgAeAFAAUABQAFAADQAeACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAANAA0AHgAeACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwAEAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAARwBHABUARwAJACsAKwArACsAKwArACsAKwArACsAKwAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACsAKwArACsAKwArACsAKwBXAFcAVwBXAFcAVwBXAFcAVwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUQBRAFEAKwArACsAKwArACsAKwArACsAKwArACsAKwBRAFEAUQBRACsAKwArACsAKwArACsAKwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUAArACsAHgAEAAQADQAEAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArAB4AHgAeAB4AHgAeAB4AKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAAQABAAEAAQABAAeAB4AHgAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAB4AHgAEAAQABAAEAAQABAAEAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQAHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwBQAFAAKwArAFAAKwArAFAAUAArACsAUABQAFAAUAArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAUAArAFAAUABQAFAAUABQAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAHgAeAFAAUABQAFAAUAArAFAAKwArACsAUABQAFAAUABQAFAAUAArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeACsAKwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgAeAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgAeAB4AHgAeAB4ABAAeAB4AHgAeAB4AHgAeAB4AHgAeAAQAHgAeAA0ADQANAA0AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAAQABAAEAAQAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAEAAQAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArAAQABAAEAAQABAAEAAQAKwAEAAQAKwAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwAEAAQABAAEAAQABAAEAFAAUABQAFAAUABQAFAAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwBQAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArABsAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArAB4AHgAeAB4ABAAEAAQABAAEAAQABABQACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArABYAFgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAGgBQAFAAUAAaAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAKwBQACsAKwBQACsAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAKwBQACsAUAArACsAKwArACsAKwBQACsAKwArACsAUAArAFAAKwBQACsAUABQAFAAKwBQAFAAKwBQACsAKwBQACsAUAArAFAAKwBQACsAUAArAFAAUAArAFAAKwArAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUAArAFAAUABQAFAAKwBQACsAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAUABQAFAAKwBQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8AJQAlACUAHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB4AHgAeACUAJQAlAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAJQAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAlACUAJQAlACUAHgAlACUAJQAlACUAIAAgACAAJQAlACAAJQAlACAAIAAgACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACEAIQAhACEAIQAlACUAIAAgACUAJQAgACAAIAAgACAAIAAgACAAIAAgACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJQAlACUAIAAlACUAJQAlACAAIAAgACUAIAAgACAAJQAlACUAJQAlACUAJQAgACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAlAB4AJQAeACUAJQAlACUAJQAgACUAJQAlACUAHgAlAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAJQAlACUAJQAgACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACAAIAAgACUAJQAlACAAIAAgACAAIAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABcAFwAXABUAFQAVAB4AHgAeAB4AJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAgACUAJQAlACUAJQAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAgACUAJQAgACUAJQAlACUAJQAlACUAJQAgACAAIAAgACAAIAAgACAAJQAlACUAJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAgACAAIAAgACAAIAAgACAAIAAgACUAJQAgACAAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAgACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAlACAAIAAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAgACAAIAAlACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJQAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAKwArAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwAlACUAJQAlACUAJQAlACUAJQAlACUAVwBXACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAKwAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAA==';\n\nvar LETTER_NUMBER_MODIFIER = 50;\n// Non-tailorable Line Breaking Classes\nvar BK = 1; // Cause a line break (after)\nvar CR$1 = 2; // Cause a line break (after), except between CR and LF\nvar LF$1 = 3; // Cause a line break (after)\nvar CM = 4; // Prohibit a line break between the character and the preceding character\nvar NL = 5; // Cause a line break (after)\nvar WJ = 7; // Prohibit line breaks before and after\nvar ZW = 8; // Provide a break opportunity\nvar GL = 9; // Prohibit line breaks before and after\nvar SP = 10; // Enable indirect line breaks\nvar ZWJ$1 = 11; // Prohibit line breaks within joiner sequences\n// Break Opportunities\nvar B2 = 12; // Provide a line break opportunity before and after the character\nvar BA = 13; // Generally provide a line break opportunity after the character\nvar BB = 14; // Generally provide a line break opportunity before the character\nvar HY = 15; // Provide a line break opportunity after the character, except in numeric context\nvar CB = 16; // Provide a line break opportunity contingent on additional information\n// Characters Prohibiting Certain Breaks\nvar CL = 17; // Prohibit line breaks before\nvar CP = 18; // Prohibit line breaks before\nvar EX = 19; // Prohibit line breaks before\nvar IN = 20; // Allow only indirect line breaks between pairs\nvar NS = 21; // Allow only indirect line breaks before\nvar OP = 22; // Prohibit line breaks after\nvar QU = 23; // Act like they are both opening and closing\n// Numeric Context\nvar IS = 24; // Prevent breaks after any and before numeric\nvar NU = 25; // Form numeric expressions for line breaking purposes\nvar PO = 26; // Do not break following a numeric expression\nvar PR = 27; // Do not break in front of a numeric expression\nvar SY = 28; // Prevent a break before; and allow a break after\n// Other Characters\nvar AI = 29; // Act like AL when the resolvedEAW is N; otherwise; act as ID\nvar AL = 30; // Are alphabetic characters or symbols that are used with alphabetic characters\nvar CJ = 31; // Treat as NS or ID for strict or normal breaking.\nvar EB = 32; // Do not break from following Emoji Modifier\nvar EM = 33; // Do not break from preceding Emoji Base\nvar H2 = 34; // Form Korean syllable blocks\nvar H3 = 35; // Form Korean syllable blocks\nvar HL = 36; // Do not break around a following hyphen; otherwise act as Alphabetic\nvar ID = 37; // Break before or after; except in some numeric context\nvar JL = 38; // Form Korean syllable blocks\nvar JV = 39; // Form Korean syllable blocks\nvar JT = 40; // Form Korean syllable blocks\nvar RI$1 = 41; // Keep pairs together. For pairs; break before and after other classes\nvar SA = 42; // Provide a line break opportunity contingent on additional, language-specific context analysis\nvar XX = 43; // Have as yet unknown line breaking behavior or unassigned code positions\nvar ea_OP = [0x2329, 0xff08];\nvar BREAK_MANDATORY = '!';\nvar BREAK_NOT_ALLOWED$1 = '×';\nvar BREAK_ALLOWED$1 = '÷';\nvar UnicodeTrie$1 = createTrieFromBase64$1(base64$1);\nvar ALPHABETICS = [AL, HL];\nvar HARD_LINE_BREAKS = [BK, CR$1, LF$1, NL];\nvar SPACE$1 = [SP, ZW];\nvar PREFIX_POSTFIX = [PR, PO];\nvar LINE_BREAKS = HARD_LINE_BREAKS.concat(SPACE$1);\nvar KOREAN_SYLLABLE_BLOCK = [JL, JV, JT, H2, H3];\nvar HYPHEN = [HY, BA];\nvar codePointsToCharacterClasses = function (codePoints, lineBreak) {\n if (lineBreak === void 0) { lineBreak = 'strict'; }\n var types = [];\n var indices = [];\n var categories = [];\n codePoints.forEach(function (codePoint, index) {\n var classType = UnicodeTrie$1.get(codePoint);\n if (classType > LETTER_NUMBER_MODIFIER) {\n categories.push(true);\n classType -= LETTER_NUMBER_MODIFIER;\n }\n else {\n categories.push(false);\n }\n if (['normal', 'auto', 'loose'].indexOf(lineBreak) !== -1) {\n // U+2010, – U+2013, 〜 U+301C, ゠ U+30A0\n if ([0x2010, 0x2013, 0x301c, 0x30a0].indexOf(codePoint) !== -1) {\n indices.push(index);\n return types.push(CB);\n }\n }\n if (classType === CM || classType === ZWJ$1) {\n // LB10 Treat any remaining combining mark or ZWJ as AL.\n if (index === 0) {\n indices.push(index);\n return types.push(AL);\n }\n // LB9 Do not break a combining character sequence; treat it as if it has the line breaking class of\n // the base character in all of the following rules. Treat ZWJ as if it were CM.\n var prev = types[index - 1];\n if (LINE_BREAKS.indexOf(prev) === -1) {\n indices.push(indices[index - 1]);\n return types.push(prev);\n }\n indices.push(index);\n return types.push(AL);\n }\n indices.push(index);\n if (classType === CJ) {\n return types.push(lineBreak === 'strict' ? NS : ID);\n }\n if (classType === SA) {\n return types.push(AL);\n }\n if (classType === AI) {\n return types.push(AL);\n }\n // For supplementary characters, a useful default is to treat characters in the range 10000..1FFFD as AL\n // and characters in the ranges 20000..2FFFD and 30000..3FFFD as ID, until the implementation can be revised\n // to take into account the actual line breaking properties for these characters.\n if (classType === XX) {\n if ((codePoint >= 0x20000 && codePoint <= 0x2fffd) || (codePoint >= 0x30000 && codePoint <= 0x3fffd)) {\n return types.push(ID);\n }\n else {\n return types.push(AL);\n }\n }\n types.push(classType);\n });\n return [indices, types, categories];\n};\nvar isAdjacentWithSpaceIgnored = function (a, b, currentIndex, classTypes) {\n var current = classTypes[currentIndex];\n if (Array.isArray(a) ? a.indexOf(current) !== -1 : a === current) {\n var i = currentIndex;\n while (i <= classTypes.length) {\n i++;\n var next = classTypes[i];\n if (next === b) {\n return true;\n }\n if (next !== SP) {\n break;\n }\n }\n }\n if (current === SP) {\n var i = currentIndex;\n while (i > 0) {\n i--;\n var prev = classTypes[i];\n if (Array.isArray(a) ? a.indexOf(prev) !== -1 : a === prev) {\n var n = currentIndex;\n while (n <= classTypes.length) {\n n++;\n var next = classTypes[n];\n if (next === b) {\n return true;\n }\n if (next !== SP) {\n break;\n }\n }\n }\n if (prev !== SP) {\n break;\n }\n }\n }\n return false;\n};\nvar previousNonSpaceClassType = function (currentIndex, classTypes) {\n var i = currentIndex;\n while (i >= 0) {\n var type = classTypes[i];\n if (type === SP) {\n i--;\n }\n else {\n return type;\n }\n }\n return 0;\n};\nvar _lineBreakAtIndex = function (codePoints, classTypes, indicies, index, forbiddenBreaks) {\n if (indicies[index] === 0) {\n return BREAK_NOT_ALLOWED$1;\n }\n var currentIndex = index - 1;\n if (Array.isArray(forbiddenBreaks) && forbiddenBreaks[currentIndex] === true) {\n return BREAK_NOT_ALLOWED$1;\n }\n var beforeIndex = currentIndex - 1;\n var afterIndex = currentIndex + 1;\n var current = classTypes[currentIndex];\n // LB4 Always break after hard line breaks.\n // LB5 Treat CR followed by LF, as well as CR, LF, and NL as hard line breaks.\n var before = beforeIndex >= 0 ? classTypes[beforeIndex] : 0;\n var next = classTypes[afterIndex];\n if (current === CR$1 && next === LF$1) {\n return BREAK_NOT_ALLOWED$1;\n }\n if (HARD_LINE_BREAKS.indexOf(current) !== -1) {\n return BREAK_MANDATORY;\n }\n // LB6 Do not break before hard line breaks.\n if (HARD_LINE_BREAKS.indexOf(next) !== -1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB7 Do not break before spaces or zero width space.\n if (SPACE$1.indexOf(next) !== -1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB8 Break before any character following a zero-width space, even if one or more spaces intervene.\n if (previousNonSpaceClassType(currentIndex, classTypes) === ZW) {\n return BREAK_ALLOWED$1;\n }\n // LB8a Do not break after a zero width joiner.\n if (UnicodeTrie$1.get(codePoints[currentIndex]) === ZWJ$1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // zwj emojis\n if ((current === EB || current === EM) && UnicodeTrie$1.get(codePoints[afterIndex]) === ZWJ$1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB11 Do not break before or after Word joiner and related characters.\n if (current === WJ || next === WJ) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB12 Do not break after NBSP and related characters.\n if (current === GL) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB12a Do not break before NBSP and related characters, except after spaces and hyphens.\n if ([SP, BA, HY].indexOf(current) === -1 && next === GL) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB13 Do not break before ‘]’ or ‘!’ or ‘;’ or ‘/’, even after spaces.\n if ([CL, CP, EX, IS, SY].indexOf(next) !== -1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB14 Do not break after ‘[’, even after spaces.\n if (previousNonSpaceClassType(currentIndex, classTypes) === OP) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB15 Do not break within ‘”[’, even with intervening spaces.\n if (isAdjacentWithSpaceIgnored(QU, OP, currentIndex, classTypes)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB16 Do not break between closing punctuation and a nonstarter (lb=NS), even with intervening spaces.\n if (isAdjacentWithSpaceIgnored([CL, CP], NS, currentIndex, classTypes)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB17 Do not break within ‘——’, even with intervening spaces.\n if (isAdjacentWithSpaceIgnored(B2, B2, currentIndex, classTypes)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB18 Break after spaces.\n if (current === SP) {\n return BREAK_ALLOWED$1;\n }\n // LB19 Do not break before or after quotation marks, such as ‘ ” ’.\n if (current === QU || next === QU) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB20 Break before and after unresolved CB.\n if (next === CB || current === CB) {\n return BREAK_ALLOWED$1;\n }\n // LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small kana, and other non-starters, or after acute accents.\n if ([BA, HY, NS].indexOf(next) !== -1 || current === BB) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB21a Don't break after Hebrew + Hyphen.\n if (before === HL && HYPHEN.indexOf(current) !== -1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB21b Don’t break between Solidus and Hebrew letters.\n if (current === SY && next === HL) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB22 Do not break before ellipsis.\n if (next === IN) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB23 Do not break between digits and letters.\n if ((ALPHABETICS.indexOf(next) !== -1 && current === NU) || (ALPHABETICS.indexOf(current) !== -1 && next === NU)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB23a Do not break between numeric prefixes and ideographs, or between ideographs and numeric postfixes.\n if ((current === PR && [ID, EB, EM].indexOf(next) !== -1) ||\n ([ID, EB, EM].indexOf(current) !== -1 && next === PO)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB24 Do not break between numeric prefix/postfix and letters, or between letters and prefix/postfix.\n if ((ALPHABETICS.indexOf(current) !== -1 && PREFIX_POSTFIX.indexOf(next) !== -1) ||\n (PREFIX_POSTFIX.indexOf(current) !== -1 && ALPHABETICS.indexOf(next) !== -1)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB25 Do not break between the following pairs of classes relevant to numbers:\n if (\n // (PR | PO) × ( OP | HY )? NU\n ([PR, PO].indexOf(current) !== -1 &&\n (next === NU || ([OP, HY].indexOf(next) !== -1 && classTypes[afterIndex + 1] === NU))) ||\n // ( OP | HY ) × NU\n ([OP, HY].indexOf(current) !== -1 && next === NU) ||\n // NU ×\t(NU | SY | IS)\n (current === NU && [NU, SY, IS].indexOf(next) !== -1)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // NU (NU | SY | IS)* × (NU | SY | IS | CL | CP)\n if ([NU, SY, IS, CL, CP].indexOf(next) !== -1) {\n var prevIndex = currentIndex;\n while (prevIndex >= 0) {\n var type = classTypes[prevIndex];\n if (type === NU) {\n return BREAK_NOT_ALLOWED$1;\n }\n else if ([SY, IS].indexOf(type) !== -1) {\n prevIndex--;\n }\n else {\n break;\n }\n }\n }\n // NU (NU | SY | IS)* (CL | CP)? × (PO | PR))\n if ([PR, PO].indexOf(next) !== -1) {\n var prevIndex = [CL, CP].indexOf(current) !== -1 ? beforeIndex : currentIndex;\n while (prevIndex >= 0) {\n var type = classTypes[prevIndex];\n if (type === NU) {\n return BREAK_NOT_ALLOWED$1;\n }\n else if ([SY, IS].indexOf(type) !== -1) {\n prevIndex--;\n }\n else {\n break;\n }\n }\n }\n // LB26 Do not break a Korean syllable.\n if ((JL === current && [JL, JV, H2, H3].indexOf(next) !== -1) ||\n ([JV, H2].indexOf(current) !== -1 && [JV, JT].indexOf(next) !== -1) ||\n ([JT, H3].indexOf(current) !== -1 && next === JT)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB27 Treat a Korean Syllable Block the same as ID.\n if ((KOREAN_SYLLABLE_BLOCK.indexOf(current) !== -1 && [IN, PO].indexOf(next) !== -1) ||\n (KOREAN_SYLLABLE_BLOCK.indexOf(next) !== -1 && current === PR)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB28 Do not break between alphabetics (“at”).\n if (ALPHABETICS.indexOf(current) !== -1 && ALPHABETICS.indexOf(next) !== -1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB29 Do not break between numeric punctuation and alphabetics (“e.g.”).\n if (current === IS && ALPHABETICS.indexOf(next) !== -1) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB30 Do not break between letters, numbers, or ordinary symbols and opening or closing parentheses.\n if ((ALPHABETICS.concat(NU).indexOf(current) !== -1 &&\n next === OP &&\n ea_OP.indexOf(codePoints[afterIndex]) === -1) ||\n (ALPHABETICS.concat(NU).indexOf(next) !== -1 && current === CP)) {\n return BREAK_NOT_ALLOWED$1;\n }\n // LB30a Break between two regional indicator symbols if and only if there are an even number of regional\n // indicators preceding the position of the break.\n if (current === RI$1 && next === RI$1) {\n var i = indicies[currentIndex];\n var count = 1;\n while (i > 0) {\n i--;\n if (classTypes[i] === RI$1) {\n count++;\n }\n else {\n break;\n }\n }\n if (count % 2 !== 0) {\n return BREAK_NOT_ALLOWED$1;\n }\n }\n // LB30b Do not break between an emoji base and an emoji modifier.\n if (current === EB && next === EM) {\n return BREAK_NOT_ALLOWED$1;\n }\n return BREAK_ALLOWED$1;\n};\nvar cssFormattedClasses = function (codePoints, options) {\n if (!options) {\n options = { lineBreak: 'normal', wordBreak: 'normal' };\n }\n var _a = codePointsToCharacterClasses(codePoints, options.lineBreak), indicies = _a[0], classTypes = _a[1], isLetterNumber = _a[2];\n if (options.wordBreak === 'break-all' || options.wordBreak === 'break-word') {\n classTypes = classTypes.map(function (type) { return ([NU, AL, SA].indexOf(type) !== -1 ? ID : type); });\n }\n var forbiddenBreakpoints = options.wordBreak === 'keep-all'\n ? isLetterNumber.map(function (letterNumber, i) {\n return letterNumber && codePoints[i] >= 0x4e00 && codePoints[i] <= 0x9fff;\n })\n : undefined;\n return [indicies, classTypes, forbiddenBreakpoints];\n};\nvar Break = /** @class */ (function () {\n function Break(codePoints, lineBreak, start, end) {\n this.codePoints = codePoints;\n this.required = lineBreak === BREAK_MANDATORY;\n this.start = start;\n this.end = end;\n }\n Break.prototype.slice = function () {\n return fromCodePoint$1.apply(void 0, this.codePoints.slice(this.start, this.end));\n };\n return Break;\n}());\nvar LineBreaker = function (str, options) {\n var codePoints = toCodePoints$1(str);\n var _a = cssFormattedClasses(codePoints, options), indicies = _a[0], classTypes = _a[1], forbiddenBreakpoints = _a[2];\n var length = codePoints.length;\n var lastEnd = 0;\n var nextIndex = 0;\n return {\n next: function () {\n if (nextIndex >= length) {\n return { done: true, value: null };\n }\n var lineBreak = BREAK_NOT_ALLOWED$1;\n while (nextIndex < length &&\n (lineBreak = _lineBreakAtIndex(codePoints, classTypes, indicies, ++nextIndex, forbiddenBreakpoints)) ===\n BREAK_NOT_ALLOWED$1) { }\n if (lineBreak !== BREAK_NOT_ALLOWED$1 || nextIndex === length) {\n var value = new Break(codePoints, lineBreak, lastEnd, nextIndex);\n lastEnd = nextIndex;\n return { value: value, done: false };\n }\n return { done: true, value: null };\n },\n };\n};\n\n// https://www.w3.org/TR/css-syntax-3\nvar FLAG_UNRESTRICTED = 1 << 0;\nvar FLAG_ID = 1 << 1;\nvar FLAG_INTEGER = 1 << 2;\nvar FLAG_NUMBER = 1 << 3;\nvar LINE_FEED = 0x000a;\nvar SOLIDUS = 0x002f;\nvar REVERSE_SOLIDUS = 0x005c;\nvar CHARACTER_TABULATION = 0x0009;\nvar SPACE = 0x0020;\nvar QUOTATION_MARK = 0x0022;\nvar EQUALS_SIGN = 0x003d;\nvar NUMBER_SIGN = 0x0023;\nvar DOLLAR_SIGN = 0x0024;\nvar PERCENTAGE_SIGN = 0x0025;\nvar APOSTROPHE = 0x0027;\nvar LEFT_PARENTHESIS = 0x0028;\nvar RIGHT_PARENTHESIS = 0x0029;\nvar LOW_LINE = 0x005f;\nvar HYPHEN_MINUS = 0x002d;\nvar EXCLAMATION_MARK = 0x0021;\nvar LESS_THAN_SIGN = 0x003c;\nvar GREATER_THAN_SIGN = 0x003e;\nvar COMMERCIAL_AT = 0x0040;\nvar LEFT_SQUARE_BRACKET = 0x005b;\nvar RIGHT_SQUARE_BRACKET = 0x005d;\nvar CIRCUMFLEX_ACCENT = 0x003d;\nvar LEFT_CURLY_BRACKET = 0x007b;\nvar QUESTION_MARK = 0x003f;\nvar RIGHT_CURLY_BRACKET = 0x007d;\nvar VERTICAL_LINE = 0x007c;\nvar TILDE = 0x007e;\nvar CONTROL = 0x0080;\nvar REPLACEMENT_CHARACTER = 0xfffd;\nvar ASTERISK = 0x002a;\nvar PLUS_SIGN = 0x002b;\nvar COMMA = 0x002c;\nvar COLON = 0x003a;\nvar SEMICOLON = 0x003b;\nvar FULL_STOP = 0x002e;\nvar NULL = 0x0000;\nvar BACKSPACE = 0x0008;\nvar LINE_TABULATION = 0x000b;\nvar SHIFT_OUT = 0x000e;\nvar INFORMATION_SEPARATOR_ONE = 0x001f;\nvar DELETE = 0x007f;\nvar EOF = -1;\nvar ZERO = 0x0030;\nvar a$1 = 0x0061;\nvar e = 0x0065;\nvar f$1 = 0x0066;\nvar u = 0x0075;\nvar z = 0x007a;\nvar A = 0x0041;\nvar E = 0x0045;\nvar F = 0x0046;\nvar U = 0x0055;\nvar Z = 0x005a;\nvar isDigit = function (codePoint) { return codePoint >= ZERO && codePoint <= 0x0039; };\nvar isSurrogateCodePoint = function (codePoint) { return codePoint >= 0xd800 && codePoint <= 0xdfff; };\nvar isHex = function (codePoint) {\n return isDigit(codePoint) || (codePoint >= A && codePoint <= F) || (codePoint >= a$1 && codePoint <= f$1);\n};\nvar isLowerCaseLetter = function (codePoint) { return codePoint >= a$1 && codePoint <= z; };\nvar isUpperCaseLetter = function (codePoint) { return codePoint >= A && codePoint <= Z; };\nvar isLetter = function (codePoint) { return isLowerCaseLetter(codePoint) || isUpperCaseLetter(codePoint); };\nvar isNonASCIICodePoint = function (codePoint) { return codePoint >= CONTROL; };\nvar isWhiteSpace = function (codePoint) {\n return codePoint === LINE_FEED || codePoint === CHARACTER_TABULATION || codePoint === SPACE;\n};\nvar isNameStartCodePoint = function (codePoint) {\n return isLetter(codePoint) || isNonASCIICodePoint(codePoint) || codePoint === LOW_LINE;\n};\nvar isNameCodePoint = function (codePoint) {\n return isNameStartCodePoint(codePoint) || isDigit(codePoint) || codePoint === HYPHEN_MINUS;\n};\nvar isNonPrintableCodePoint = function (codePoint) {\n return ((codePoint >= NULL && codePoint <= BACKSPACE) ||\n codePoint === LINE_TABULATION ||\n (codePoint >= SHIFT_OUT && codePoint <= INFORMATION_SEPARATOR_ONE) ||\n codePoint === DELETE);\n};\nvar isValidEscape = function (c1, c2) {\n if (c1 !== REVERSE_SOLIDUS) {\n return false;\n }\n return c2 !== LINE_FEED;\n};\nvar isIdentifierStart = function (c1, c2, c3) {\n if (c1 === HYPHEN_MINUS) {\n return isNameStartCodePoint(c2) || isValidEscape(c2, c3);\n }\n else if (isNameStartCodePoint(c1)) {\n return true;\n }\n else if (c1 === REVERSE_SOLIDUS && isValidEscape(c1, c2)) {\n return true;\n }\n return false;\n};\nvar isNumberStart = function (c1, c2, c3) {\n if (c1 === PLUS_SIGN || c1 === HYPHEN_MINUS) {\n if (isDigit(c2)) {\n return true;\n }\n return c2 === FULL_STOP && isDigit(c3);\n }\n if (c1 === FULL_STOP) {\n return isDigit(c2);\n }\n return isDigit(c1);\n};\nvar stringToNumber = function (codePoints) {\n var c = 0;\n var sign = 1;\n if (codePoints[c] === PLUS_SIGN || codePoints[c] === HYPHEN_MINUS) {\n if (codePoints[c] === HYPHEN_MINUS) {\n sign = -1;\n }\n c++;\n }\n var integers = [];\n while (isDigit(codePoints[c])) {\n integers.push(codePoints[c++]);\n }\n var int = integers.length ? parseInt(fromCodePoint$1.apply(void 0, integers), 10) : 0;\n if (codePoints[c] === FULL_STOP) {\n c++;\n }\n var fraction = [];\n while (isDigit(codePoints[c])) {\n fraction.push(codePoints[c++]);\n }\n var fracd = fraction.length;\n var frac = fracd ? parseInt(fromCodePoint$1.apply(void 0, fraction), 10) : 0;\n if (codePoints[c] === E || codePoints[c] === e) {\n c++;\n }\n var expsign = 1;\n if (codePoints[c] === PLUS_SIGN || codePoints[c] === HYPHEN_MINUS) {\n if (codePoints[c] === HYPHEN_MINUS) {\n expsign = -1;\n }\n c++;\n }\n var exponent = [];\n while (isDigit(codePoints[c])) {\n exponent.push(codePoints[c++]);\n }\n var exp = exponent.length ? parseInt(fromCodePoint$1.apply(void 0, exponent), 10) : 0;\n return sign * (int + frac * Math.pow(10, -fracd)) * Math.pow(10, expsign * exp);\n};\nvar LEFT_PARENTHESIS_TOKEN = {\n type: 2 /* LEFT_PARENTHESIS_TOKEN */\n};\nvar RIGHT_PARENTHESIS_TOKEN = {\n type: 3 /* RIGHT_PARENTHESIS_TOKEN */\n};\nvar COMMA_TOKEN = { type: 4 /* COMMA_TOKEN */ };\nvar SUFFIX_MATCH_TOKEN = { type: 13 /* SUFFIX_MATCH_TOKEN */ };\nvar PREFIX_MATCH_TOKEN = { type: 8 /* PREFIX_MATCH_TOKEN */ };\nvar COLUMN_TOKEN = { type: 21 /* COLUMN_TOKEN */ };\nvar DASH_MATCH_TOKEN = { type: 9 /* DASH_MATCH_TOKEN */ };\nvar INCLUDE_MATCH_TOKEN = { type: 10 /* INCLUDE_MATCH_TOKEN */ };\nvar LEFT_CURLY_BRACKET_TOKEN = {\n type: 11 /* LEFT_CURLY_BRACKET_TOKEN */\n};\nvar RIGHT_CURLY_BRACKET_TOKEN = {\n type: 12 /* RIGHT_CURLY_BRACKET_TOKEN */\n};\nvar SUBSTRING_MATCH_TOKEN = { type: 14 /* SUBSTRING_MATCH_TOKEN */ };\nvar BAD_URL_TOKEN = { type: 23 /* BAD_URL_TOKEN */ };\nvar BAD_STRING_TOKEN = { type: 1 /* BAD_STRING_TOKEN */ };\nvar CDO_TOKEN = { type: 25 /* CDO_TOKEN */ };\nvar CDC_TOKEN = { type: 24 /* CDC_TOKEN */ };\nvar COLON_TOKEN = { type: 26 /* COLON_TOKEN */ };\nvar SEMICOLON_TOKEN = { type: 27 /* SEMICOLON_TOKEN */ };\nvar LEFT_SQUARE_BRACKET_TOKEN = {\n type: 28 /* LEFT_SQUARE_BRACKET_TOKEN */\n};\nvar RIGHT_SQUARE_BRACKET_TOKEN = {\n type: 29 /* RIGHT_SQUARE_BRACKET_TOKEN */\n};\nvar WHITESPACE_TOKEN = { type: 31 /* WHITESPACE_TOKEN */ };\nvar EOF_TOKEN = { type: 32 /* EOF_TOKEN */ };\nvar Tokenizer = /** @class */ (function () {\n function Tokenizer() {\n this._value = [];\n }\n Tokenizer.prototype.write = function (chunk) {\n this._value = this._value.concat(toCodePoints$1(chunk));\n };\n Tokenizer.prototype.read = function () {\n var tokens = [];\n var token = this.consumeToken();\n while (token !== EOF_TOKEN) {\n tokens.push(token);\n token = this.consumeToken();\n }\n return tokens;\n };\n Tokenizer.prototype.consumeToken = function () {\n var codePoint = this.consumeCodePoint();\n switch (codePoint) {\n case QUOTATION_MARK:\n return this.consumeStringToken(QUOTATION_MARK);\n case NUMBER_SIGN:\n var c1 = this.peekCodePoint(0);\n var c2 = this.peekCodePoint(1);\n var c3 = this.peekCodePoint(2);\n if (isNameCodePoint(c1) || isValidEscape(c2, c3)) {\n var flags = isIdentifierStart(c1, c2, c3) ? FLAG_ID : FLAG_UNRESTRICTED;\n var value = this.consumeName();\n return { type: 5 /* HASH_TOKEN */, value: value, flags: flags };\n }\n break;\n case DOLLAR_SIGN:\n if (this.peekCodePoint(0) === EQUALS_SIGN) {\n this.consumeCodePoint();\n return SUFFIX_MATCH_TOKEN;\n }\n break;\n case APOSTROPHE:\n return this.consumeStringToken(APOSTROPHE);\n case LEFT_PARENTHESIS:\n return LEFT_PARENTHESIS_TOKEN;\n case RIGHT_PARENTHESIS:\n return RIGHT_PARENTHESIS_TOKEN;\n case ASTERISK:\n if (this.peekCodePoint(0) === EQUALS_SIGN) {\n this.consumeCodePoint();\n return SUBSTRING_MATCH_TOKEN;\n }\n break;\n case PLUS_SIGN:\n if (isNumberStart(codePoint, this.peekCodePoint(0), this.peekCodePoint(1))) {\n this.reconsumeCodePoint(codePoint);\n return this.consumeNumericToken();\n }\n break;\n case COMMA:\n return COMMA_TOKEN;\n case HYPHEN_MINUS:\n var e1 = codePoint;\n var e2 = this.peekCodePoint(0);\n var e3 = this.peekCodePoint(1);\n if (isNumberStart(e1, e2, e3)) {\n this.reconsumeCodePoint(codePoint);\n return this.consumeNumericToken();\n }\n if (isIdentifierStart(e1, e2, e3)) {\n this.reconsumeCodePoint(codePoint);\n return this.consumeIdentLikeToken();\n }\n if (e2 === HYPHEN_MINUS && e3 === GREATER_THAN_SIGN) {\n this.consumeCodePoint();\n this.consumeCodePoint();\n return CDC_TOKEN;\n }\n break;\n case FULL_STOP:\n if (isNumberStart(codePoint, this.peekCodePoint(0), this.peekCodePoint(1))) {\n this.reconsumeCodePoint(codePoint);\n return this.consumeNumericToken();\n }\n break;\n case SOLIDUS:\n if (this.peekCodePoint(0) === ASTERISK) {\n this.consumeCodePoint();\n while (true) {\n var c = this.consumeCodePoint();\n if (c === ASTERISK) {\n c = this.consumeCodePoint();\n if (c === SOLIDUS) {\n return this.consumeToken();\n }\n }\n if (c === EOF) {\n return this.consumeToken();\n }\n }\n }\n break;\n case COLON:\n return COLON_TOKEN;\n case SEMICOLON:\n return SEMICOLON_TOKEN;\n case LESS_THAN_SIGN:\n if (this.peekCodePoint(0) === EXCLAMATION_MARK &&\n this.peekCodePoint(1) === HYPHEN_MINUS &&\n this.peekCodePoint(2) === HYPHEN_MINUS) {\n this.consumeCodePoint();\n this.consumeCodePoint();\n return CDO_TOKEN;\n }\n break;\n case COMMERCIAL_AT:\n var a1 = this.peekCodePoint(0);\n var a2 = this.peekCodePoint(1);\n var a3 = this.peekCodePoint(2);\n if (isIdentifierStart(a1, a2, a3)) {\n var value = this.consumeName();\n return { type: 7 /* AT_KEYWORD_TOKEN */, value: value };\n }\n break;\n case LEFT_SQUARE_BRACKET:\n return LEFT_SQUARE_BRACKET_TOKEN;\n case REVERSE_SOLIDUS:\n if (isValidEscape(codePoint, this.peekCodePoint(0))) {\n this.reconsumeCodePoint(codePoint);\n return this.consumeIdentLikeToken();\n }\n break;\n case RIGHT_SQUARE_BRACKET:\n return RIGHT_SQUARE_BRACKET_TOKEN;\n case CIRCUMFLEX_ACCENT:\n if (this.peekCodePoint(0) === EQUALS_SIGN) {\n this.consumeCodePoint();\n return PREFIX_MATCH_TOKEN;\n }\n break;\n case LEFT_CURLY_BRACKET:\n return LEFT_CURLY_BRACKET_TOKEN;\n case RIGHT_CURLY_BRACKET:\n return RIGHT_CURLY_BRACKET_TOKEN;\n case u:\n case U:\n var u1 = this.peekCodePoint(0);\n var u2 = this.peekCodePoint(1);\n if (u1 === PLUS_SIGN && (isHex(u2) || u2 === QUESTION_MARK)) {\n this.consumeCodePoint();\n this.consumeUnicodeRangeToken();\n }\n this.reconsumeCodePoint(codePoint);\n return this.consumeIdentLikeToken();\n case VERTICAL_LINE:\n if (this.peekCodePoint(0) === EQUALS_SIGN) {\n this.consumeCodePoint();\n return DASH_MATCH_TOKEN;\n }\n if (this.peekCodePoint(0) === VERTICAL_LINE) {\n this.consumeCodePoint();\n return COLUMN_TOKEN;\n }\n break;\n case TILDE:\n if (this.peekCodePoint(0) === EQUALS_SIGN) {\n this.consumeCodePoint();\n return INCLUDE_MATCH_TOKEN;\n }\n break;\n case EOF:\n return EOF_TOKEN;\n }\n if (isWhiteSpace(codePoint)) {\n this.consumeWhiteSpace();\n return WHITESPACE_TOKEN;\n }\n if (isDigit(codePoint)) {\n this.reconsumeCodePoint(codePoint);\n return this.consumeNumericToken();\n }\n if (isNameStartCodePoint(codePoint)) {\n this.reconsumeCodePoint(codePoint);\n return this.consumeIdentLikeToken();\n }\n return { type: 6 /* DELIM_TOKEN */, value: fromCodePoint$1(codePoint) };\n };\n Tokenizer.prototype.consumeCodePoint = function () {\n var value = this._value.shift();\n return typeof value === 'undefined' ? -1 : value;\n };\n Tokenizer.prototype.reconsumeCodePoint = function (codePoint) {\n this._value.unshift(codePoint);\n };\n Tokenizer.prototype.peekCodePoint = function (delta) {\n if (delta >= this._value.length) {\n return -1;\n }\n return this._value[delta];\n };\n Tokenizer.prototype.consumeUnicodeRangeToken = function () {\n var digits = [];\n var codePoint = this.consumeCodePoint();\n while (isHex(codePoint) && digits.length < 6) {\n digits.push(codePoint);\n codePoint = this.consumeCodePoint();\n }\n var questionMarks = false;\n while (codePoint === QUESTION_MARK && digits.length < 6) {\n digits.push(codePoint);\n codePoint = this.consumeCodePoint();\n questionMarks = true;\n }\n if (questionMarks) {\n var start_1 = parseInt(fromCodePoint$1.apply(void 0, digits.map(function (digit) { return (digit === QUESTION_MARK ? ZERO : digit); })), 16);\n var end = parseInt(fromCodePoint$1.apply(void 0, digits.map(function (digit) { return (digit === QUESTION_MARK ? F : digit); })), 16);\n return { type: 30 /* UNICODE_RANGE_TOKEN */, start: start_1, end: end };\n }\n var start = parseInt(fromCodePoint$1.apply(void 0, digits), 16);\n if (this.peekCodePoint(0) === HYPHEN_MINUS && isHex(this.peekCodePoint(1))) {\n this.consumeCodePoint();\n codePoint = this.consumeCodePoint();\n var endDigits = [];\n while (isHex(codePoint) && endDigits.length < 6) {\n endDigits.push(codePoint);\n codePoint = this.consumeCodePoint();\n }\n var end = parseInt(fromCodePoint$1.apply(void 0, endDigits), 16);\n return { type: 30 /* UNICODE_RANGE_TOKEN */, start: start, end: end };\n }\n else {\n return { type: 30 /* UNICODE_RANGE_TOKEN */, start: start, end: start };\n }\n };\n Tokenizer.prototype.consumeIdentLikeToken = function () {\n var value = this.consumeName();\n if (value.toLowerCase() === 'url' && this.peekCodePoint(0) === LEFT_PARENTHESIS) {\n this.consumeCodePoint();\n return this.consumeUrlToken();\n }\n else if (this.peekCodePoint(0) === LEFT_PARENTHESIS) {\n this.consumeCodePoint();\n return { type: 19 /* FUNCTION_TOKEN */, value: value };\n }\n return { type: 20 /* IDENT_TOKEN */, value: value };\n };\n Tokenizer.prototype.consumeUrlToken = function () {\n var value = [];\n this.consumeWhiteSpace();\n if (this.peekCodePoint(0) === EOF) {\n return { type: 22 /* URL_TOKEN */, value: '' };\n }\n var next = this.peekCodePoint(0);\n if (next === APOSTROPHE || next === QUOTATION_MARK) {\n var stringToken = this.consumeStringToken(this.consumeCodePoint());\n if (stringToken.type === 0 /* STRING_TOKEN */) {\n this.consumeWhiteSpace();\n if (this.peekCodePoint(0) === EOF || this.peekCodePoint(0) === RIGHT_PARENTHESIS) {\n this.consumeCodePoint();\n return { type: 22 /* URL_TOKEN */, value: stringToken.value };\n }\n }\n this.consumeBadUrlRemnants();\n return BAD_URL_TOKEN;\n }\n while (true) {\n var codePoint = this.consumeCodePoint();\n if (codePoint === EOF || codePoint === RIGHT_PARENTHESIS) {\n return { type: 22 /* URL_TOKEN */, value: fromCodePoint$1.apply(void 0, value) };\n }\n else if (isWhiteSpace(codePoint)) {\n this.consumeWhiteSpace();\n if (this.peekCodePoint(0) === EOF || this.peekCodePoint(0) === RIGHT_PARENTHESIS) {\n this.consumeCodePoint();\n return { type: 22 /* URL_TOKEN */, value: fromCodePoint$1.apply(void 0, value) };\n }\n this.consumeBadUrlRemnants();\n return BAD_URL_TOKEN;\n }\n else if (codePoint === QUOTATION_MARK ||\n codePoint === APOSTROPHE ||\n codePoint === LEFT_PARENTHESIS ||\n isNonPrintableCodePoint(codePoint)) {\n this.consumeBadUrlRemnants();\n return BAD_URL_TOKEN;\n }\n else if (codePoint === REVERSE_SOLIDUS) {\n if (isValidEscape(codePoint, this.peekCodePoint(0))) {\n value.push(this.consumeEscapedCodePoint());\n }\n else {\n this.consumeBadUrlRemnants();\n return BAD_URL_TOKEN;\n }\n }\n else {\n value.push(codePoint);\n }\n }\n };\n Tokenizer.prototype.consumeWhiteSpace = function () {\n while (isWhiteSpace(this.peekCodePoint(0))) {\n this.consumeCodePoint();\n }\n };\n Tokenizer.prototype.consumeBadUrlRemnants = function () {\n while (true) {\n var codePoint = this.consumeCodePoint();\n if (codePoint === RIGHT_PARENTHESIS || codePoint === EOF) {\n return;\n }\n if (isValidEscape(codePoint, this.peekCodePoint(0))) {\n this.consumeEscapedCodePoint();\n }\n }\n };\n Tokenizer.prototype.consumeStringSlice = function (count) {\n var SLICE_STACK_SIZE = 50000;\n var value = '';\n while (count > 0) {\n var amount = Math.min(SLICE_STACK_SIZE, count);\n value += fromCodePoint$1.apply(void 0, this._value.splice(0, amount));\n count -= amount;\n }\n this._value.shift();\n return value;\n };\n Tokenizer.prototype.consumeStringToken = function (endingCodePoint) {\n var value = '';\n var i = 0;\n do {\n var codePoint = this._value[i];\n if (codePoint === EOF || codePoint === undefined || codePoint === endingCodePoint) {\n value += this.consumeStringSlice(i);\n return { type: 0 /* STRING_TOKEN */, value: value };\n }\n if (codePoint === LINE_FEED) {\n this._value.splice(0, i);\n return BAD_STRING_TOKEN;\n }\n if (codePoint === REVERSE_SOLIDUS) {\n var next = this._value[i + 1];\n if (next !== EOF && next !== undefined) {\n if (next === LINE_FEED) {\n value += this.consumeStringSlice(i);\n i = -1;\n this._value.shift();\n }\n else if (isValidEscape(codePoint, next)) {\n value += this.consumeStringSlice(i);\n value += fromCodePoint$1(this.consumeEscapedCodePoint());\n i = -1;\n }\n }\n }\n i++;\n } while (true);\n };\n Tokenizer.prototype.consumeNumber = function () {\n var repr = [];\n var type = FLAG_INTEGER;\n var c1 = this.peekCodePoint(0);\n if (c1 === PLUS_SIGN || c1 === HYPHEN_MINUS) {\n repr.push(this.consumeCodePoint());\n }\n while (isDigit(this.peekCodePoint(0))) {\n repr.push(this.consumeCodePoint());\n }\n c1 = this.peekCodePoint(0);\n var c2 = this.peekCodePoint(1);\n if (c1 === FULL_STOP && isDigit(c2)) {\n repr.push(this.consumeCodePoint(), this.consumeCodePoint());\n type = FLAG_NUMBER;\n while (isDigit(this.peekCodePoint(0))) {\n repr.push(this.consumeCodePoint());\n }\n }\n c1 = this.peekCodePoint(0);\n c2 = this.peekCodePoint(1);\n var c3 = this.peekCodePoint(2);\n if ((c1 === E || c1 === e) && (((c2 === PLUS_SIGN || c2 === HYPHEN_MINUS) && isDigit(c3)) || isDigit(c2))) {\n repr.push(this.consumeCodePoint(), this.consumeCodePoint());\n type = FLAG_NUMBER;\n while (isDigit(this.peekCodePoint(0))) {\n repr.push(this.consumeCodePoint());\n }\n }\n return [stringToNumber(repr), type];\n };\n Tokenizer.prototype.consumeNumericToken = function () {\n var _a = this.consumeNumber(), number = _a[0], flags = _a[1];\n var c1 = this.peekCodePoint(0);\n var c2 = this.peekCodePoint(1);\n var c3 = this.peekCodePoint(2);\n if (isIdentifierStart(c1, c2, c3)) {\n var unit = this.consumeName();\n return { type: 15 /* DIMENSION_TOKEN */, number: number, flags: flags, unit: unit };\n }\n if (c1 === PERCENTAGE_SIGN) {\n this.consumeCodePoint();\n return { type: 16 /* PERCENTAGE_TOKEN */, number: number, flags: flags };\n }\n return { type: 17 /* NUMBER_TOKEN */, number: number, flags: flags };\n };\n Tokenizer.prototype.consumeEscapedCodePoint = function () {\n var codePoint = this.consumeCodePoint();\n if (isHex(codePoint)) {\n var hex = fromCodePoint$1(codePoint);\n while (isHex(this.peekCodePoint(0)) && hex.length < 6) {\n hex += fromCodePoint$1(this.consumeCodePoint());\n }\n if (isWhiteSpace(this.peekCodePoint(0))) {\n this.consumeCodePoint();\n }\n var hexCodePoint = parseInt(hex, 16);\n if (hexCodePoint === 0 || isSurrogateCodePoint(hexCodePoint) || hexCodePoint > 0x10ffff) {\n return REPLACEMENT_CHARACTER;\n }\n return hexCodePoint;\n }\n if (codePoint === EOF) {\n return REPLACEMENT_CHARACTER;\n }\n return codePoint;\n };\n Tokenizer.prototype.consumeName = function () {\n var result = '';\n while (true) {\n var codePoint = this.consumeCodePoint();\n if (isNameCodePoint(codePoint)) {\n result += fromCodePoint$1(codePoint);\n }\n else if (isValidEscape(codePoint, this.peekCodePoint(0))) {\n result += fromCodePoint$1(this.consumeEscapedCodePoint());\n }\n else {\n this.reconsumeCodePoint(codePoint);\n return result;\n }\n }\n };\n return Tokenizer;\n}());\n\nvar Parser = /** @class */ (function () {\n function Parser(tokens) {\n this._tokens = tokens;\n }\n Parser.create = function (value) {\n var tokenizer = new Tokenizer();\n tokenizer.write(value);\n return new Parser(tokenizer.read());\n };\n Parser.parseValue = function (value) {\n return Parser.create(value).parseComponentValue();\n };\n Parser.parseValues = function (value) {\n return Parser.create(value).parseComponentValues();\n };\n Parser.prototype.parseComponentValue = function () {\n var token = this.consumeToken();\n while (token.type === 31 /* WHITESPACE_TOKEN */) {\n token = this.consumeToken();\n }\n if (token.type === 32 /* EOF_TOKEN */) {\n throw new SyntaxError(\"Error parsing CSS component value, unexpected EOF\");\n }\n this.reconsumeToken(token);\n var value = this.consumeComponentValue();\n do {\n token = this.consumeToken();\n } while (token.type === 31 /* WHITESPACE_TOKEN */);\n if (token.type === 32 /* EOF_TOKEN */) {\n return value;\n }\n throw new SyntaxError(\"Error parsing CSS component value, multiple values found when expecting only one\");\n };\n Parser.prototype.parseComponentValues = function () {\n var values = [];\n while (true) {\n var value = this.consumeComponentValue();\n if (value.type === 32 /* EOF_TOKEN */) {\n return values;\n }\n values.push(value);\n values.push();\n }\n };\n Parser.prototype.consumeComponentValue = function () {\n var token = this.consumeToken();\n switch (token.type) {\n case 11 /* LEFT_CURLY_BRACKET_TOKEN */:\n case 28 /* LEFT_SQUARE_BRACKET_TOKEN */:\n case 2 /* LEFT_PARENTHESIS_TOKEN */:\n return this.consumeSimpleBlock(token.type);\n case 19 /* FUNCTION_TOKEN */:\n return this.consumeFunction(token);\n }\n return token;\n };\n Parser.prototype.consumeSimpleBlock = function (type) {\n var block = { type: type, values: [] };\n var token = this.consumeToken();\n while (true) {\n if (token.type === 32 /* EOF_TOKEN */ || isEndingTokenFor(token, type)) {\n return block;\n }\n this.reconsumeToken(token);\n block.values.push(this.consumeComponentValue());\n token = this.consumeToken();\n }\n };\n Parser.prototype.consumeFunction = function (functionToken) {\n var cssFunction = {\n name: functionToken.value,\n values: [],\n type: 18 /* FUNCTION */\n };\n while (true) {\n var token = this.consumeToken();\n if (token.type === 32 /* EOF_TOKEN */ || token.type === 3 /* RIGHT_PARENTHESIS_TOKEN */) {\n return cssFunction;\n }\n this.reconsumeToken(token);\n cssFunction.values.push(this.consumeComponentValue());\n }\n };\n Parser.prototype.consumeToken = function () {\n var token = this._tokens.shift();\n return typeof token === 'undefined' ? EOF_TOKEN : token;\n };\n Parser.prototype.reconsumeToken = function (token) {\n this._tokens.unshift(token);\n };\n return Parser;\n}());\nvar isDimensionToken = function (token) { return token.type === 15 /* DIMENSION_TOKEN */; };\nvar isNumberToken = function (token) { return token.type === 17 /* NUMBER_TOKEN */; };\nvar isIdentToken = function (token) { return token.type === 20 /* IDENT_TOKEN */; };\nvar isStringToken = function (token) { return token.type === 0 /* STRING_TOKEN */; };\nvar isIdentWithValue = function (token, value) {\n return isIdentToken(token) && token.value === value;\n};\nvar nonWhiteSpace = function (token) { return token.type !== 31 /* WHITESPACE_TOKEN */; };\nvar nonFunctionArgSeparator = function (token) {\n return token.type !== 31 /* WHITESPACE_TOKEN */ && token.type !== 4 /* COMMA_TOKEN */;\n};\nvar parseFunctionArgs = function (tokens) {\n var args = [];\n var arg = [];\n tokens.forEach(function (token) {\n if (token.type === 4 /* COMMA_TOKEN */) {\n if (arg.length === 0) {\n throw new Error(\"Error parsing function args, zero tokens for arg\");\n }\n args.push(arg);\n arg = [];\n return;\n }\n if (token.type !== 31 /* WHITESPACE_TOKEN */) {\n arg.push(token);\n }\n });\n if (arg.length) {\n args.push(arg);\n }\n return args;\n};\nvar isEndingTokenFor = function (token, type) {\n if (type === 11 /* LEFT_CURLY_BRACKET_TOKEN */ && token.type === 12 /* RIGHT_CURLY_BRACKET_TOKEN */) {\n return true;\n }\n if (type === 28 /* LEFT_SQUARE_BRACKET_TOKEN */ && token.type === 29 /* RIGHT_SQUARE_BRACKET_TOKEN */) {\n return true;\n }\n return type === 2 /* LEFT_PARENTHESIS_TOKEN */ && token.type === 3 /* RIGHT_PARENTHESIS_TOKEN */;\n};\n\nvar isLength = function (token) {\n return token.type === 17 /* NUMBER_TOKEN */ || token.type === 15 /* DIMENSION_TOKEN */;\n};\n\nvar isLengthPercentage = function (token) {\n return token.type === 16 /* PERCENTAGE_TOKEN */ || isLength(token);\n};\nvar parseLengthPercentageTuple = function (tokens) {\n return tokens.length > 1 ? [tokens[0], tokens[1]] : [tokens[0]];\n};\nvar ZERO_LENGTH = {\n type: 17 /* NUMBER_TOKEN */,\n number: 0,\n flags: FLAG_INTEGER\n};\nvar FIFTY_PERCENT = {\n type: 16 /* PERCENTAGE_TOKEN */,\n number: 50,\n flags: FLAG_INTEGER\n};\nvar HUNDRED_PERCENT = {\n type: 16 /* PERCENTAGE_TOKEN */,\n number: 100,\n flags: FLAG_INTEGER\n};\nvar getAbsoluteValueForTuple = function (tuple, width, height) {\n var x = tuple[0], y = tuple[1];\n return [getAbsoluteValue(x, width), getAbsoluteValue(typeof y !== 'undefined' ? y : x, height)];\n};\nvar getAbsoluteValue = function (token, parent) {\n if (token.type === 16 /* PERCENTAGE_TOKEN */) {\n return (token.number / 100) * parent;\n }\n if (isDimensionToken(token)) {\n switch (token.unit) {\n case 'rem':\n case 'em':\n return 16 * token.number; // TODO use correct font-size\n case 'px':\n default:\n return token.number;\n }\n }\n return token.number;\n};\n\nvar DEG = 'deg';\nvar GRAD = 'grad';\nvar RAD = 'rad';\nvar TURN = 'turn';\nvar angle = {\n name: 'angle',\n parse: function (_context, value) {\n if (value.type === 15 /* DIMENSION_TOKEN */) {\n switch (value.unit) {\n case DEG:\n return (Math.PI * value.number) / 180;\n case GRAD:\n return (Math.PI / 200) * value.number;\n case RAD:\n return value.number;\n case TURN:\n return Math.PI * 2 * value.number;\n }\n }\n throw new Error(\"Unsupported angle type\");\n }\n};\nvar isAngle = function (value) {\n if (value.type === 15 /* DIMENSION_TOKEN */) {\n if (value.unit === DEG || value.unit === GRAD || value.unit === RAD || value.unit === TURN) {\n return true;\n }\n }\n return false;\n};\nvar parseNamedSide = function (tokens) {\n var sideOrCorner = tokens\n .filter(isIdentToken)\n .map(function (ident) { return ident.value; })\n .join(' ');\n switch (sideOrCorner) {\n case 'to bottom right':\n case 'to right bottom':\n case 'left top':\n case 'top left':\n return [ZERO_LENGTH, ZERO_LENGTH];\n case 'to top':\n case 'bottom':\n return deg(0);\n case 'to bottom left':\n case 'to left bottom':\n case 'right top':\n case 'top right':\n return [ZERO_LENGTH, HUNDRED_PERCENT];\n case 'to right':\n case 'left':\n return deg(90);\n case 'to top left':\n case 'to left top':\n case 'right bottom':\n case 'bottom right':\n return [HUNDRED_PERCENT, HUNDRED_PERCENT];\n case 'to bottom':\n case 'top':\n return deg(180);\n case 'to top right':\n case 'to right top':\n case 'left bottom':\n case 'bottom left':\n return [HUNDRED_PERCENT, ZERO_LENGTH];\n case 'to left':\n case 'right':\n return deg(270);\n }\n return 0;\n};\nvar deg = function (deg) { return (Math.PI * deg) / 180; };\n\nvar color$1 = {\n name: 'color',\n parse: function (context, value) {\n if (value.type === 18 /* FUNCTION */) {\n var colorFunction = SUPPORTED_COLOR_FUNCTIONS[value.name];\n if (typeof colorFunction === 'undefined') {\n throw new Error(\"Attempting to parse an unsupported color function \\\"\" + value.name + \"\\\"\");\n }\n return colorFunction(context, value.values);\n }\n if (value.type === 5 /* HASH_TOKEN */) {\n if (value.value.length === 3) {\n var r = value.value.substring(0, 1);\n var g = value.value.substring(1, 2);\n var b = value.value.substring(2, 3);\n return pack(parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16), 1);\n }\n if (value.value.length === 4) {\n var r = value.value.substring(0, 1);\n var g = value.value.substring(1, 2);\n var b = value.value.substring(2, 3);\n var a = value.value.substring(3, 4);\n return pack(parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16), parseInt(a + a, 16) / 255);\n }\n if (value.value.length === 6) {\n var r = value.value.substring(0, 2);\n var g = value.value.substring(2, 4);\n var b = value.value.substring(4, 6);\n return pack(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1);\n }\n if (value.value.length === 8) {\n var r = value.value.substring(0, 2);\n var g = value.value.substring(2, 4);\n var b = value.value.substring(4, 6);\n var a = value.value.substring(6, 8);\n return pack(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), parseInt(a, 16) / 255);\n }\n }\n if (value.type === 20 /* IDENT_TOKEN */) {\n var namedColor = COLORS[value.value.toUpperCase()];\n if (typeof namedColor !== 'undefined') {\n return namedColor;\n }\n }\n return COLORS.TRANSPARENT;\n }\n};\nvar isTransparent = function (color) { return (0xff & color) === 0; };\nvar asString = function (color) {\n var alpha = 0xff & color;\n var blue = 0xff & (color >> 8);\n var green = 0xff & (color >> 16);\n var red = 0xff & (color >> 24);\n return alpha < 255 ? \"rgba(\" + red + \",\" + green + \",\" + blue + \",\" + alpha / 255 + \")\" : \"rgb(\" + red + \",\" + green + \",\" + blue + \")\";\n};\nvar pack = function (r, g, b, a) {\n return ((r << 24) | (g << 16) | (b << 8) | (Math.round(a * 255) << 0)) >>> 0;\n};\nvar getTokenColorValue = function (token, i) {\n if (token.type === 17 /* NUMBER_TOKEN */) {\n return token.number;\n }\n if (token.type === 16 /* PERCENTAGE_TOKEN */) {\n var max = i === 3 ? 1 : 255;\n return i === 3 ? (token.number / 100) * max : Math.round((token.number / 100) * max);\n }\n return 0;\n};\nvar rgb = function (_context, args) {\n var tokens = args.filter(nonFunctionArgSeparator);\n if (tokens.length === 3) {\n var _a = tokens.map(getTokenColorValue), r = _a[0], g = _a[1], b = _a[2];\n return pack(r, g, b, 1);\n }\n if (tokens.length === 4) {\n var _b = tokens.map(getTokenColorValue), r = _b[0], g = _b[1], b = _b[2], a = _b[3];\n return pack(r, g, b, a);\n }\n return 0;\n};\nfunction hue2rgb(t1, t2, hue) {\n if (hue < 0) {\n hue += 1;\n }\n if (hue >= 1) {\n hue -= 1;\n }\n if (hue < 1 / 6) {\n return (t2 - t1) * hue * 6 + t1;\n }\n else if (hue < 1 / 2) {\n return t2;\n }\n else if (hue < 2 / 3) {\n return (t2 - t1) * 6 * (2 / 3 - hue) + t1;\n }\n else {\n return t1;\n }\n}\nvar hsl = function (context, args) {\n var tokens = args.filter(nonFunctionArgSeparator);\n var hue = tokens[0], saturation = tokens[1], lightness = tokens[2], alpha = tokens[3];\n var h = (hue.type === 17 /* NUMBER_TOKEN */ ? deg(hue.number) : angle.parse(context, hue)) / (Math.PI * 2);\n var s = isLengthPercentage(saturation) ? saturation.number / 100 : 0;\n var l = isLengthPercentage(lightness) ? lightness.number / 100 : 0;\n var a = typeof alpha !== 'undefined' && isLengthPercentage(alpha) ? getAbsoluteValue(alpha, 1) : 1;\n if (s === 0) {\n return pack(l * 255, l * 255, l * 255, 1);\n }\n var t2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;\n var t1 = l * 2 - t2;\n var r = hue2rgb(t1, t2, h + 1 / 3);\n var g = hue2rgb(t1, t2, h);\n var b = hue2rgb(t1, t2, h - 1 / 3);\n return pack(r * 255, g * 255, b * 255, a);\n};\nvar SUPPORTED_COLOR_FUNCTIONS = {\n hsl: hsl,\n hsla: hsl,\n rgb: rgb,\n rgba: rgb\n};\nvar parseColor = function (context, value) {\n return color$1.parse(context, Parser.create(value).parseComponentValue());\n};\nvar COLORS = {\n ALICEBLUE: 0xf0f8ffff,\n ANTIQUEWHITE: 0xfaebd7ff,\n AQUA: 0x00ffffff,\n AQUAMARINE: 0x7fffd4ff,\n AZURE: 0xf0ffffff,\n BEIGE: 0xf5f5dcff,\n BISQUE: 0xffe4c4ff,\n BLACK: 0x000000ff,\n BLANCHEDALMOND: 0xffebcdff,\n BLUE: 0x0000ffff,\n BLUEVIOLET: 0x8a2be2ff,\n BROWN: 0xa52a2aff,\n BURLYWOOD: 0xdeb887ff,\n CADETBLUE: 0x5f9ea0ff,\n CHARTREUSE: 0x7fff00ff,\n CHOCOLATE: 0xd2691eff,\n CORAL: 0xff7f50ff,\n CORNFLOWERBLUE: 0x6495edff,\n CORNSILK: 0xfff8dcff,\n CRIMSON: 0xdc143cff,\n CYAN: 0x00ffffff,\n DARKBLUE: 0x00008bff,\n DARKCYAN: 0x008b8bff,\n DARKGOLDENROD: 0xb886bbff,\n DARKGRAY: 0xa9a9a9ff,\n DARKGREEN: 0x006400ff,\n DARKGREY: 0xa9a9a9ff,\n DARKKHAKI: 0xbdb76bff,\n DARKMAGENTA: 0x8b008bff,\n DARKOLIVEGREEN: 0x556b2fff,\n DARKORANGE: 0xff8c00ff,\n DARKORCHID: 0x9932ccff,\n DARKRED: 0x8b0000ff,\n DARKSALMON: 0xe9967aff,\n DARKSEAGREEN: 0x8fbc8fff,\n DARKSLATEBLUE: 0x483d8bff,\n DARKSLATEGRAY: 0x2f4f4fff,\n DARKSLATEGREY: 0x2f4f4fff,\n DARKTURQUOISE: 0x00ced1ff,\n DARKVIOLET: 0x9400d3ff,\n DEEPPINK: 0xff1493ff,\n DEEPSKYBLUE: 0x00bfffff,\n DIMGRAY: 0x696969ff,\n DIMGREY: 0x696969ff,\n DODGERBLUE: 0x1e90ffff,\n FIREBRICK: 0xb22222ff,\n FLORALWHITE: 0xfffaf0ff,\n FORESTGREEN: 0x228b22ff,\n FUCHSIA: 0xff00ffff,\n GAINSBORO: 0xdcdcdcff,\n GHOSTWHITE: 0xf8f8ffff,\n GOLD: 0xffd700ff,\n GOLDENROD: 0xdaa520ff,\n GRAY: 0x808080ff,\n GREEN: 0x008000ff,\n GREENYELLOW: 0xadff2fff,\n GREY: 0x808080ff,\n HONEYDEW: 0xf0fff0ff,\n HOTPINK: 0xff69b4ff,\n INDIANRED: 0xcd5c5cff,\n INDIGO: 0x4b0082ff,\n IVORY: 0xfffff0ff,\n KHAKI: 0xf0e68cff,\n LAVENDER: 0xe6e6faff,\n LAVENDERBLUSH: 0xfff0f5ff,\n LAWNGREEN: 0x7cfc00ff,\n LEMONCHIFFON: 0xfffacdff,\n LIGHTBLUE: 0xadd8e6ff,\n LIGHTCORAL: 0xf08080ff,\n LIGHTCYAN: 0xe0ffffff,\n LIGHTGOLDENRODYELLOW: 0xfafad2ff,\n LIGHTGRAY: 0xd3d3d3ff,\n LIGHTGREEN: 0x90ee90ff,\n LIGHTGREY: 0xd3d3d3ff,\n LIGHTPINK: 0xffb6c1ff,\n LIGHTSALMON: 0xffa07aff,\n LIGHTSEAGREEN: 0x20b2aaff,\n LIGHTSKYBLUE: 0x87cefaff,\n LIGHTSLATEGRAY: 0x778899ff,\n LIGHTSLATEGREY: 0x778899ff,\n LIGHTSTEELBLUE: 0xb0c4deff,\n LIGHTYELLOW: 0xffffe0ff,\n LIME: 0x00ff00ff,\n LIMEGREEN: 0x32cd32ff,\n LINEN: 0xfaf0e6ff,\n MAGENTA: 0xff00ffff,\n MAROON: 0x800000ff,\n MEDIUMAQUAMARINE: 0x66cdaaff,\n MEDIUMBLUE: 0x0000cdff,\n MEDIUMORCHID: 0xba55d3ff,\n MEDIUMPURPLE: 0x9370dbff,\n MEDIUMSEAGREEN: 0x3cb371ff,\n MEDIUMSLATEBLUE: 0x7b68eeff,\n MEDIUMSPRINGGREEN: 0x00fa9aff,\n MEDIUMTURQUOISE: 0x48d1ccff,\n MEDIUMVIOLETRED: 0xc71585ff,\n MIDNIGHTBLUE: 0x191970ff,\n MINTCREAM: 0xf5fffaff,\n MISTYROSE: 0xffe4e1ff,\n MOCCASIN: 0xffe4b5ff,\n NAVAJOWHITE: 0xffdeadff,\n NAVY: 0x000080ff,\n OLDLACE: 0xfdf5e6ff,\n OLIVE: 0x808000ff,\n OLIVEDRAB: 0x6b8e23ff,\n ORANGE: 0xffa500ff,\n ORANGERED: 0xff4500ff,\n ORCHID: 0xda70d6ff,\n PALEGOLDENROD: 0xeee8aaff,\n PALEGREEN: 0x98fb98ff,\n PALETURQUOISE: 0xafeeeeff,\n PALEVIOLETRED: 0xdb7093ff,\n PAPAYAWHIP: 0xffefd5ff,\n PEACHPUFF: 0xffdab9ff,\n PERU: 0xcd853fff,\n PINK: 0xffc0cbff,\n PLUM: 0xdda0ddff,\n POWDERBLUE: 0xb0e0e6ff,\n PURPLE: 0x800080ff,\n REBECCAPURPLE: 0x663399ff,\n RED: 0xff0000ff,\n ROSYBROWN: 0xbc8f8fff,\n ROYALBLUE: 0x4169e1ff,\n SADDLEBROWN: 0x8b4513ff,\n SALMON: 0xfa8072ff,\n SANDYBROWN: 0xf4a460ff,\n SEAGREEN: 0x2e8b57ff,\n SEASHELL: 0xfff5eeff,\n SIENNA: 0xa0522dff,\n SILVER: 0xc0c0c0ff,\n SKYBLUE: 0x87ceebff,\n SLATEBLUE: 0x6a5acdff,\n SLATEGRAY: 0x708090ff,\n SLATEGREY: 0x708090ff,\n SNOW: 0xfffafaff,\n SPRINGGREEN: 0x00ff7fff,\n STEELBLUE: 0x4682b4ff,\n TAN: 0xd2b48cff,\n TEAL: 0x008080ff,\n THISTLE: 0xd8bfd8ff,\n TOMATO: 0xff6347ff,\n TRANSPARENT: 0x00000000,\n TURQUOISE: 0x40e0d0ff,\n VIOLET: 0xee82eeff,\n WHEAT: 0xf5deb3ff,\n WHITE: 0xffffffff,\n WHITESMOKE: 0xf5f5f5ff,\n YELLOW: 0xffff00ff,\n YELLOWGREEN: 0x9acd32ff\n};\n\nvar backgroundClip = {\n name: 'background-clip',\n initialValue: 'border-box',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return tokens.map(function (token) {\n if (isIdentToken(token)) {\n switch (token.value) {\n case 'padding-box':\n return 1 /* PADDING_BOX */;\n case 'content-box':\n return 2 /* CONTENT_BOX */;\n }\n }\n return 0 /* BORDER_BOX */;\n });\n }\n};\n\nvar backgroundColor = {\n name: \"background-color\",\n initialValue: 'transparent',\n prefix: false,\n type: 3 /* TYPE_VALUE */,\n format: 'color'\n};\n\nvar parseColorStop = function (context, args) {\n var color = color$1.parse(context, args[0]);\n var stop = args[1];\n return stop && isLengthPercentage(stop) ? { color: color, stop: stop } : { color: color, stop: null };\n};\nvar processColorStops = function (stops, lineLength) {\n var first = stops[0];\n var last = stops[stops.length - 1];\n if (first.stop === null) {\n first.stop = ZERO_LENGTH;\n }\n if (last.stop === null) {\n last.stop = HUNDRED_PERCENT;\n }\n var processStops = [];\n var previous = 0;\n for (var i = 0; i < stops.length; i++) {\n var stop_1 = stops[i].stop;\n if (stop_1 !== null) {\n var absoluteValue = getAbsoluteValue(stop_1, lineLength);\n if (absoluteValue > previous) {\n processStops.push(absoluteValue);\n }\n else {\n processStops.push(previous);\n }\n previous = absoluteValue;\n }\n else {\n processStops.push(null);\n }\n }\n var gapBegin = null;\n for (var i = 0; i < processStops.length; i++) {\n var stop_2 = processStops[i];\n if (stop_2 === null) {\n if (gapBegin === null) {\n gapBegin = i;\n }\n }\n else if (gapBegin !== null) {\n var gapLength = i - gapBegin;\n var beforeGap = processStops[gapBegin - 1];\n var gapValue = (stop_2 - beforeGap) / (gapLength + 1);\n for (var g = 1; g <= gapLength; g++) {\n processStops[gapBegin + g - 1] = gapValue * g;\n }\n gapBegin = null;\n }\n }\n return stops.map(function (_a, i) {\n var color = _a.color;\n return { color: color, stop: Math.max(Math.min(1, processStops[i] / lineLength), 0) };\n });\n};\nvar getAngleFromCorner = function (corner, width, height) {\n var centerX = width / 2;\n var centerY = height / 2;\n var x = getAbsoluteValue(corner[0], width) - centerX;\n var y = centerY - getAbsoluteValue(corner[1], height);\n return (Math.atan2(y, x) + Math.PI * 2) % (Math.PI * 2);\n};\nvar calculateGradientDirection = function (angle, width, height) {\n var radian = typeof angle === 'number' ? angle : getAngleFromCorner(angle, width, height);\n var lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));\n var halfWidth = width / 2;\n var halfHeight = height / 2;\n var halfLineLength = lineLength / 2;\n var yDiff = Math.sin(radian - Math.PI / 2) * halfLineLength;\n var xDiff = Math.cos(radian - Math.PI / 2) * halfLineLength;\n return [lineLength, halfWidth - xDiff, halfWidth + xDiff, halfHeight - yDiff, halfHeight + yDiff];\n};\nvar distance = function (a, b) { return Math.sqrt(a * a + b * b); };\nvar findCorner = function (width, height, x, y, closest) {\n var corners = [\n [0, 0],\n [0, height],\n [width, 0],\n [width, height]\n ];\n return corners.reduce(function (stat, corner) {\n var cx = corner[0], cy = corner[1];\n var d = distance(x - cx, y - cy);\n if (closest ? d < stat.optimumDistance : d > stat.optimumDistance) {\n return {\n optimumCorner: corner,\n optimumDistance: d\n };\n }\n return stat;\n }, {\n optimumDistance: closest ? Infinity : -Infinity,\n optimumCorner: null\n }).optimumCorner;\n};\nvar calculateRadius = function (gradient, x, y, width, height) {\n var rx = 0;\n var ry = 0;\n switch (gradient.size) {\n case 0 /* CLOSEST_SIDE */:\n // The ending shape is sized so that that it exactly meets the side of the gradient box closest to the gradient’s center.\n // If the shape is an ellipse, it exactly meets the closest side in each dimension.\n if (gradient.shape === 0 /* CIRCLE */) {\n rx = ry = Math.min(Math.abs(x), Math.abs(x - width), Math.abs(y), Math.abs(y - height));\n }\n else if (gradient.shape === 1 /* ELLIPSE */) {\n rx = Math.min(Math.abs(x), Math.abs(x - width));\n ry = Math.min(Math.abs(y), Math.abs(y - height));\n }\n break;\n case 2 /* CLOSEST_CORNER */:\n // The ending shape is sized so that that it passes through the corner of the gradient box closest to the gradient’s center.\n // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified.\n if (gradient.shape === 0 /* CIRCLE */) {\n rx = ry = Math.min(distance(x, y), distance(x, y - height), distance(x - width, y), distance(x - width, y - height));\n }\n else if (gradient.shape === 1 /* ELLIPSE */) {\n // Compute the ratio ry/rx (which is to be the same as for \"closest-side\")\n var c = Math.min(Math.abs(y), Math.abs(y - height)) / Math.min(Math.abs(x), Math.abs(x - width));\n var _a = findCorner(width, height, x, y, true), cx = _a[0], cy = _a[1];\n rx = distance(cx - x, (cy - y) / c);\n ry = c * rx;\n }\n break;\n case 1 /* FARTHEST_SIDE */:\n // Same as closest-side, except the ending shape is sized based on the farthest side(s)\n if (gradient.shape === 0 /* CIRCLE */) {\n rx = ry = Math.max(Math.abs(x), Math.abs(x - width), Math.abs(y), Math.abs(y - height));\n }\n else if (gradient.shape === 1 /* ELLIPSE */) {\n rx = Math.max(Math.abs(x), Math.abs(x - width));\n ry = Math.max(Math.abs(y), Math.abs(y - height));\n }\n break;\n case 3 /* FARTHEST_CORNER */:\n // Same as closest-corner, except the ending shape is sized based on the farthest corner.\n // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified.\n if (gradient.shape === 0 /* CIRCLE */) {\n rx = ry = Math.max(distance(x, y), distance(x, y - height), distance(x - width, y), distance(x - width, y - height));\n }\n else if (gradient.shape === 1 /* ELLIPSE */) {\n // Compute the ratio ry/rx (which is to be the same as for \"farthest-side\")\n var c = Math.max(Math.abs(y), Math.abs(y - height)) / Math.max(Math.abs(x), Math.abs(x - width));\n var _b = findCorner(width, height, x, y, false), cx = _b[0], cy = _b[1];\n rx = distance(cx - x, (cy - y) / c);\n ry = c * rx;\n }\n break;\n }\n if (Array.isArray(gradient.size)) {\n rx = getAbsoluteValue(gradient.size[0], width);\n ry = gradient.size.length === 2 ? getAbsoluteValue(gradient.size[1], height) : rx;\n }\n return [rx, ry];\n};\n\nvar linearGradient = function (context, tokens) {\n var angle$1 = deg(180);\n var stops = [];\n parseFunctionArgs(tokens).forEach(function (arg, i) {\n if (i === 0) {\n var firstToken = arg[0];\n if (firstToken.type === 20 /* IDENT_TOKEN */ && firstToken.value === 'to') {\n angle$1 = parseNamedSide(arg);\n return;\n }\n else if (isAngle(firstToken)) {\n angle$1 = angle.parse(context, firstToken);\n return;\n }\n }\n var colorStop = parseColorStop(context, arg);\n stops.push(colorStop);\n });\n return { angle: angle$1, stops: stops, type: 1 /* LINEAR_GRADIENT */ };\n};\n\nvar prefixLinearGradient = function (context, tokens) {\n var angle$1 = deg(180);\n var stops = [];\n parseFunctionArgs(tokens).forEach(function (arg, i) {\n if (i === 0) {\n var firstToken = arg[0];\n if (firstToken.type === 20 /* IDENT_TOKEN */ &&\n ['top', 'left', 'right', 'bottom'].indexOf(firstToken.value) !== -1) {\n angle$1 = parseNamedSide(arg);\n return;\n }\n else if (isAngle(firstToken)) {\n angle$1 = (angle.parse(context, firstToken) + deg(270)) % deg(360);\n return;\n }\n }\n var colorStop = parseColorStop(context, arg);\n stops.push(colorStop);\n });\n return {\n angle: angle$1,\n stops: stops,\n type: 1 /* LINEAR_GRADIENT */\n };\n};\n\nvar webkitGradient = function (context, tokens) {\n var angle = deg(180);\n var stops = [];\n var type = 1 /* LINEAR_GRADIENT */;\n var shape = 0 /* CIRCLE */;\n var size = 3 /* FARTHEST_CORNER */;\n var position = [];\n parseFunctionArgs(tokens).forEach(function (arg, i) {\n var firstToken = arg[0];\n if (i === 0) {\n if (isIdentToken(firstToken) && firstToken.value === 'linear') {\n type = 1 /* LINEAR_GRADIENT */;\n return;\n }\n else if (isIdentToken(firstToken) && firstToken.value === 'radial') {\n type = 2 /* RADIAL_GRADIENT */;\n return;\n }\n }\n if (firstToken.type === 18 /* FUNCTION */) {\n if (firstToken.name === 'from') {\n var color = color$1.parse(context, firstToken.values[0]);\n stops.push({ stop: ZERO_LENGTH, color: color });\n }\n else if (firstToken.name === 'to') {\n var color = color$1.parse(context, firstToken.values[0]);\n stops.push({ stop: HUNDRED_PERCENT, color: color });\n }\n else if (firstToken.name === 'color-stop') {\n var values = firstToken.values.filter(nonFunctionArgSeparator);\n if (values.length === 2) {\n var color = color$1.parse(context, values[1]);\n var stop_1 = values[0];\n if (isNumberToken(stop_1)) {\n stops.push({\n stop: { type: 16 /* PERCENTAGE_TOKEN */, number: stop_1.number * 100, flags: stop_1.flags },\n color: color\n });\n }\n }\n }\n }\n });\n return type === 1 /* LINEAR_GRADIENT */\n ? {\n angle: (angle + deg(180)) % deg(360),\n stops: stops,\n type: type\n }\n : { size: size, shape: shape, stops: stops, position: position, type: type };\n};\n\nvar CLOSEST_SIDE = 'closest-side';\nvar FARTHEST_SIDE = 'farthest-side';\nvar CLOSEST_CORNER = 'closest-corner';\nvar FARTHEST_CORNER = 'farthest-corner';\nvar CIRCLE = 'circle';\nvar ELLIPSE = 'ellipse';\nvar COVER = 'cover';\nvar CONTAIN = 'contain';\nvar radialGradient = function (context, tokens) {\n var shape = 0 /* CIRCLE */;\n var size = 3 /* FARTHEST_CORNER */;\n var stops = [];\n var position = [];\n parseFunctionArgs(tokens).forEach(function (arg, i) {\n var isColorStop = true;\n if (i === 0) {\n var isAtPosition_1 = false;\n isColorStop = arg.reduce(function (acc, token) {\n if (isAtPosition_1) {\n if (isIdentToken(token)) {\n switch (token.value) {\n case 'center':\n position.push(FIFTY_PERCENT);\n return acc;\n case 'top':\n case 'left':\n position.push(ZERO_LENGTH);\n return acc;\n case 'right':\n case 'bottom':\n position.push(HUNDRED_PERCENT);\n return acc;\n }\n }\n else if (isLengthPercentage(token) || isLength(token)) {\n position.push(token);\n }\n }\n else if (isIdentToken(token)) {\n switch (token.value) {\n case CIRCLE:\n shape = 0 /* CIRCLE */;\n return false;\n case ELLIPSE:\n shape = 1 /* ELLIPSE */;\n return false;\n case 'at':\n isAtPosition_1 = true;\n return false;\n case CLOSEST_SIDE:\n size = 0 /* CLOSEST_SIDE */;\n return false;\n case COVER:\n case FARTHEST_SIDE:\n size = 1 /* FARTHEST_SIDE */;\n return false;\n case CONTAIN:\n case CLOSEST_CORNER:\n size = 2 /* CLOSEST_CORNER */;\n return false;\n case FARTHEST_CORNER:\n size = 3 /* FARTHEST_CORNER */;\n return false;\n }\n }\n else if (isLength(token) || isLengthPercentage(token)) {\n if (!Array.isArray(size)) {\n size = [];\n }\n size.push(token);\n return false;\n }\n return acc;\n }, isColorStop);\n }\n if (isColorStop) {\n var colorStop = parseColorStop(context, arg);\n stops.push(colorStop);\n }\n });\n return { size: size, shape: shape, stops: stops, position: position, type: 2 /* RADIAL_GRADIENT */ };\n};\n\nvar prefixRadialGradient = function (context, tokens) {\n var shape = 0 /* CIRCLE */;\n var size = 3 /* FARTHEST_CORNER */;\n var stops = [];\n var position = [];\n parseFunctionArgs(tokens).forEach(function (arg, i) {\n var isColorStop = true;\n if (i === 0) {\n isColorStop = arg.reduce(function (acc, token) {\n if (isIdentToken(token)) {\n switch (token.value) {\n case 'center':\n position.push(FIFTY_PERCENT);\n return false;\n case 'top':\n case 'left':\n position.push(ZERO_LENGTH);\n return false;\n case 'right':\n case 'bottom':\n position.push(HUNDRED_PERCENT);\n return false;\n }\n }\n else if (isLengthPercentage(token) || isLength(token)) {\n position.push(token);\n return false;\n }\n return acc;\n }, isColorStop);\n }\n else if (i === 1) {\n isColorStop = arg.reduce(function (acc, token) {\n if (isIdentToken(token)) {\n switch (token.value) {\n case CIRCLE:\n shape = 0 /* CIRCLE */;\n return false;\n case ELLIPSE:\n shape = 1 /* ELLIPSE */;\n return false;\n case CONTAIN:\n case CLOSEST_SIDE:\n size = 0 /* CLOSEST_SIDE */;\n return false;\n case FARTHEST_SIDE:\n size = 1 /* FARTHEST_SIDE */;\n return false;\n case CLOSEST_CORNER:\n size = 2 /* CLOSEST_CORNER */;\n return false;\n case COVER:\n case FARTHEST_CORNER:\n size = 3 /* FARTHEST_CORNER */;\n return false;\n }\n }\n else if (isLength(token) || isLengthPercentage(token)) {\n if (!Array.isArray(size)) {\n size = [];\n }\n size.push(token);\n return false;\n }\n return acc;\n }, isColorStop);\n }\n if (isColorStop) {\n var colorStop = parseColorStop(context, arg);\n stops.push(colorStop);\n }\n });\n return { size: size, shape: shape, stops: stops, position: position, type: 2 /* RADIAL_GRADIENT */ };\n};\n\nvar isLinearGradient = function (background) {\n return background.type === 1 /* LINEAR_GRADIENT */;\n};\nvar isRadialGradient = function (background) {\n return background.type === 2 /* RADIAL_GRADIENT */;\n};\nvar image$1 = {\n name: 'image',\n parse: function (context, value) {\n if (value.type === 22 /* URL_TOKEN */) {\n var image_1 = { url: value.value, type: 0 /* URL */ };\n context.cache.addImage(value.value);\n return image_1;\n }\n if (value.type === 18 /* FUNCTION */) {\n var imageFunction = SUPPORTED_IMAGE_FUNCTIONS[value.name];\n if (typeof imageFunction === 'undefined') {\n throw new Error(\"Attempting to parse an unsupported image function \\\"\" + value.name + \"\\\"\");\n }\n return imageFunction(context, value.values);\n }\n throw new Error(\"Unsupported image type \" + value.type);\n }\n};\nfunction isSupportedImage(value) {\n return (!(value.type === 20 /* IDENT_TOKEN */ && value.value === 'none') &&\n (value.type !== 18 /* FUNCTION */ || !!SUPPORTED_IMAGE_FUNCTIONS[value.name]));\n}\nvar SUPPORTED_IMAGE_FUNCTIONS = {\n 'linear-gradient': linearGradient,\n '-moz-linear-gradient': prefixLinearGradient,\n '-ms-linear-gradient': prefixLinearGradient,\n '-o-linear-gradient': prefixLinearGradient,\n '-webkit-linear-gradient': prefixLinearGradient,\n 'radial-gradient': radialGradient,\n '-moz-radial-gradient': prefixRadialGradient,\n '-ms-radial-gradient': prefixRadialGradient,\n '-o-radial-gradient': prefixRadialGradient,\n '-webkit-radial-gradient': prefixRadialGradient,\n '-webkit-gradient': webkitGradient\n};\n\nvar backgroundImage = {\n name: 'background-image',\n initialValue: 'none',\n type: 1 /* LIST */,\n prefix: false,\n parse: function (context, tokens) {\n if (tokens.length === 0) {\n return [];\n }\n var first = tokens[0];\n if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') {\n return [];\n }\n return tokens\n .filter(function (value) { return nonFunctionArgSeparator(value) && isSupportedImage(value); })\n .map(function (value) { return image$1.parse(context, value); });\n }\n};\n\nvar backgroundOrigin = {\n name: 'background-origin',\n initialValue: 'border-box',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return tokens.map(function (token) {\n if (isIdentToken(token)) {\n switch (token.value) {\n case 'padding-box':\n return 1 /* PADDING_BOX */;\n case 'content-box':\n return 2 /* CONTENT_BOX */;\n }\n }\n return 0 /* BORDER_BOX */;\n });\n }\n};\n\nvar backgroundPosition = {\n name: 'background-position',\n initialValue: '0% 0%',\n type: 1 /* LIST */,\n prefix: false,\n parse: function (_context, tokens) {\n return parseFunctionArgs(tokens)\n .map(function (values) { return values.filter(isLengthPercentage); })\n .map(parseLengthPercentageTuple);\n }\n};\n\nvar backgroundRepeat = {\n name: 'background-repeat',\n initialValue: 'repeat',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return parseFunctionArgs(tokens)\n .map(function (values) {\n return values\n .filter(isIdentToken)\n .map(function (token) { return token.value; })\n .join(' ');\n })\n .map(parseBackgroundRepeat);\n }\n};\nvar parseBackgroundRepeat = function (value) {\n switch (value) {\n case 'no-repeat':\n return 1 /* NO_REPEAT */;\n case 'repeat-x':\n case 'repeat no-repeat':\n return 2 /* REPEAT_X */;\n case 'repeat-y':\n case 'no-repeat repeat':\n return 3 /* REPEAT_Y */;\n case 'repeat':\n default:\n return 0 /* REPEAT */;\n }\n};\n\nvar BACKGROUND_SIZE;\n(function (BACKGROUND_SIZE) {\n BACKGROUND_SIZE[\"AUTO\"] = \"auto\";\n BACKGROUND_SIZE[\"CONTAIN\"] = \"contain\";\n BACKGROUND_SIZE[\"COVER\"] = \"cover\";\n})(BACKGROUND_SIZE || (BACKGROUND_SIZE = {}));\nvar backgroundSize = {\n name: 'background-size',\n initialValue: '0',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return parseFunctionArgs(tokens).map(function (values) { return values.filter(isBackgroundSizeInfoToken); });\n }\n};\nvar isBackgroundSizeInfoToken = function (value) {\n return isIdentToken(value) || isLengthPercentage(value);\n};\n\nvar borderColorForSide = function (side) { return ({\n name: \"border-\" + side + \"-color\",\n initialValue: 'transparent',\n prefix: false,\n type: 3 /* TYPE_VALUE */,\n format: 'color'\n}); };\nvar borderTopColor = borderColorForSide('top');\nvar borderRightColor = borderColorForSide('right');\nvar borderBottomColor = borderColorForSide('bottom');\nvar borderLeftColor = borderColorForSide('left');\n\nvar borderRadiusForSide = function (side) { return ({\n name: \"border-radius-\" + side,\n initialValue: '0 0',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return parseLengthPercentageTuple(tokens.filter(isLengthPercentage));\n }\n}); };\nvar borderTopLeftRadius = borderRadiusForSide('top-left');\nvar borderTopRightRadius = borderRadiusForSide('top-right');\nvar borderBottomRightRadius = borderRadiusForSide('bottom-right');\nvar borderBottomLeftRadius = borderRadiusForSide('bottom-left');\n\nvar borderStyleForSide = function (side) { return ({\n name: \"border-\" + side + \"-style\",\n initialValue: 'solid',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, style) {\n switch (style) {\n case 'none':\n return 0 /* NONE */;\n case 'dashed':\n return 2 /* DASHED */;\n case 'dotted':\n return 3 /* DOTTED */;\n case 'double':\n return 4 /* DOUBLE */;\n }\n return 1 /* SOLID */;\n }\n}); };\nvar borderTopStyle = borderStyleForSide('top');\nvar borderRightStyle = borderStyleForSide('right');\nvar borderBottomStyle = borderStyleForSide('bottom');\nvar borderLeftStyle = borderStyleForSide('left');\n\nvar borderWidthForSide = function (side) { return ({\n name: \"border-\" + side + \"-width\",\n initialValue: '0',\n type: 0 /* VALUE */,\n prefix: false,\n parse: function (_context, token) {\n if (isDimensionToken(token)) {\n return token.number;\n }\n return 0;\n }\n}); };\nvar borderTopWidth = borderWidthForSide('top');\nvar borderRightWidth = borderWidthForSide('right');\nvar borderBottomWidth = borderWidthForSide('bottom');\nvar borderLeftWidth = borderWidthForSide('left');\n\nvar color = {\n name: \"color\",\n initialValue: 'transparent',\n prefix: false,\n type: 3 /* TYPE_VALUE */,\n format: 'color'\n};\n\nvar direction = {\n name: 'direction',\n initialValue: 'ltr',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, direction) {\n switch (direction) {\n case 'rtl':\n return 1 /* RTL */;\n case 'ltr':\n default:\n return 0 /* LTR */;\n }\n }\n};\n\nvar display = {\n name: 'display',\n initialValue: 'inline-block',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return tokens.filter(isIdentToken).reduce(function (bit, token) {\n return bit | parseDisplayValue(token.value);\n }, 0 /* NONE */);\n }\n};\nvar parseDisplayValue = function (display) {\n switch (display) {\n case 'block':\n case '-webkit-box':\n return 2 /* BLOCK */;\n case 'inline':\n return 4 /* INLINE */;\n case 'run-in':\n return 8 /* RUN_IN */;\n case 'flow':\n return 16 /* FLOW */;\n case 'flow-root':\n return 32 /* FLOW_ROOT */;\n case 'table':\n return 64 /* TABLE */;\n case 'flex':\n case '-webkit-flex':\n return 128 /* FLEX */;\n case 'grid':\n case '-ms-grid':\n return 256 /* GRID */;\n case 'ruby':\n return 512 /* RUBY */;\n case 'subgrid':\n return 1024 /* SUBGRID */;\n case 'list-item':\n return 2048 /* LIST_ITEM */;\n case 'table-row-group':\n return 4096 /* TABLE_ROW_GROUP */;\n case 'table-header-group':\n return 8192 /* TABLE_HEADER_GROUP */;\n case 'table-footer-group':\n return 16384 /* TABLE_FOOTER_GROUP */;\n case 'table-row':\n return 32768 /* TABLE_ROW */;\n case 'table-cell':\n return 65536 /* TABLE_CELL */;\n case 'table-column-group':\n return 131072 /* TABLE_COLUMN_GROUP */;\n case 'table-column':\n return 262144 /* TABLE_COLUMN */;\n case 'table-caption':\n return 524288 /* TABLE_CAPTION */;\n case 'ruby-base':\n return 1048576 /* RUBY_BASE */;\n case 'ruby-text':\n return 2097152 /* RUBY_TEXT */;\n case 'ruby-base-container':\n return 4194304 /* RUBY_BASE_CONTAINER */;\n case 'ruby-text-container':\n return 8388608 /* RUBY_TEXT_CONTAINER */;\n case 'contents':\n return 16777216 /* CONTENTS */;\n case 'inline-block':\n return 33554432 /* INLINE_BLOCK */;\n case 'inline-list-item':\n return 67108864 /* INLINE_LIST_ITEM */;\n case 'inline-table':\n return 134217728 /* INLINE_TABLE */;\n case 'inline-flex':\n return 268435456 /* INLINE_FLEX */;\n case 'inline-grid':\n return 536870912 /* INLINE_GRID */;\n }\n return 0 /* NONE */;\n};\n\nvar float = {\n name: 'float',\n initialValue: 'none',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, float) {\n switch (float) {\n case 'left':\n return 1 /* LEFT */;\n case 'right':\n return 2 /* RIGHT */;\n case 'inline-start':\n return 3 /* INLINE_START */;\n case 'inline-end':\n return 4 /* INLINE_END */;\n }\n return 0 /* NONE */;\n }\n};\n\nvar letterSpacing = {\n name: 'letter-spacing',\n initialValue: '0',\n prefix: false,\n type: 0 /* VALUE */,\n parse: function (_context, token) {\n if (token.type === 20 /* IDENT_TOKEN */ && token.value === 'normal') {\n return 0;\n }\n if (token.type === 17 /* NUMBER_TOKEN */) {\n return token.number;\n }\n if (token.type === 15 /* DIMENSION_TOKEN */) {\n return token.number;\n }\n return 0;\n }\n};\n\nvar LINE_BREAK;\n(function (LINE_BREAK) {\n LINE_BREAK[\"NORMAL\"] = \"normal\";\n LINE_BREAK[\"STRICT\"] = \"strict\";\n})(LINE_BREAK || (LINE_BREAK = {}));\nvar lineBreak = {\n name: 'line-break',\n initialValue: 'normal',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, lineBreak) {\n switch (lineBreak) {\n case 'strict':\n return LINE_BREAK.STRICT;\n case 'normal':\n default:\n return LINE_BREAK.NORMAL;\n }\n }\n};\n\nvar lineHeight = {\n name: 'line-height',\n initialValue: 'normal',\n prefix: false,\n type: 4 /* TOKEN_VALUE */\n};\nvar computeLineHeight = function (token, fontSize) {\n if (isIdentToken(token) && token.value === 'normal') {\n return 1.2 * fontSize;\n }\n else if (token.type === 17 /* NUMBER_TOKEN */) {\n return fontSize * token.number;\n }\n else if (isLengthPercentage(token)) {\n return getAbsoluteValue(token, fontSize);\n }\n return fontSize;\n};\n\nvar listStyleImage = {\n name: 'list-style-image',\n initialValue: 'none',\n type: 0 /* VALUE */,\n prefix: false,\n parse: function (context, token) {\n if (token.type === 20 /* IDENT_TOKEN */ && token.value === 'none') {\n return null;\n }\n return image$1.parse(context, token);\n }\n};\n\nvar listStylePosition = {\n name: 'list-style-position',\n initialValue: 'outside',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, position) {\n switch (position) {\n case 'inside':\n return 0 /* INSIDE */;\n case 'outside':\n default:\n return 1 /* OUTSIDE */;\n }\n }\n};\n\nvar listStyleType = {\n name: 'list-style-type',\n initialValue: 'none',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, type) {\n switch (type) {\n case 'disc':\n return 0 /* DISC */;\n case 'circle':\n return 1 /* CIRCLE */;\n case 'square':\n return 2 /* SQUARE */;\n case 'decimal':\n return 3 /* DECIMAL */;\n case 'cjk-decimal':\n return 4 /* CJK_DECIMAL */;\n case 'decimal-leading-zero':\n return 5 /* DECIMAL_LEADING_ZERO */;\n case 'lower-roman':\n return 6 /* LOWER_ROMAN */;\n case 'upper-roman':\n return 7 /* UPPER_ROMAN */;\n case 'lower-greek':\n return 8 /* LOWER_GREEK */;\n case 'lower-alpha':\n return 9 /* LOWER_ALPHA */;\n case 'upper-alpha':\n return 10 /* UPPER_ALPHA */;\n case 'arabic-indic':\n return 11 /* ARABIC_INDIC */;\n case 'armenian':\n return 12 /* ARMENIAN */;\n case 'bengali':\n return 13 /* BENGALI */;\n case 'cambodian':\n return 14 /* CAMBODIAN */;\n case 'cjk-earthly-branch':\n return 15 /* CJK_EARTHLY_BRANCH */;\n case 'cjk-heavenly-stem':\n return 16 /* CJK_HEAVENLY_STEM */;\n case 'cjk-ideographic':\n return 17 /* CJK_IDEOGRAPHIC */;\n case 'devanagari':\n return 18 /* DEVANAGARI */;\n case 'ethiopic-numeric':\n return 19 /* ETHIOPIC_NUMERIC */;\n case 'georgian':\n return 20 /* GEORGIAN */;\n case 'gujarati':\n return 21 /* GUJARATI */;\n case 'gurmukhi':\n return 22 /* GURMUKHI */;\n case 'hebrew':\n return 22 /* HEBREW */;\n case 'hiragana':\n return 23 /* HIRAGANA */;\n case 'hiragana-iroha':\n return 24 /* HIRAGANA_IROHA */;\n case 'japanese-formal':\n return 25 /* JAPANESE_FORMAL */;\n case 'japanese-informal':\n return 26 /* JAPANESE_INFORMAL */;\n case 'kannada':\n return 27 /* KANNADA */;\n case 'katakana':\n return 28 /* KATAKANA */;\n case 'katakana-iroha':\n return 29 /* KATAKANA_IROHA */;\n case 'khmer':\n return 30 /* KHMER */;\n case 'korean-hangul-formal':\n return 31 /* KOREAN_HANGUL_FORMAL */;\n case 'korean-hanja-formal':\n return 32 /* KOREAN_HANJA_FORMAL */;\n case 'korean-hanja-informal':\n return 33 /* KOREAN_HANJA_INFORMAL */;\n case 'lao':\n return 34 /* LAO */;\n case 'lower-armenian':\n return 35 /* LOWER_ARMENIAN */;\n case 'malayalam':\n return 36 /* MALAYALAM */;\n case 'mongolian':\n return 37 /* MONGOLIAN */;\n case 'myanmar':\n return 38 /* MYANMAR */;\n case 'oriya':\n return 39 /* ORIYA */;\n case 'persian':\n return 40 /* PERSIAN */;\n case 'simp-chinese-formal':\n return 41 /* SIMP_CHINESE_FORMAL */;\n case 'simp-chinese-informal':\n return 42 /* SIMP_CHINESE_INFORMAL */;\n case 'tamil':\n return 43 /* TAMIL */;\n case 'telugu':\n return 44 /* TELUGU */;\n case 'thai':\n return 45 /* THAI */;\n case 'tibetan':\n return 46 /* TIBETAN */;\n case 'trad-chinese-formal':\n return 47 /* TRAD_CHINESE_FORMAL */;\n case 'trad-chinese-informal':\n return 48 /* TRAD_CHINESE_INFORMAL */;\n case 'upper-armenian':\n return 49 /* UPPER_ARMENIAN */;\n case 'disclosure-open':\n return 50 /* DISCLOSURE_OPEN */;\n case 'disclosure-closed':\n return 51 /* DISCLOSURE_CLOSED */;\n case 'none':\n default:\n return -1 /* NONE */;\n }\n }\n};\n\nvar marginForSide = function (side) { return ({\n name: \"margin-\" + side,\n initialValue: '0',\n prefix: false,\n type: 4 /* TOKEN_VALUE */\n}); };\nvar marginTop = marginForSide('top');\nvar marginRight = marginForSide('right');\nvar marginBottom = marginForSide('bottom');\nvar marginLeft = marginForSide('left');\n\nvar overflow = {\n name: 'overflow',\n initialValue: 'visible',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return tokens.filter(isIdentToken).map(function (overflow) {\n switch (overflow.value) {\n case 'hidden':\n return 1 /* HIDDEN */;\n case 'scroll':\n return 2 /* SCROLL */;\n case 'clip':\n return 3 /* CLIP */;\n case 'auto':\n return 4 /* AUTO */;\n case 'visible':\n default:\n return 0 /* VISIBLE */;\n }\n });\n }\n};\n\nvar overflowWrap = {\n name: 'overflow-wrap',\n initialValue: 'normal',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, overflow) {\n switch (overflow) {\n case 'break-word':\n return \"break-word\" /* BREAK_WORD */;\n case 'normal':\n default:\n return \"normal\" /* NORMAL */;\n }\n }\n};\n\nvar paddingForSide = function (side) { return ({\n name: \"padding-\" + side,\n initialValue: '0',\n prefix: false,\n type: 3 /* TYPE_VALUE */,\n format: 'length-percentage'\n}); };\nvar paddingTop = paddingForSide('top');\nvar paddingRight = paddingForSide('right');\nvar paddingBottom = paddingForSide('bottom');\nvar paddingLeft = paddingForSide('left');\n\nvar textAlign = {\n name: 'text-align',\n initialValue: 'left',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, textAlign) {\n switch (textAlign) {\n case 'right':\n return 2 /* RIGHT */;\n case 'center':\n case 'justify':\n return 1 /* CENTER */;\n case 'left':\n default:\n return 0 /* LEFT */;\n }\n }\n};\n\nvar position = {\n name: 'position',\n initialValue: 'static',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, position) {\n switch (position) {\n case 'relative':\n return 1 /* RELATIVE */;\n case 'absolute':\n return 2 /* ABSOLUTE */;\n case 'fixed':\n return 3 /* FIXED */;\n case 'sticky':\n return 4 /* STICKY */;\n }\n return 0 /* STATIC */;\n }\n};\n\nvar textShadow = {\n name: 'text-shadow',\n initialValue: 'none',\n type: 1 /* LIST */,\n prefix: false,\n parse: function (context, tokens) {\n if (tokens.length === 1 && isIdentWithValue(tokens[0], 'none')) {\n return [];\n }\n return parseFunctionArgs(tokens).map(function (values) {\n var shadow = {\n color: COLORS.TRANSPARENT,\n offsetX: ZERO_LENGTH,\n offsetY: ZERO_LENGTH,\n blur: ZERO_LENGTH\n };\n var c = 0;\n for (var i = 0; i < values.length; i++) {\n var token = values[i];\n if (isLength(token)) {\n if (c === 0) {\n shadow.offsetX = token;\n }\n else if (c === 1) {\n shadow.offsetY = token;\n }\n else {\n shadow.blur = token;\n }\n c++;\n }\n else {\n shadow.color = color$1.parse(context, token);\n }\n }\n return shadow;\n });\n }\n};\n\nvar textTransform = {\n name: 'text-transform',\n initialValue: 'none',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, textTransform) {\n switch (textTransform) {\n case 'uppercase':\n return 2 /* UPPERCASE */;\n case 'lowercase':\n return 1 /* LOWERCASE */;\n case 'capitalize':\n return 3 /* CAPITALIZE */;\n }\n return 0 /* NONE */;\n }\n};\n\nvar transform$1 = {\n name: 'transform',\n initialValue: 'none',\n prefix: true,\n type: 0 /* VALUE */,\n parse: function (_context, token) {\n if (token.type === 20 /* IDENT_TOKEN */ && token.value === 'none') {\n return null;\n }\n if (token.type === 18 /* FUNCTION */) {\n var transformFunction = SUPPORTED_TRANSFORM_FUNCTIONS[token.name];\n if (typeof transformFunction === 'undefined') {\n throw new Error(\"Attempting to parse an unsupported transform function \\\"\" + token.name + \"\\\"\");\n }\n return transformFunction(token.values);\n }\n return null;\n }\n};\nvar matrix = function (args) {\n var values = args.filter(function (arg) { return arg.type === 17 /* NUMBER_TOKEN */; }).map(function (arg) { return arg.number; });\n return values.length === 6 ? values : null;\n};\n// doesn't support 3D transforms at the moment\nvar matrix3d = function (args) {\n var values = args.filter(function (arg) { return arg.type === 17 /* NUMBER_TOKEN */; }).map(function (arg) { return arg.number; });\n var a1 = values[0], b1 = values[1]; values[2]; values[3]; var a2 = values[4], b2 = values[5]; values[6]; values[7]; values[8]; values[9]; values[10]; values[11]; var a4 = values[12], b4 = values[13]; values[14]; values[15];\n return values.length === 16 ? [a1, b1, a2, b2, a4, b4] : null;\n};\nvar SUPPORTED_TRANSFORM_FUNCTIONS = {\n matrix: matrix,\n matrix3d: matrix3d\n};\n\nvar DEFAULT_VALUE = {\n type: 16 /* PERCENTAGE_TOKEN */,\n number: 50,\n flags: FLAG_INTEGER\n};\nvar DEFAULT = [DEFAULT_VALUE, DEFAULT_VALUE];\nvar transformOrigin = {\n name: 'transform-origin',\n initialValue: '50% 50%',\n prefix: true,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n var origins = tokens.filter(isLengthPercentage);\n if (origins.length !== 2) {\n return DEFAULT;\n }\n return [origins[0], origins[1]];\n }\n};\n\nvar visibility = {\n name: 'visible',\n initialValue: 'none',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, visibility) {\n switch (visibility) {\n case 'hidden':\n return 1 /* HIDDEN */;\n case 'collapse':\n return 2 /* COLLAPSE */;\n case 'visible':\n default:\n return 0 /* VISIBLE */;\n }\n }\n};\n\nvar WORD_BREAK;\n(function (WORD_BREAK) {\n WORD_BREAK[\"NORMAL\"] = \"normal\";\n WORD_BREAK[\"BREAK_ALL\"] = \"break-all\";\n WORD_BREAK[\"KEEP_ALL\"] = \"keep-all\";\n})(WORD_BREAK || (WORD_BREAK = {}));\nvar wordBreak = {\n name: 'word-break',\n initialValue: 'normal',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, wordBreak) {\n switch (wordBreak) {\n case 'break-all':\n return WORD_BREAK.BREAK_ALL;\n case 'keep-all':\n return WORD_BREAK.KEEP_ALL;\n case 'normal':\n default:\n return WORD_BREAK.NORMAL;\n }\n }\n};\n\nvar zIndex = {\n name: 'z-index',\n initialValue: 'auto',\n prefix: false,\n type: 0 /* VALUE */,\n parse: function (_context, token) {\n if (token.type === 20 /* IDENT_TOKEN */) {\n return { auto: true, order: 0 };\n }\n if (isNumberToken(token)) {\n return { auto: false, order: token.number };\n }\n throw new Error(\"Invalid z-index number parsed\");\n }\n};\n\nvar time = {\n name: 'time',\n parse: function (_context, value) {\n if (value.type === 15 /* DIMENSION_TOKEN */) {\n switch (value.unit.toLowerCase()) {\n case 's':\n return 1000 * value.number;\n case 'ms':\n return value.number;\n }\n }\n throw new Error(\"Unsupported time type\");\n }\n};\n\nvar opacity = {\n name: 'opacity',\n initialValue: '1',\n type: 0 /* VALUE */,\n prefix: false,\n parse: function (_context, token) {\n if (isNumberToken(token)) {\n return token.number;\n }\n return 1;\n }\n};\n\nvar textDecorationColor = {\n name: \"text-decoration-color\",\n initialValue: 'transparent',\n prefix: false,\n type: 3 /* TYPE_VALUE */,\n format: 'color'\n};\n\nvar textDecorationLine = {\n name: 'text-decoration-line',\n initialValue: 'none',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n return tokens\n .filter(isIdentToken)\n .map(function (token) {\n switch (token.value) {\n case 'underline':\n return 1 /* UNDERLINE */;\n case 'overline':\n return 2 /* OVERLINE */;\n case 'line-through':\n return 3 /* LINE_THROUGH */;\n case 'none':\n return 4 /* BLINK */;\n }\n return 0 /* NONE */;\n })\n .filter(function (line) { return line !== 0 /* NONE */; });\n }\n};\n\nvar fontFamily = {\n name: \"font-family\",\n initialValue: '',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n var accumulator = [];\n var results = [];\n tokens.forEach(function (token) {\n switch (token.type) {\n case 20 /* IDENT_TOKEN */:\n case 0 /* STRING_TOKEN */:\n accumulator.push(token.value);\n break;\n case 17 /* NUMBER_TOKEN */:\n accumulator.push(token.number.toString());\n break;\n case 4 /* COMMA_TOKEN */:\n results.push(accumulator.join(' '));\n accumulator.length = 0;\n break;\n }\n });\n if (accumulator.length) {\n results.push(accumulator.join(' '));\n }\n return results.map(function (result) { return (result.indexOf(' ') === -1 ? result : \"'\" + result + \"'\"); });\n }\n};\n\nvar fontSize = {\n name: \"font-size\",\n initialValue: '0',\n prefix: false,\n type: 3 /* TYPE_VALUE */,\n format: 'length'\n};\n\nvar fontWeight = {\n name: 'font-weight',\n initialValue: 'normal',\n type: 0 /* VALUE */,\n prefix: false,\n parse: function (_context, token) {\n if (isNumberToken(token)) {\n return token.number;\n }\n if (isIdentToken(token)) {\n switch (token.value) {\n case 'bold':\n return 700;\n case 'normal':\n default:\n return 400;\n }\n }\n return 400;\n }\n};\n\nvar fontVariant = {\n name: 'font-variant',\n initialValue: 'none',\n type: 1 /* LIST */,\n prefix: false,\n parse: function (_context, tokens) {\n return tokens.filter(isIdentToken).map(function (token) { return token.value; });\n }\n};\n\nvar fontStyle = {\n name: 'font-style',\n initialValue: 'normal',\n prefix: false,\n type: 2 /* IDENT_VALUE */,\n parse: function (_context, overflow) {\n switch (overflow) {\n case 'oblique':\n return \"oblique\" /* OBLIQUE */;\n case 'italic':\n return \"italic\" /* ITALIC */;\n case 'normal':\n default:\n return \"normal\" /* NORMAL */;\n }\n }\n};\n\nvar contains = function (bit, value) { return (bit & value) !== 0; };\n\nvar content = {\n name: 'content',\n initialValue: 'none',\n type: 1 /* LIST */,\n prefix: false,\n parse: function (_context, tokens) {\n if (tokens.length === 0) {\n return [];\n }\n var first = tokens[0];\n if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') {\n return [];\n }\n return tokens;\n }\n};\n\nvar counterIncrement = {\n name: 'counter-increment',\n initialValue: 'none',\n prefix: true,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n if (tokens.length === 0) {\n return null;\n }\n var first = tokens[0];\n if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') {\n return null;\n }\n var increments = [];\n var filtered = tokens.filter(nonWhiteSpace);\n for (var i = 0; i < filtered.length; i++) {\n var counter = filtered[i];\n var next = filtered[i + 1];\n if (counter.type === 20 /* IDENT_TOKEN */) {\n var increment = next && isNumberToken(next) ? next.number : 1;\n increments.push({ counter: counter.value, increment: increment });\n }\n }\n return increments;\n }\n};\n\nvar counterReset = {\n name: 'counter-reset',\n initialValue: 'none',\n prefix: true,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n if (tokens.length === 0) {\n return [];\n }\n var resets = [];\n var filtered = tokens.filter(nonWhiteSpace);\n for (var i = 0; i < filtered.length; i++) {\n var counter = filtered[i];\n var next = filtered[i + 1];\n if (isIdentToken(counter) && counter.value !== 'none') {\n var reset = next && isNumberToken(next) ? next.number : 0;\n resets.push({ counter: counter.value, reset: reset });\n }\n }\n return resets;\n }\n};\n\nvar duration = {\n name: 'duration',\n initialValue: '0s',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (context, tokens) {\n return tokens.filter(isDimensionToken).map(function (token) { return time.parse(context, token); });\n }\n};\n\nvar quotes = {\n name: 'quotes',\n initialValue: 'none',\n prefix: true,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n if (tokens.length === 0) {\n return null;\n }\n var first = tokens[0];\n if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') {\n return null;\n }\n var quotes = [];\n var filtered = tokens.filter(isStringToken);\n if (filtered.length % 2 !== 0) {\n return null;\n }\n for (var i = 0; i < filtered.length; i += 2) {\n var open_1 = filtered[i].value;\n var close_1 = filtered[i + 1].value;\n quotes.push({ open: open_1, close: close_1 });\n }\n return quotes;\n }\n};\nvar getQuote = function (quotes, depth, open) {\n if (!quotes) {\n return '';\n }\n var quote = quotes[Math.min(depth, quotes.length - 1)];\n if (!quote) {\n return '';\n }\n return open ? quote.open : quote.close;\n};\n\nvar boxShadow = {\n name: 'box-shadow',\n initialValue: 'none',\n type: 1 /* LIST */,\n prefix: false,\n parse: function (context, tokens) {\n if (tokens.length === 1 && isIdentWithValue(tokens[0], 'none')) {\n return [];\n }\n return parseFunctionArgs(tokens).map(function (values) {\n var shadow = {\n color: 0x000000ff,\n offsetX: ZERO_LENGTH,\n offsetY: ZERO_LENGTH,\n blur: ZERO_LENGTH,\n spread: ZERO_LENGTH,\n inset: false\n };\n var c = 0;\n for (var i = 0; i < values.length; i++) {\n var token = values[i];\n if (isIdentWithValue(token, 'inset')) {\n shadow.inset = true;\n }\n else if (isLength(token)) {\n if (c === 0) {\n shadow.offsetX = token;\n }\n else if (c === 1) {\n shadow.offsetY = token;\n }\n else if (c === 2) {\n shadow.blur = token;\n }\n else {\n shadow.spread = token;\n }\n c++;\n }\n else {\n shadow.color = color$1.parse(context, token);\n }\n }\n return shadow;\n });\n }\n};\n\nvar paintOrder = {\n name: 'paint-order',\n initialValue: 'normal',\n prefix: false,\n type: 1 /* LIST */,\n parse: function (_context, tokens) {\n var DEFAULT_VALUE = [0 /* FILL */, 1 /* STROKE */, 2 /* MARKERS */];\n var layers = [];\n tokens.filter(isIdentToken).forEach(function (token) {\n switch (token.value) {\n case 'stroke':\n layers.push(1 /* STROKE */);\n break;\n case 'fill':\n layers.push(0 /* FILL */);\n break;\n case 'markers':\n layers.push(2 /* MARKERS */);\n break;\n }\n });\n DEFAULT_VALUE.forEach(function (value) {\n if (layers.indexOf(value) === -1) {\n layers.push(value);\n }\n });\n return layers;\n }\n};\n\nvar webkitTextStrokeColor = {\n name: \"-webkit-text-stroke-color\",\n initialValue: 'currentcolor',\n prefix: false,\n type: 3 /* TYPE_VALUE */,\n format: 'color'\n};\n\nvar webkitTextStrokeWidth = {\n name: \"-webkit-text-stroke-width\",\n initialValue: '0',\n type: 0 /* VALUE */,\n prefix: false,\n parse: function (_context, token) {\n if (isDimensionToken(token)) {\n return token.number;\n }\n return 0;\n }\n};\n\nvar CSSParsedDeclaration = /** @class */ (function () {\n function CSSParsedDeclaration(context, declaration) {\n var _a, _b;\n this.animationDuration = parse$4(context, duration, declaration.animationDuration);\n this.backgroundClip = parse$4(context, backgroundClip, declaration.backgroundClip);\n this.backgroundColor = parse$4(context, backgroundColor, declaration.backgroundColor);\n this.backgroundImage = parse$4(context, backgroundImage, declaration.backgroundImage);\n this.backgroundOrigin = parse$4(context, backgroundOrigin, declaration.backgroundOrigin);\n this.backgroundPosition = parse$4(context, backgroundPosition, declaration.backgroundPosition);\n this.backgroundRepeat = parse$4(context, backgroundRepeat, declaration.backgroundRepeat);\n this.backgroundSize = parse$4(context, backgroundSize, declaration.backgroundSize);\n this.borderTopColor = parse$4(context, borderTopColor, declaration.borderTopColor);\n this.borderRightColor = parse$4(context, borderRightColor, declaration.borderRightColor);\n this.borderBottomColor = parse$4(context, borderBottomColor, declaration.borderBottomColor);\n this.borderLeftColor = parse$4(context, borderLeftColor, declaration.borderLeftColor);\n this.borderTopLeftRadius = parse$4(context, borderTopLeftRadius, declaration.borderTopLeftRadius);\n this.borderTopRightRadius = parse$4(context, borderTopRightRadius, declaration.borderTopRightRadius);\n this.borderBottomRightRadius = parse$4(context, borderBottomRightRadius, declaration.borderBottomRightRadius);\n this.borderBottomLeftRadius = parse$4(context, borderBottomLeftRadius, declaration.borderBottomLeftRadius);\n this.borderTopStyle = parse$4(context, borderTopStyle, declaration.borderTopStyle);\n this.borderRightStyle = parse$4(context, borderRightStyle, declaration.borderRightStyle);\n this.borderBottomStyle = parse$4(context, borderBottomStyle, declaration.borderBottomStyle);\n this.borderLeftStyle = parse$4(context, borderLeftStyle, declaration.borderLeftStyle);\n this.borderTopWidth = parse$4(context, borderTopWidth, declaration.borderTopWidth);\n this.borderRightWidth = parse$4(context, borderRightWidth, declaration.borderRightWidth);\n this.borderBottomWidth = parse$4(context, borderBottomWidth, declaration.borderBottomWidth);\n this.borderLeftWidth = parse$4(context, borderLeftWidth, declaration.borderLeftWidth);\n this.boxShadow = parse$4(context, boxShadow, declaration.boxShadow);\n this.color = parse$4(context, color, declaration.color);\n this.direction = parse$4(context, direction, declaration.direction);\n this.display = parse$4(context, display, declaration.display);\n this.float = parse$4(context, float, declaration.cssFloat);\n this.fontFamily = parse$4(context, fontFamily, declaration.fontFamily);\n this.fontSize = parse$4(context, fontSize, declaration.fontSize);\n this.fontStyle = parse$4(context, fontStyle, declaration.fontStyle);\n this.fontVariant = parse$4(context, fontVariant, declaration.fontVariant);\n this.fontWeight = parse$4(context, fontWeight, declaration.fontWeight);\n this.letterSpacing = parse$4(context, letterSpacing, declaration.letterSpacing);\n this.lineBreak = parse$4(context, lineBreak, declaration.lineBreak);\n this.lineHeight = parse$4(context, lineHeight, declaration.lineHeight);\n this.listStyleImage = parse$4(context, listStyleImage, declaration.listStyleImage);\n this.listStylePosition = parse$4(context, listStylePosition, declaration.listStylePosition);\n this.listStyleType = parse$4(context, listStyleType, declaration.listStyleType);\n this.marginTop = parse$4(context, marginTop, declaration.marginTop);\n this.marginRight = parse$4(context, marginRight, declaration.marginRight);\n this.marginBottom = parse$4(context, marginBottom, declaration.marginBottom);\n this.marginLeft = parse$4(context, marginLeft, declaration.marginLeft);\n this.opacity = parse$4(context, opacity, declaration.opacity);\n var overflowTuple = parse$4(context, overflow, declaration.overflow);\n this.overflowX = overflowTuple[0];\n this.overflowY = overflowTuple[overflowTuple.length > 1 ? 1 : 0];\n this.overflowWrap = parse$4(context, overflowWrap, declaration.overflowWrap);\n this.paddingTop = parse$4(context, paddingTop, declaration.paddingTop);\n this.paddingRight = parse$4(context, paddingRight, declaration.paddingRight);\n this.paddingBottom = parse$4(context, paddingBottom, declaration.paddingBottom);\n this.paddingLeft = parse$4(context, paddingLeft, declaration.paddingLeft);\n this.paintOrder = parse$4(context, paintOrder, declaration.paintOrder);\n this.position = parse$4(context, position, declaration.position);\n this.textAlign = parse$4(context, textAlign, declaration.textAlign);\n this.textDecorationColor = parse$4(context, textDecorationColor, (_a = declaration.textDecorationColor) !== null && _a !== void 0 ? _a : declaration.color);\n this.textDecorationLine = parse$4(context, textDecorationLine, (_b = declaration.textDecorationLine) !== null && _b !== void 0 ? _b : declaration.textDecoration);\n this.textShadow = parse$4(context, textShadow, declaration.textShadow);\n this.textTransform = parse$4(context, textTransform, declaration.textTransform);\n this.transform = parse$4(context, transform$1, declaration.transform);\n this.transformOrigin = parse$4(context, transformOrigin, declaration.transformOrigin);\n this.visibility = parse$4(context, visibility, declaration.visibility);\n this.webkitTextStrokeColor = parse$4(context, webkitTextStrokeColor, declaration.webkitTextStrokeColor);\n this.webkitTextStrokeWidth = parse$4(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);\n this.wordBreak = parse$4(context, wordBreak, declaration.wordBreak);\n this.zIndex = parse$4(context, zIndex, declaration.zIndex);\n }\n CSSParsedDeclaration.prototype.isVisible = function () {\n return this.display > 0 && this.opacity > 0 && this.visibility === 0 /* VISIBLE */;\n };\n CSSParsedDeclaration.prototype.isTransparent = function () {\n return isTransparent(this.backgroundColor);\n };\n CSSParsedDeclaration.prototype.isTransformed = function () {\n return this.transform !== null;\n };\n CSSParsedDeclaration.prototype.isPositioned = function () {\n return this.position !== 0 /* STATIC */;\n };\n CSSParsedDeclaration.prototype.isPositionedWithZIndex = function () {\n return this.isPositioned() && !this.zIndex.auto;\n };\n CSSParsedDeclaration.prototype.isFloating = function () {\n return this.float !== 0 /* NONE */;\n };\n CSSParsedDeclaration.prototype.isInlineLevel = function () {\n return (contains(this.display, 4 /* INLINE */) ||\n contains(this.display, 33554432 /* INLINE_BLOCK */) ||\n contains(this.display, 268435456 /* INLINE_FLEX */) ||\n contains(this.display, 536870912 /* INLINE_GRID */) ||\n contains(this.display, 67108864 /* INLINE_LIST_ITEM */) ||\n contains(this.display, 134217728 /* INLINE_TABLE */));\n };\n return CSSParsedDeclaration;\n}());\nvar CSSParsedPseudoDeclaration = /** @class */ (function () {\n function CSSParsedPseudoDeclaration(context, declaration) {\n this.content = parse$4(context, content, declaration.content);\n this.quotes = parse$4(context, quotes, declaration.quotes);\n }\n return CSSParsedPseudoDeclaration;\n}());\nvar CSSParsedCounterDeclaration = /** @class */ (function () {\n function CSSParsedCounterDeclaration(context, declaration) {\n this.counterIncrement = parse$4(context, counterIncrement, declaration.counterIncrement);\n this.counterReset = parse$4(context, counterReset, declaration.counterReset);\n }\n return CSSParsedCounterDeclaration;\n}());\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nvar parse$4 = function (context, descriptor, style) {\n var tokenizer = new Tokenizer();\n var value = style !== null && typeof style !== 'undefined' ? style.toString() : descriptor.initialValue;\n tokenizer.write(value);\n var parser = new Parser(tokenizer.read());\n switch (descriptor.type) {\n case 2 /* IDENT_VALUE */:\n var token = parser.parseComponentValue();\n return descriptor.parse(context, isIdentToken(token) ? token.value : descriptor.initialValue);\n case 0 /* VALUE */:\n return descriptor.parse(context, parser.parseComponentValue());\n case 1 /* LIST */:\n return descriptor.parse(context, parser.parseComponentValues());\n case 4 /* TOKEN_VALUE */:\n return parser.parseComponentValue();\n case 3 /* TYPE_VALUE */:\n switch (descriptor.format) {\n case 'angle':\n return angle.parse(context, parser.parseComponentValue());\n case 'color':\n return color$1.parse(context, parser.parseComponentValue());\n case 'image':\n return image$1.parse(context, parser.parseComponentValue());\n case 'length':\n var length_1 = parser.parseComponentValue();\n return isLength(length_1) ? length_1 : ZERO_LENGTH;\n case 'length-percentage':\n var value_1 = parser.parseComponentValue();\n return isLengthPercentage(value_1) ? value_1 : ZERO_LENGTH;\n case 'time':\n return time.parse(context, parser.parseComponentValue());\n }\n break;\n }\n};\n\nvar elementDebuggerAttribute = 'data-html2canvas-debug';\nvar getElementDebugType = function (element) {\n var attribute = element.getAttribute(elementDebuggerAttribute);\n switch (attribute) {\n case 'all':\n return 1 /* ALL */;\n case 'clone':\n return 2 /* CLONE */;\n case 'parse':\n return 3 /* PARSE */;\n case 'render':\n return 4 /* RENDER */;\n default:\n return 0 /* NONE */;\n }\n};\nvar isDebugging = function (element, type) {\n var elementType = getElementDebugType(element);\n return elementType === 1 /* ALL */ || type === elementType;\n};\n\nvar ElementContainer = /** @class */ (function () {\n function ElementContainer(context, element) {\n this.context = context;\n this.textNodes = [];\n this.elements = [];\n this.flags = 0;\n if (isDebugging(element, 3 /* PARSE */)) {\n debugger;\n }\n this.styles = new CSSParsedDeclaration(context, window.getComputedStyle(element, null));\n if (isHTMLElementNode(element)) {\n if (this.styles.animationDuration.some(function (duration) { return duration > 0; })) {\n element.style.animationDuration = '0s';\n }\n if (this.styles.transform !== null) {\n // getBoundingClientRect takes transforms into account\n element.style.transform = 'none';\n }\n }\n this.bounds = parseBounds(this.context, element);\n if (isDebugging(element, 4 /* RENDER */)) {\n this.flags |= 16 /* DEBUG_RENDER */;\n }\n }\n return ElementContainer;\n}());\n\n/*\n * text-segmentation 1.0.3 \n * Copyright (c) 2022 Niklas von Hertzen \n * Released under MIT License\n */\nvar base64 = 'AAAAAAAAAAAAEA4AGBkAAFAaAAACAAAAAAAIABAAGAAwADgACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAAQABIAEQATAAIABAACAAQAAgAEAAIABAAVABcAAgAEAAIABAACAAQAGAAaABwAHgAgACIAI4AlgAIABAAmwCjAKgAsAC2AL4AvQDFAMoA0gBPAVYBWgEIAAgACACMANoAYgFkAWwBdAF8AX0BhQGNAZUBlgGeAaMBlQGWAasBswF8AbsBwwF0AcsBYwHTAQgA2wG/AOMBdAF8AekB8QF0AfkB+wHiAHQBfAEIAAMC5gQIAAsCEgIIAAgAFgIeAggAIgIpAggAMQI5AkACygEIAAgASAJQAlgCYAIIAAgACAAKBQoFCgUTBRMFGQUrBSsFCAAIAAgACAAIAAgACAAIAAgACABdAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABoAmgCrwGvAQgAbgJ2AggAHgEIAAgACADnAXsCCAAIAAgAgwIIAAgACAAIAAgACACKAggAkQKZAggAPADJAAgAoQKkAqwCsgK6AsICCADJAggA0AIIAAgACAAIANYC3gIIAAgACAAIAAgACABAAOYCCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAkASoB+QIEAAgACAA8AEMCCABCBQgACABJBVAFCAAIAAgACAAIAAgACAAIAAgACABTBVoFCAAIAFoFCABfBWUFCAAIAAgACAAIAAgAbQUIAAgACAAIAAgACABzBXsFfQWFBYoFigWKBZEFigWKBYoFmAWfBaYFrgWxBbkFCAAIAAgACAAIAAgACAAIAAgACAAIAMEFCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAMgFCADQBQgACAAIAAgACAAIAAgACAAIAAgACAAIAO4CCAAIAAgAiQAIAAgACABAAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAD0AggACAD8AggACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIANYFCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAMDvwAIAAgAJAIIAAgACAAIAAgACAAIAAgACwMTAwgACAB9BOsEGwMjAwgAKwMyAwsFYgE3A/MEPwMIAEUDTQNRAwgAWQOsAGEDCAAIAAgACAAIAAgACABpAzQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFIQUoBSwFCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABtAwgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABMAEwACAAIAAgACAAIABgACAAIAAgACAC/AAgACAAyAQgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACACAAIAAwAAgACAAIAAgACAAIAAgACAAIAAAARABIAAgACAAIABQASAAIAAgAIABwAEAAjgCIABsAqAC2AL0AigDQAtwC+IJIQqVAZUBWQqVAZUBlQGVAZUBlQGrC5UBlQGVAZUBlQGVAZUBlQGVAXsKlQGVAbAK6wsrDGUMpQzlDJUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAfAKAAuZA64AtwCJALoC6ADwAAgAuACgA/oEpgO6AqsD+AAIAAgAswMIAAgACAAIAIkAuwP5AfsBwwPLAwgACAAIAAgACADRA9kDCAAIAOED6QMIAAgACAAIAAgACADuA/YDCAAIAP4DyQAIAAgABgQIAAgAXQAOBAgACAAIAAgACAAIABMECAAIAAgACAAIAAgACAD8AAQBCAAIAAgAGgQiBCoECAExBAgAEAEIAAgACAAIAAgACAAIAAgACAAIAAgACAA4BAgACABABEYECAAIAAgATAQYAQgAVAQIAAgACAAIAAgACAAIAAgACAAIAFoECAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAOQEIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAB+BAcACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAEABhgSMBAgACAAIAAgAlAQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAwAEAAQABAADAAMAAwADAAQABAAEAAQABAAEAAQABHATAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAdQMIAAgACAAIAAgACAAIAMkACAAIAAgAfQMIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACACFA4kDCAAIAAgACAAIAOcBCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAIcDCAAIAAgACAAIAAgACAAIAAgACAAIAJEDCAAIAAgACADFAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABgBAgAZgQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAbAQCBXIECAAIAHkECAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABAAJwEQACjBKoEsgQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAC6BMIECAAIAAgACAAIAAgACABmBAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAxwQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAGYECAAIAAgAzgQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAigWKBYoFigWKBYoFigWKBd0FXwUIAOIF6gXxBYoF3gT5BQAGCAaKBYoFigWKBYoFigWKBYoFigWKBYoFigXWBIoFigWKBYoFigWKBYoFigWKBYsFEAaKBYoFigWKBYoFigWKBRQGCACKBYoFigWKBQgACAAIANEECAAIABgGigUgBggAJgYIAC4GMwaKBYoF0wQ3Bj4GigWKBYoFigWKBYoFigWKBYoFigWKBYoFigUIAAgACAAIAAgACAAIAAgAigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWLBf///////wQABAAEAAQABAAEAAQABAAEAAQAAwAEAAQAAgAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAQADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAUAAAAFAAUAAAAFAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAQABAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUAAQAAAAUABQAFAAUABQAFAAAAAAAFAAUAAAAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAFAAUAAQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABwAFAAUABQAFAAAABwAHAAcAAAAHAAcABwAFAAEAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAcABwAFAAUAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAQABAAAAAAAAAAAAAAAFAAUABQAFAAAABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAcABwAHAAcAAAAHAAcAAAAAAAUABQAHAAUAAQAHAAEABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABwABAAUABQAFAAUAAAAAAAAAAAAAAAEAAQABAAEAAQABAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABwAFAAUAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUAAQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABQANAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAAEAAQABAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAABQAHAAUABQAFAAAAAAAAAAcABQAFAAUABQAFAAQABAAEAAQABAAEAAQABAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUAAAAFAAUABQAFAAUAAAAFAAUABQAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAAAAAAAAAAAAUABQAFAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAUAAAAHAAcABwAFAAUABQAFAAUABQAFAAUABwAHAAcABwAFAAcABwAAAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUABwAHAAUABQAFAAUAAAAAAAcABwAAAAAABwAHAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAFAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABwAHAAcABQAFAAAAAAAAAAAABQAFAAAAAAAFAAUABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAFAAUABQAFAAUAAAAFAAUABwAAAAcABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAUABwAFAAUABQAFAAAAAAAHAAcAAAAAAAcABwAFAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAcABwAAAAAAAAAHAAcABwAAAAcABwAHAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAABQAHAAcABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAHAAcABwAAAAUABQAFAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAcABQAHAAcABQAHAAcAAAAFAAcABwAAAAcABwAFAAUAAAAAAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAUABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAFAAcABwAFAAUABQAAAAUAAAAHAAcABwAHAAcABwAHAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAHAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABwAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUAAAAFAAAAAAAAAAAABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABwAFAAUABQAFAAUAAAAFAAUAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABwAFAAUABQAFAAUABQAAAAUABQAHAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABQAFAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAcABQAFAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAHAAUABQAFAAUABQAFAAUABwAHAAcABwAHAAcABwAHAAUABwAHAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABwAHAAcABwAFAAUABwAHAAcAAAAAAAAAAAAHAAcABQAHAAcABwAHAAcABwAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAcABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAHAAUABQAFAAUABQAFAAUAAAAFAAAABQAAAAAABQAFAAUABQAFAAUABQAFAAcABwAHAAcABwAHAAUABQAFAAUABQAFAAUABQAFAAUAAAAAAAUABQAFAAUABQAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABwAFAAcABwAHAAcABwAFAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAUABQAFAAUABwAHAAUABQAHAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAcABQAFAAcABwAHAAUABwAFAAUABQAHAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABwAHAAcABwAHAAUABQAFAAUABQAFAAUABQAHAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAcABQAFAAUABQAFAAUABQAAAAAAAAAAAAUAAAAAAAAAAAAAAAAABQAAAAAABwAFAAUAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUAAAAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAUABQAHAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAHAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAHAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAcABwAFAAUABQAFAAcABwAFAAUABwAHAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAcABwAFAAUABwAHAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAFAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAUABQAAAAAABQAFAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAFAAcABwAAAAAAAAAAAAAABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAFAAcABwAFAAcABwAAAAcABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAFAAUABQAAAAUABQAAAAAAAAAAAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABwAFAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABQAFAAUABQAFAAUABQAFAAUABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAHAAcABQAHAAUABQAAAAAAAAAAAAAAAAAFAAAABwAHAAcABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAHAAcABwAAAAAABwAHAAAAAAAHAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAAAAAAFAAUABQAFAAUABQAFAAAAAAAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAUABQAFAAUABwAHAAUABQAFAAcABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAcABQAFAAUABQAFAAUABwAFAAcABwAFAAcABQAFAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAcABQAFAAUABQAAAAAABwAHAAcABwAFAAUABwAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAHAAUABQAFAAUABQAFAAUABQAHAAcABQAHAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAFAAcABwAFAAUABQAFAAUABQAHAAUAAAAAAAAAAAAAAAAAAAAAAAcABwAFAAUABQAFAAcABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAUABQAFAAUABQAHAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAAAAAAFAAUABwAHAAcABwAFAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABwAHAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAFAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAcABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAAAHAAUABQAFAAUABQAFAAUABwAFAAUABwAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUAAAAAAAAABQAAAAUABQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAHAAcAAAAFAAUAAAAHAAcABQAHAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAAAAAAAAAAAAAAAAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAUABQAFAAAAAAAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAABQAFAAUABQAFAAUABQAAAAUABQAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAFAAUABQAFAAUADgAOAA4ADgAOAA4ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAAAAAAAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAMAAwADAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAAAAAAAAAAAAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAAAAAAAAAAAAsADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwACwAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAADgAOAA4AAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAAAA4ADgAOAA4ADgAOAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAAAA4AAAAOAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAADgAAAAAAAAAAAA4AAAAOAAAAAAAAAAAADgAOAA4AAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAA4ADgAOAA4ADgAOAA4ADgAOAAAADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4AAAAAAAAAAAAAAAAAAAAAAA4ADgAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAOAA4ADgAOAA4ADgAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAAAAAAAAA=';\n\n/*\n * utrie 1.0.2 \n * Copyright (c) 2022 Niklas von Hertzen \n * Released under MIT License\n */\nvar chars$1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n// Use a lookup table to find the index.\nvar lookup$1 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);\nfor (var i$1 = 0; i$1 < chars$1.length; i$1++) {\n lookup$1[chars$1.charCodeAt(i$1)] = i$1;\n}\nvar decode$6 = function (base64) {\n var bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;\n if (base64[base64.length - 1] === '=') {\n bufferLength--;\n if (base64[base64.length - 2] === '=') {\n bufferLength--;\n }\n }\n var buffer = typeof ArrayBuffer !== 'undefined' &&\n typeof Uint8Array !== 'undefined' &&\n typeof Uint8Array.prototype.slice !== 'undefined'\n ? new ArrayBuffer(bufferLength)\n : new Array(bufferLength);\n var bytes = Array.isArray(buffer) ? buffer : new Uint8Array(buffer);\n for (i = 0; i < len; i += 4) {\n encoded1 = lookup$1[base64.charCodeAt(i)];\n encoded2 = lookup$1[base64.charCodeAt(i + 1)];\n encoded3 = lookup$1[base64.charCodeAt(i + 2)];\n encoded4 = lookup$1[base64.charCodeAt(i + 3)];\n bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);\n bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);\n bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);\n }\n return buffer;\n};\nvar polyUint16Array = function (buffer) {\n var length = buffer.length;\n var bytes = [];\n for (var i = 0; i < length; i += 2) {\n bytes.push((buffer[i + 1] << 8) | buffer[i]);\n }\n return bytes;\n};\nvar polyUint32Array = function (buffer) {\n var length = buffer.length;\n var bytes = [];\n for (var i = 0; i < length; i += 4) {\n bytes.push((buffer[i + 3] << 24) | (buffer[i + 2] << 16) | (buffer[i + 1] << 8) | buffer[i]);\n }\n return bytes;\n};\n\n/** Shift size for getting the index-2 table offset. */\nvar UTRIE2_SHIFT_2 = 5;\n/** Shift size for getting the index-1 table offset. */\nvar UTRIE2_SHIFT_1 = 6 + 5;\n/**\n * Shift size for shifting left the index array values.\n * Increases possible data size with 16-bit index values at the cost\n * of compactability.\n * This requires data blocks to be aligned by UTRIE2_DATA_GRANULARITY.\n */\nvar UTRIE2_INDEX_SHIFT = 2;\n/**\n * Difference between the two shift sizes,\n * for getting an index-1 offset from an index-2 offset. 6=11-5\n */\nvar UTRIE2_SHIFT_1_2 = UTRIE2_SHIFT_1 - UTRIE2_SHIFT_2;\n/**\n * The part of the index-2 table for U+D800..U+DBFF stores values for\n * lead surrogate code _units_ not code _points_.\n * Values for lead surrogate code _points_ are indexed with this portion of the table.\n * Length=32=0x20=0x400>>UTRIE2_SHIFT_2. (There are 1024=0x400 lead surrogates.)\n */\nvar UTRIE2_LSCP_INDEX_2_OFFSET = 0x10000 >> UTRIE2_SHIFT_2;\n/** Number of entries in a data block. 32=0x20 */\nvar UTRIE2_DATA_BLOCK_LENGTH = 1 << UTRIE2_SHIFT_2;\n/** Mask for getting the lower bits for the in-data-block offset. */\nvar UTRIE2_DATA_MASK = UTRIE2_DATA_BLOCK_LENGTH - 1;\nvar UTRIE2_LSCP_INDEX_2_LENGTH = 0x400 >> UTRIE2_SHIFT_2;\n/** Count the lengths of both BMP pieces. 2080=0x820 */\nvar UTRIE2_INDEX_2_BMP_LENGTH = UTRIE2_LSCP_INDEX_2_OFFSET + UTRIE2_LSCP_INDEX_2_LENGTH;\n/**\n * The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820.\n * Length 32=0x20 for lead bytes C0..DF, regardless of UTRIE2_SHIFT_2.\n */\nvar UTRIE2_UTF8_2B_INDEX_2_OFFSET = UTRIE2_INDEX_2_BMP_LENGTH;\nvar UTRIE2_UTF8_2B_INDEX_2_LENGTH = 0x800 >> 6; /* U+0800 is the first code point after 2-byte UTF-8 */\n/**\n * The index-1 table, only used for supplementary code points, at offset 2112=0x840.\n * Variable length, for code points up to highStart, where the last single-value range starts.\n * Maximum length 512=0x200=0x100000>>UTRIE2_SHIFT_1.\n * (For 0x100000 supplementary code points U+10000..U+10ffff.)\n *\n * The part of the index-2 table for supplementary code points starts\n * after this index-1 table.\n *\n * Both the index-1 table and the following part of the index-2 table\n * are omitted completely if there is only BMP data.\n */\nvar UTRIE2_INDEX_1_OFFSET = UTRIE2_UTF8_2B_INDEX_2_OFFSET + UTRIE2_UTF8_2B_INDEX_2_LENGTH;\n/**\n * Number of index-1 entries for the BMP. 32=0x20\n * This part of the index-1 table is omitted from the serialized form.\n */\nvar UTRIE2_OMITTED_BMP_INDEX_1_LENGTH = 0x10000 >> UTRIE2_SHIFT_1;\n/** Number of entries in an index-2 block. 64=0x40 */\nvar UTRIE2_INDEX_2_BLOCK_LENGTH = 1 << UTRIE2_SHIFT_1_2;\n/** Mask for getting the lower bits for the in-index-2-block offset. */\nvar UTRIE2_INDEX_2_MASK = UTRIE2_INDEX_2_BLOCK_LENGTH - 1;\nvar slice16 = function (view, start, end) {\n if (view.slice) {\n return view.slice(start, end);\n }\n return new Uint16Array(Array.prototype.slice.call(view, start, end));\n};\nvar slice32 = function (view, start, end) {\n if (view.slice) {\n return view.slice(start, end);\n }\n return new Uint32Array(Array.prototype.slice.call(view, start, end));\n};\nvar createTrieFromBase64 = function (base64, _byteLength) {\n var buffer = decode$6(base64);\n var view32 = Array.isArray(buffer) ? polyUint32Array(buffer) : new Uint32Array(buffer);\n var view16 = Array.isArray(buffer) ? polyUint16Array(buffer) : new Uint16Array(buffer);\n var headerLength = 24;\n var index = slice16(view16, headerLength / 2, view32[4] / 2);\n var data = view32[5] === 2\n ? slice16(view16, (headerLength + view32[4]) / 2)\n : slice32(view32, Math.ceil((headerLength + view32[4]) / 4));\n return new Trie(view32[0], view32[1], view32[2], view32[3], index, data);\n};\nvar Trie = /** @class */ (function () {\n function Trie(initialValue, errorValue, highStart, highValueIndex, index, data) {\n this.initialValue = initialValue;\n this.errorValue = errorValue;\n this.highStart = highStart;\n this.highValueIndex = highValueIndex;\n this.index = index;\n this.data = data;\n }\n /**\n * Get the value for a code point as stored in the Trie.\n *\n * @param codePoint the code point\n * @return the value\n */\n Trie.prototype.get = function (codePoint) {\n var ix;\n if (codePoint >= 0) {\n if (codePoint < 0x0d800 || (codePoint > 0x0dbff && codePoint <= 0x0ffff)) {\n // Ordinary BMP code point, excluding leading surrogates.\n // BMP uses a single level lookup. BMP index starts at offset 0 in the Trie2 index.\n // 16 bit data is stored in the index array itself.\n ix = this.index[codePoint >> UTRIE2_SHIFT_2];\n ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK);\n return this.data[ix];\n }\n if (codePoint <= 0xffff) {\n // Lead Surrogate Code Point. A Separate index section is stored for\n // lead surrogate code units and code points.\n // The main index has the code unit data.\n // For this function, we need the code point data.\n // Note: this expression could be refactored for slightly improved efficiency, but\n // surrogate code points will be so rare in practice that it's not worth it.\n ix = this.index[UTRIE2_LSCP_INDEX_2_OFFSET + ((codePoint - 0xd800) >> UTRIE2_SHIFT_2)];\n ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK);\n return this.data[ix];\n }\n if (codePoint < this.highStart) {\n // Supplemental code point, use two-level lookup.\n ix = UTRIE2_INDEX_1_OFFSET - UTRIE2_OMITTED_BMP_INDEX_1_LENGTH + (codePoint >> UTRIE2_SHIFT_1);\n ix = this.index[ix];\n ix += (codePoint >> UTRIE2_SHIFT_2) & UTRIE2_INDEX_2_MASK;\n ix = this.index[ix];\n ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK);\n return this.data[ix];\n }\n if (codePoint <= 0x10ffff) {\n return this.data[this.highValueIndex];\n }\n }\n // Fall through. The code point is outside of the legal range of 0..0x10ffff.\n return this.errorValue;\n };\n return Trie;\n}());\n\n/*\n * base64-arraybuffer 1.0.2 \n * Copyright (c) 2022 Niklas von Hertzen \n * Released under MIT License\n */\nvar chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n// Use a lookup table to find the index.\nvar lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);\nfor (var i$4 = 0; i$4 < chars.length; i$4++) {\n lookup[chars.charCodeAt(i$4)] = i$4;\n}\n\nvar Prepend = 1;\nvar CR = 2;\nvar LF = 3;\nvar Control$1 = 4;\nvar Extend = 5;\nvar SpacingMark = 7;\nvar L = 8;\nvar V = 9;\nvar T = 10;\nvar LV = 11;\nvar LVT = 12;\nvar ZWJ = 13;\nvar Extended_Pictographic = 14;\nvar RI = 15;\nvar toCodePoints = function (str) {\n var codePoints = [];\n var i = 0;\n var length = str.length;\n while (i < length) {\n var value = str.charCodeAt(i++);\n if (value >= 0xd800 && value <= 0xdbff && i < length) {\n var extra = str.charCodeAt(i++);\n if ((extra & 0xfc00) === 0xdc00) {\n codePoints.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);\n }\n else {\n codePoints.push(value);\n i--;\n }\n }\n else {\n codePoints.push(value);\n }\n }\n return codePoints;\n};\nvar fromCodePoint = function () {\n var codePoints = [];\n for (var _i = 0; _i < arguments.length; _i++) {\n codePoints[_i] = arguments[_i];\n }\n if (String.fromCodePoint) {\n return String.fromCodePoint.apply(String, codePoints);\n }\n var length = codePoints.length;\n if (!length) {\n return '';\n }\n var codeUnits = [];\n var index = -1;\n var result = '';\n while (++index < length) {\n var codePoint = codePoints[index];\n if (codePoint <= 0xffff) {\n codeUnits.push(codePoint);\n }\n else {\n codePoint -= 0x10000;\n codeUnits.push((codePoint >> 10) + 0xd800, (codePoint % 0x400) + 0xdc00);\n }\n if (index + 1 === length || codeUnits.length > 0x4000) {\n result += String.fromCharCode.apply(String, codeUnits);\n codeUnits.length = 0;\n }\n }\n return result;\n};\nvar UnicodeTrie = createTrieFromBase64(base64);\nvar BREAK_NOT_ALLOWED = '×';\nvar BREAK_ALLOWED = '÷';\nvar codePointToClass = function (codePoint) { return UnicodeTrie.get(codePoint); };\nvar _graphemeBreakAtIndex = function (_codePoints, classTypes, index) {\n var prevIndex = index - 2;\n var prev = classTypes[prevIndex];\n var current = classTypes[index - 1];\n var next = classTypes[index];\n // GB3 Do not break between a CR and LF\n if (current === CR && next === LF) {\n return BREAK_NOT_ALLOWED;\n }\n // GB4 Otherwise, break before and after controls.\n if (current === CR || current === LF || current === Control$1) {\n return BREAK_ALLOWED;\n }\n // GB5\n if (next === CR || next === LF || next === Control$1) {\n return BREAK_ALLOWED;\n }\n // Do not break Hangul syllable sequences.\n // GB6\n if (current === L && [L, V, LV, LVT].indexOf(next) !== -1) {\n return BREAK_NOT_ALLOWED;\n }\n // GB7\n if ((current === LV || current === V) && (next === V || next === T)) {\n return BREAK_NOT_ALLOWED;\n }\n // GB8\n if ((current === LVT || current === T) && next === T) {\n return BREAK_NOT_ALLOWED;\n }\n // GB9 Do not break before extending characters or ZWJ.\n if (next === ZWJ || next === Extend) {\n return BREAK_NOT_ALLOWED;\n }\n // Do not break before SpacingMarks, or after Prepend characters.\n // GB9a\n if (next === SpacingMark) {\n return BREAK_NOT_ALLOWED;\n }\n // GB9a\n if (current === Prepend) {\n return BREAK_NOT_ALLOWED;\n }\n // GB11 Do not break within emoji modifier sequences or emoji zwj sequences.\n if (current === ZWJ && next === Extended_Pictographic) {\n while (prev === Extend) {\n prev = classTypes[--prevIndex];\n }\n if (prev === Extended_Pictographic) {\n return BREAK_NOT_ALLOWED;\n }\n }\n // GB12 Do not break within emoji flag sequences.\n // That is, do not break between regional indicator (RI) symbols\n // if there is an odd number of RI characters before the break point.\n if (current === RI && next === RI) {\n var countRI = 0;\n while (prev === RI) {\n countRI++;\n prev = classTypes[--prevIndex];\n }\n if (countRI % 2 === 0) {\n return BREAK_NOT_ALLOWED;\n }\n }\n return BREAK_ALLOWED;\n};\nvar GraphemeBreaker = function (str) {\n var codePoints = toCodePoints(str);\n var length = codePoints.length;\n var index = 0;\n var lastEnd = 0;\n var classTypes = codePoints.map(codePointToClass);\n return {\n next: function () {\n if (index >= length) {\n return { done: true, value: null };\n }\n var graphemeBreak = BREAK_NOT_ALLOWED;\n while (index < length &&\n (graphemeBreak = _graphemeBreakAtIndex(codePoints, classTypes, ++index)) === BREAK_NOT_ALLOWED) { }\n if (graphemeBreak !== BREAK_NOT_ALLOWED || index === length) {\n var value = fromCodePoint.apply(null, codePoints.slice(lastEnd, index));\n lastEnd = index;\n return { value: value, done: false };\n }\n return { done: true, value: null };\n },\n };\n};\nvar splitGraphemes = function (str) {\n var breaker = GraphemeBreaker(str);\n var graphemes = [];\n var bk;\n while (!(bk = breaker.next()).done) {\n if (bk.value) {\n graphemes.push(bk.value.slice());\n }\n }\n return graphemes;\n};\n\nvar testRangeBounds = function (document) {\n var TEST_HEIGHT = 123;\n if (document.createRange) {\n var range = document.createRange();\n if (range.getBoundingClientRect) {\n var testElement = document.createElement('boundtest');\n testElement.style.height = TEST_HEIGHT + \"px\";\n testElement.style.display = 'block';\n document.body.appendChild(testElement);\n range.selectNode(testElement);\n var rangeBounds = range.getBoundingClientRect();\n var rangeHeight = Math.round(rangeBounds.height);\n document.body.removeChild(testElement);\n if (rangeHeight === TEST_HEIGHT) {\n return true;\n }\n }\n }\n return false;\n};\nvar testIOSLineBreak = function (document) {\n var testElement = document.createElement('boundtest');\n testElement.style.width = '50px';\n testElement.style.display = 'block';\n testElement.style.fontSize = '12px';\n testElement.style.letterSpacing = '0px';\n testElement.style.wordSpacing = '0px';\n document.body.appendChild(testElement);\n var range = document.createRange();\n testElement.innerHTML = typeof ''.repeat === 'function' ? '👨'.repeat(10) : '';\n var node = testElement.firstChild;\n var textList = toCodePoints$1(node.data).map(function (i) { return fromCodePoint$1(i); });\n var offset = 0;\n var prev = {};\n // ios 13 does not handle range getBoundingClientRect line changes correctly #2177\n var supports = textList.every(function (text, i) {\n range.setStart(node, offset);\n range.setEnd(node, offset + text.length);\n var rect = range.getBoundingClientRect();\n offset += text.length;\n var boundAhead = rect.x > prev.x || rect.y > prev.y;\n prev = rect;\n if (i === 0) {\n return true;\n }\n return boundAhead;\n });\n document.body.removeChild(testElement);\n return supports;\n};\nvar testCORS = function () { return typeof new Image().crossOrigin !== 'undefined'; };\nvar testResponseType = function () { return typeof new XMLHttpRequest().responseType === 'string'; };\nvar testSVG = function (document) {\n var img = new Image();\n var canvas = document.createElement('canvas');\n var ctx = canvas.getContext('2d');\n if (!ctx) {\n return false;\n }\n img.src = \"data:image/svg+xml,\";\n try {\n ctx.drawImage(img, 0, 0);\n canvas.toDataURL();\n }\n catch (e) {\n return false;\n }\n return true;\n};\nvar isGreenPixel = function (data) {\n return data[0] === 0 && data[1] === 255 && data[2] === 0 && data[3] === 255;\n};\nvar testForeignObject = function (document) {\n var canvas = document.createElement('canvas');\n var size = 100;\n canvas.width = size;\n canvas.height = size;\n var ctx = canvas.getContext('2d');\n if (!ctx) {\n return Promise.reject(false);\n }\n ctx.fillStyle = 'rgb(0, 255, 0)';\n ctx.fillRect(0, 0, size, size);\n var img = new Image();\n var greenImageSrc = canvas.toDataURL();\n img.src = greenImageSrc;\n var svg = createForeignObjectSVG(size, size, 0, 0, img);\n ctx.fillStyle = 'red';\n ctx.fillRect(0, 0, size, size);\n return loadSerializedSVG$1(svg)\n .then(function (img) {\n ctx.drawImage(img, 0, 0);\n var data = ctx.getImageData(0, 0, size, size).data;\n ctx.fillStyle = 'red';\n ctx.fillRect(0, 0, size, size);\n var node = document.createElement('div');\n node.style.backgroundImage = \"url(\" + greenImageSrc + \")\";\n node.style.height = size + \"px\";\n // Firefox 55 does not render inline tags\n return isGreenPixel(data)\n ? loadSerializedSVG$1(createForeignObjectSVG(size, size, 0, 0, node))\n : Promise.reject(false);\n })\n .then(function (img) {\n ctx.drawImage(img, 0, 0);\n // Edge does not render background-images\n return isGreenPixel(ctx.getImageData(0, 0, size, size).data);\n })\n .catch(function () { return false; });\n};\nvar createForeignObjectSVG = function (width, height, x, y, node) {\n var xmlns = 'http://www.w3.org/2000/svg';\n var svg = document.createElementNS(xmlns, 'svg');\n var foreignObject = document.createElementNS(xmlns, 'foreignObject');\n svg.setAttributeNS(null, 'width', width.toString());\n svg.setAttributeNS(null, 'height', height.toString());\n foreignObject.setAttributeNS(null, 'width', '100%');\n foreignObject.setAttributeNS(null, 'height', '100%');\n foreignObject.setAttributeNS(null, 'x', x.toString());\n foreignObject.setAttributeNS(null, 'y', y.toString());\n foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true');\n svg.appendChild(foreignObject);\n foreignObject.appendChild(node);\n return svg;\n};\nvar loadSerializedSVG$1 = function (svg) {\n return new Promise(function (resolve, reject) {\n var img = new Image();\n img.onload = function () { return resolve(img); };\n img.onerror = reject;\n img.src = \"data:image/svg+xml;charset=utf-8,\" + encodeURIComponent(new XMLSerializer().serializeToString(svg));\n });\n};\nvar FEATURES = {\n get SUPPORT_RANGE_BOUNDS() {\n var value = testRangeBounds(document);\n Object.defineProperty(FEATURES, 'SUPPORT_RANGE_BOUNDS', { value: value });\n return value;\n },\n get SUPPORT_WORD_BREAKING() {\n var value = FEATURES.SUPPORT_RANGE_BOUNDS && testIOSLineBreak(document);\n Object.defineProperty(FEATURES, 'SUPPORT_WORD_BREAKING', { value: value });\n return value;\n },\n get SUPPORT_SVG_DRAWING() {\n var value = testSVG(document);\n Object.defineProperty(FEATURES, 'SUPPORT_SVG_DRAWING', { value: value });\n return value;\n },\n get SUPPORT_FOREIGNOBJECT_DRAWING() {\n var value = typeof Array.from === 'function' && typeof window.fetch === 'function'\n ? testForeignObject(document)\n : Promise.resolve(false);\n Object.defineProperty(FEATURES, 'SUPPORT_FOREIGNOBJECT_DRAWING', { value: value });\n return value;\n },\n get SUPPORT_CORS_IMAGES() {\n var value = testCORS();\n Object.defineProperty(FEATURES, 'SUPPORT_CORS_IMAGES', { value: value });\n return value;\n },\n get SUPPORT_RESPONSE_TYPE() {\n var value = testResponseType();\n Object.defineProperty(FEATURES, 'SUPPORT_RESPONSE_TYPE', { value: value });\n return value;\n },\n get SUPPORT_CORS_XHR() {\n var value = 'withCredentials' in new XMLHttpRequest();\n Object.defineProperty(FEATURES, 'SUPPORT_CORS_XHR', { value: value });\n return value;\n },\n get SUPPORT_NATIVE_TEXT_SEGMENTATION() {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n var value = !!(typeof Intl !== 'undefined' && Intl.Segmenter);\n Object.defineProperty(FEATURES, 'SUPPORT_NATIVE_TEXT_SEGMENTATION', { value: value });\n return value;\n }\n};\n\nvar TextBounds = /** @class */ (function () {\n function TextBounds(text, bounds) {\n this.text = text;\n this.bounds = bounds;\n }\n return TextBounds;\n}());\nvar parseTextBounds = function (context, value, styles, node) {\n var textList = breakText(value, styles);\n var textBounds = [];\n var offset = 0;\n textList.forEach(function (text) {\n if (styles.textDecorationLine.length || text.trim().length > 0) {\n if (FEATURES.SUPPORT_RANGE_BOUNDS) {\n var clientRects = createRange(node, offset, text.length).getClientRects();\n if (clientRects.length > 1) {\n var subSegments = segmentGraphemes(text);\n var subOffset_1 = 0;\n subSegments.forEach(function (subSegment) {\n textBounds.push(new TextBounds(subSegment, Bounds.fromDOMRectList(context, createRange(node, subOffset_1 + offset, subSegment.length).getClientRects())));\n subOffset_1 += subSegment.length;\n });\n }\n else {\n textBounds.push(new TextBounds(text, Bounds.fromDOMRectList(context, clientRects)));\n }\n }\n else {\n var replacementNode = node.splitText(text.length);\n textBounds.push(new TextBounds(text, getWrapperBounds(context, node)));\n node = replacementNode;\n }\n }\n else if (!FEATURES.SUPPORT_RANGE_BOUNDS) {\n node = node.splitText(text.length);\n }\n offset += text.length;\n });\n return textBounds;\n};\nvar getWrapperBounds = function (context, node) {\n var ownerDocument = node.ownerDocument;\n if (ownerDocument) {\n var wrapper = ownerDocument.createElement('html2canvaswrapper');\n wrapper.appendChild(node.cloneNode(true));\n var parentNode = node.parentNode;\n if (parentNode) {\n parentNode.replaceChild(wrapper, node);\n var bounds = parseBounds(context, wrapper);\n if (wrapper.firstChild) {\n parentNode.replaceChild(wrapper.firstChild, wrapper);\n }\n return bounds;\n }\n }\n return Bounds.EMPTY;\n};\nvar createRange = function (node, offset, length) {\n var ownerDocument = node.ownerDocument;\n if (!ownerDocument) {\n throw new Error('Node has no owner document');\n }\n var range = ownerDocument.createRange();\n range.setStart(node, offset);\n range.setEnd(node, offset + length);\n return range;\n};\nvar segmentGraphemes = function (value) {\n if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n var segmenter = new Intl.Segmenter(void 0, { granularity: 'grapheme' });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Array.from(segmenter.segment(value)).map(function (segment) { return segment.segment; });\n }\n return splitGraphemes(value);\n};\nvar segmentWords = function (value, styles) {\n if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n var segmenter = new Intl.Segmenter(void 0, {\n granularity: 'word'\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return Array.from(segmenter.segment(value)).map(function (segment) { return segment.segment; });\n }\n return breakWords(value, styles);\n};\nvar breakText = function (value, styles) {\n return styles.letterSpacing !== 0 ? segmentGraphemes(value) : segmentWords(value, styles);\n};\n// https://drafts.csswg.org/css-text/#word-separator\nvar wordSeparators = [0x0020, 0x00a0, 0x1361, 0x10100, 0x10101, 0x1039, 0x1091];\nvar breakWords = function (str, styles) {\n var breaker = LineBreaker(str, {\n lineBreak: styles.lineBreak,\n wordBreak: styles.overflowWrap === \"break-word\" /* BREAK_WORD */ ? 'break-word' : styles.wordBreak\n });\n var words = [];\n var bk;\n var _loop_1 = function () {\n if (bk.value) {\n var value = bk.value.slice();\n var codePoints = toCodePoints$1(value);\n var word_1 = '';\n codePoints.forEach(function (codePoint) {\n if (wordSeparators.indexOf(codePoint) === -1) {\n word_1 += fromCodePoint$1(codePoint);\n }\n else {\n if (word_1.length) {\n words.push(word_1);\n }\n words.push(fromCodePoint$1(codePoint));\n word_1 = '';\n }\n });\n if (word_1.length) {\n words.push(word_1);\n }\n }\n };\n while (!(bk = breaker.next()).done) {\n _loop_1();\n }\n return words;\n};\n\nvar TextContainer = /** @class */ (function () {\n function TextContainer(context, node, styles) {\n this.text = transform(node.data, styles.textTransform);\n this.textBounds = parseTextBounds(context, this.text, styles, node);\n }\n return TextContainer;\n}());\nvar transform = function (text, transform) {\n switch (transform) {\n case 1 /* LOWERCASE */:\n return text.toLowerCase();\n case 3 /* CAPITALIZE */:\n return text.replace(CAPITALIZE, capitalize);\n case 2 /* UPPERCASE */:\n return text.toUpperCase();\n default:\n return text;\n }\n};\nvar CAPITALIZE = /(^|\\s|:|-|\\(|\\))([a-z])/g;\nvar capitalize = function (m, p1, p2) {\n if (m.length > 0) {\n return p1 + p2.toUpperCase();\n }\n return m;\n};\n\nvar ImageElementContainer = /** @class */ (function (_super) {\n __extends(ImageElementContainer, _super);\n function ImageElementContainer(context, img) {\n var _this = _super.call(this, context, img) || this;\n _this.src = img.currentSrc || img.src;\n _this.intrinsicWidth = img.naturalWidth;\n _this.intrinsicHeight = img.naturalHeight;\n _this.context.cache.addImage(_this.src);\n return _this;\n }\n return ImageElementContainer;\n}(ElementContainer));\n\nvar CanvasElementContainer = /** @class */ (function (_super) {\n __extends(CanvasElementContainer, _super);\n function CanvasElementContainer(context, canvas) {\n var _this = _super.call(this, context, canvas) || this;\n _this.canvas = canvas;\n _this.intrinsicWidth = canvas.width;\n _this.intrinsicHeight = canvas.height;\n return _this;\n }\n return CanvasElementContainer;\n}(ElementContainer));\n\nvar SVGElementContainer = /** @class */ (function (_super) {\n __extends(SVGElementContainer, _super);\n function SVGElementContainer(context, img) {\n var _this = _super.call(this, context, img) || this;\n var s = new XMLSerializer();\n var bounds = parseBounds(context, img);\n img.setAttribute('width', bounds.width + \"px\");\n img.setAttribute('height', bounds.height + \"px\");\n _this.svg = \"data:image/svg+xml,\" + encodeURIComponent(s.serializeToString(img));\n _this.intrinsicWidth = img.width.baseVal.value;\n _this.intrinsicHeight = img.height.baseVal.value;\n _this.context.cache.addImage(_this.svg);\n return _this;\n }\n return SVGElementContainer;\n}(ElementContainer));\n\nvar LIElementContainer = /** @class */ (function (_super) {\n __extends(LIElementContainer, _super);\n function LIElementContainer(context, element) {\n var _this = _super.call(this, context, element) || this;\n _this.value = element.value;\n return _this;\n }\n return LIElementContainer;\n}(ElementContainer));\n\nvar OLElementContainer = /** @class */ (function (_super) {\n __extends(OLElementContainer, _super);\n function OLElementContainer(context, element) {\n var _this = _super.call(this, context, element) || this;\n _this.start = element.start;\n _this.reversed = typeof element.reversed === 'boolean' && element.reversed === true;\n return _this;\n }\n return OLElementContainer;\n}(ElementContainer));\n\nvar CHECKBOX_BORDER_RADIUS = [\n {\n type: 15 /* DIMENSION_TOKEN */,\n flags: 0,\n unit: 'px',\n number: 3\n }\n];\nvar RADIO_BORDER_RADIUS = [\n {\n type: 16 /* PERCENTAGE_TOKEN */,\n flags: 0,\n number: 50\n }\n];\nvar reformatInputBounds = function (bounds) {\n if (bounds.width > bounds.height) {\n return new Bounds(bounds.left + (bounds.width - bounds.height) / 2, bounds.top, bounds.height, bounds.height);\n }\n else if (bounds.width < bounds.height) {\n return new Bounds(bounds.left, bounds.top + (bounds.height - bounds.width) / 2, bounds.width, bounds.width);\n }\n return bounds;\n};\nvar getInputValue = function (node) {\n var value = node.type === PASSWORD ? new Array(node.value.length + 1).join('\\u2022') : node.value;\n return value.length === 0 ? node.placeholder || '' : value;\n};\nvar CHECKBOX = 'checkbox';\nvar RADIO = 'radio';\nvar PASSWORD = 'password';\nvar INPUT_COLOR = 0x2a2a2aff;\nvar InputElementContainer = /** @class */ (function (_super) {\n __extends(InputElementContainer, _super);\n function InputElementContainer(context, input) {\n var _this = _super.call(this, context, input) || this;\n _this.type = input.type.toLowerCase();\n _this.checked = input.checked;\n _this.value = getInputValue(input);\n if (_this.type === CHECKBOX || _this.type === RADIO) {\n _this.styles.backgroundColor = 0xdededeff;\n _this.styles.borderTopColor =\n _this.styles.borderRightColor =\n _this.styles.borderBottomColor =\n _this.styles.borderLeftColor =\n 0xa5a5a5ff;\n _this.styles.borderTopWidth =\n _this.styles.borderRightWidth =\n _this.styles.borderBottomWidth =\n _this.styles.borderLeftWidth =\n 1;\n _this.styles.borderTopStyle =\n _this.styles.borderRightStyle =\n _this.styles.borderBottomStyle =\n _this.styles.borderLeftStyle =\n 1 /* SOLID */;\n _this.styles.backgroundClip = [0 /* BORDER_BOX */];\n _this.styles.backgroundOrigin = [0 /* BORDER_BOX */];\n _this.bounds = reformatInputBounds(_this.bounds);\n }\n switch (_this.type) {\n case CHECKBOX:\n _this.styles.borderTopRightRadius =\n _this.styles.borderTopLeftRadius =\n _this.styles.borderBottomRightRadius =\n _this.styles.borderBottomLeftRadius =\n CHECKBOX_BORDER_RADIUS;\n break;\n case RADIO:\n _this.styles.borderTopRightRadius =\n _this.styles.borderTopLeftRadius =\n _this.styles.borderBottomRightRadius =\n _this.styles.borderBottomLeftRadius =\n RADIO_BORDER_RADIUS;\n break;\n }\n return _this;\n }\n return InputElementContainer;\n}(ElementContainer));\n\nvar SelectElementContainer = /** @class */ (function (_super) {\n __extends(SelectElementContainer, _super);\n function SelectElementContainer(context, element) {\n var _this = _super.call(this, context, element) || this;\n var option = element.options[element.selectedIndex || 0];\n _this.value = option ? option.text || '' : '';\n return _this;\n }\n return SelectElementContainer;\n}(ElementContainer));\n\nvar TextareaElementContainer = /** @class */ (function (_super) {\n __extends(TextareaElementContainer, _super);\n function TextareaElementContainer(context, element) {\n var _this = _super.call(this, context, element) || this;\n _this.value = element.value;\n return _this;\n }\n return TextareaElementContainer;\n}(ElementContainer));\n\nvar IFrameElementContainer = /** @class */ (function (_super) {\n __extends(IFrameElementContainer, _super);\n function IFrameElementContainer(context, iframe) {\n var _this = _super.call(this, context, iframe) || this;\n _this.src = iframe.src;\n _this.width = parseInt(iframe.width, 10) || 0;\n _this.height = parseInt(iframe.height, 10) || 0;\n _this.backgroundColor = _this.styles.backgroundColor;\n try {\n if (iframe.contentWindow &&\n iframe.contentWindow.document &&\n iframe.contentWindow.document.documentElement) {\n _this.tree = parseTree(context, iframe.contentWindow.document.documentElement);\n // http://www.w3.org/TR/css3-background/#special-backgrounds\n var documentBackgroundColor = iframe.contentWindow.document.documentElement\n ? parseColor(context, getComputedStyle(iframe.contentWindow.document.documentElement).backgroundColor)\n : COLORS.TRANSPARENT;\n var bodyBackgroundColor = iframe.contentWindow.document.body\n ? parseColor(context, getComputedStyle(iframe.contentWindow.document.body).backgroundColor)\n : COLORS.TRANSPARENT;\n _this.backgroundColor = isTransparent(documentBackgroundColor)\n ? isTransparent(bodyBackgroundColor)\n ? _this.styles.backgroundColor\n : bodyBackgroundColor\n : documentBackgroundColor;\n }\n }\n catch (e) { }\n return _this;\n }\n return IFrameElementContainer;\n}(ElementContainer));\n\nvar LIST_OWNERS = ['OL', 'UL', 'MENU'];\nvar parseNodeTree = function (context, node, parent, root) {\n for (var childNode = node.firstChild, nextNode = void 0; childNode; childNode = nextNode) {\n nextNode = childNode.nextSibling;\n if (isTextNode(childNode) && childNode.data.trim().length > 0) {\n parent.textNodes.push(new TextContainer(context, childNode, parent.styles));\n }\n else if (isElementNode(childNode)) {\n if (isSlotElement(childNode) && childNode.assignedNodes) {\n childNode.assignedNodes().forEach(function (childNode) { return parseNodeTree(context, childNode, parent, root); });\n }\n else {\n var container = createContainer(context, childNode);\n if (container.styles.isVisible()) {\n if (createsRealStackingContext(childNode, container, root)) {\n container.flags |= 4 /* CREATES_REAL_STACKING_CONTEXT */;\n }\n else if (createsStackingContext(container.styles)) {\n container.flags |= 2 /* CREATES_STACKING_CONTEXT */;\n }\n if (LIST_OWNERS.indexOf(childNode.tagName) !== -1) {\n container.flags |= 8 /* IS_LIST_OWNER */;\n }\n parent.elements.push(container);\n childNode.slot;\n if (childNode.shadowRoot) {\n parseNodeTree(context, childNode.shadowRoot, container, root);\n }\n else if (!isTextareaElement(childNode) &&\n !isSVGElement(childNode) &&\n !isSelectElement(childNode)) {\n parseNodeTree(context, childNode, container, root);\n }\n }\n }\n }\n }\n};\nvar createContainer = function (context, element) {\n if (isImageElement(element)) {\n return new ImageElementContainer(context, element);\n }\n if (isCanvasElement(element)) {\n return new CanvasElementContainer(context, element);\n }\n if (isSVGElement(element)) {\n return new SVGElementContainer(context, element);\n }\n if (isLIElement(element)) {\n return new LIElementContainer(context, element);\n }\n if (isOLElement(element)) {\n return new OLElementContainer(context, element);\n }\n if (isInputElement(element)) {\n return new InputElementContainer(context, element);\n }\n if (isSelectElement(element)) {\n return new SelectElementContainer(context, element);\n }\n if (isTextareaElement(element)) {\n return new TextareaElementContainer(context, element);\n }\n if (isIFrameElement(element)) {\n return new IFrameElementContainer(context, element);\n }\n return new ElementContainer(context, element);\n};\nvar parseTree = function (context, element) {\n var container = createContainer(context, element);\n container.flags |= 4 /* CREATES_REAL_STACKING_CONTEXT */;\n parseNodeTree(context, element, container, container);\n return container;\n};\nvar createsRealStackingContext = function (node, container, root) {\n return (container.styles.isPositionedWithZIndex() ||\n container.styles.opacity < 1 ||\n container.styles.isTransformed() ||\n (isBodyElement(node) && root.styles.isTransparent()));\n};\nvar createsStackingContext = function (styles) { return styles.isPositioned() || styles.isFloating(); };\nvar isTextNode = function (node) { return node.nodeType === Node.TEXT_NODE; };\nvar isElementNode = function (node) { return node.nodeType === Node.ELEMENT_NODE; };\nvar isHTMLElementNode = function (node) {\n return isElementNode(node) && typeof node.style !== 'undefined' && !isSVGElementNode(node);\n};\nvar isSVGElementNode = function (element) {\n return typeof element.className === 'object';\n};\nvar isLIElement = function (node) { return node.tagName === 'LI'; };\nvar isOLElement = function (node) { return node.tagName === 'OL'; };\nvar isInputElement = function (node) { return node.tagName === 'INPUT'; };\nvar isHTMLElement = function (node) { return node.tagName === 'HTML'; };\nvar isSVGElement = function (node) { return node.tagName === 'svg'; };\nvar isBodyElement = function (node) { return node.tagName === 'BODY'; };\nvar isCanvasElement = function (node) { return node.tagName === 'CANVAS'; };\nvar isVideoElement = function (node) { return node.tagName === 'VIDEO'; };\nvar isImageElement = function (node) { return node.tagName === 'IMG'; };\nvar isIFrameElement = function (node) { return node.tagName === 'IFRAME'; };\nvar isStyleElement = function (node) { return node.tagName === 'STYLE'; };\nvar isScriptElement = function (node) { return node.tagName === 'SCRIPT'; };\nvar isTextareaElement = function (node) { return node.tagName === 'TEXTAREA'; };\nvar isSelectElement = function (node) { return node.tagName === 'SELECT'; };\nvar isSlotElement = function (node) { return node.tagName === 'SLOT'; };\n// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name\nvar isCustomElement = function (node) { return node.tagName.indexOf('-') > 0; };\n\nvar CounterState = /** @class */ (function () {\n function CounterState() {\n this.counters = {};\n }\n CounterState.prototype.getCounterValue = function (name) {\n var counter = this.counters[name];\n if (counter && counter.length) {\n return counter[counter.length - 1];\n }\n return 1;\n };\n CounterState.prototype.getCounterValues = function (name) {\n var counter = this.counters[name];\n return counter ? counter : [];\n };\n CounterState.prototype.pop = function (counters) {\n var _this = this;\n counters.forEach(function (counter) { return _this.counters[counter].pop(); });\n };\n CounterState.prototype.parse = function (style) {\n var _this = this;\n var counterIncrement = style.counterIncrement;\n var counterReset = style.counterReset;\n var canReset = true;\n if (counterIncrement !== null) {\n counterIncrement.forEach(function (entry) {\n var counter = _this.counters[entry.counter];\n if (counter && entry.increment !== 0) {\n canReset = false;\n if (!counter.length) {\n counter.push(1);\n }\n counter[Math.max(0, counter.length - 1)] += entry.increment;\n }\n });\n }\n var counterNames = [];\n if (canReset) {\n counterReset.forEach(function (entry) {\n var counter = _this.counters[entry.counter];\n counterNames.push(entry.counter);\n if (!counter) {\n counter = _this.counters[entry.counter] = [];\n }\n counter.push(entry.reset);\n });\n }\n return counterNames;\n };\n return CounterState;\n}());\nvar ROMAN_UPPER = {\n integers: [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],\n values: ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']\n};\nvar ARMENIAN = {\n integers: [\n 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 90, 80, 70,\n 60, 50, 40, 30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1\n ],\n values: [\n 'Ք',\n 'Փ',\n 'Ւ',\n 'Ց',\n 'Ր',\n 'Տ',\n 'Վ',\n 'Ս',\n 'Ռ',\n 'Ջ',\n 'Պ',\n 'Չ',\n 'Ո',\n 'Շ',\n 'Ն',\n 'Յ',\n 'Մ',\n 'Ճ',\n 'Ղ',\n 'Ձ',\n 'Հ',\n 'Կ',\n 'Ծ',\n 'Խ',\n 'Լ',\n 'Ի',\n 'Ժ',\n 'Թ',\n 'Ը',\n 'Է',\n 'Զ',\n 'Ե',\n 'Դ',\n 'Գ',\n 'Բ',\n 'Ա'\n ]\n};\nvar HEBREW = {\n integers: [\n 10000, 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, 400, 300, 200, 100, 90, 80, 70, 60, 50, 40, 30, 20,\n 19, 18, 17, 16, 15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1\n ],\n values: [\n 'י׳',\n 'ט׳',\n 'ח׳',\n 'ז׳',\n 'ו׳',\n 'ה׳',\n 'ד׳',\n 'ג׳',\n 'ב׳',\n 'א׳',\n 'ת',\n 'ש',\n 'ר',\n 'ק',\n 'צ',\n 'פ',\n 'ע',\n 'ס',\n 'נ',\n 'מ',\n 'ל',\n 'כ',\n 'יט',\n 'יח',\n 'יז',\n 'טז',\n 'טו',\n 'י',\n 'ט',\n 'ח',\n 'ז',\n 'ו',\n 'ה',\n 'ד',\n 'ג',\n 'ב',\n 'א'\n ]\n};\nvar GEORGIAN = {\n integers: [\n 10000, 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 90,\n 80, 70, 60, 50, 40, 30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1\n ],\n values: [\n 'ჵ',\n 'ჰ',\n 'ჯ',\n 'ჴ',\n 'ხ',\n 'ჭ',\n 'წ',\n 'ძ',\n 'ც',\n 'ჩ',\n 'შ',\n 'ყ',\n 'ღ',\n 'ქ',\n 'ფ',\n 'ჳ',\n 'ტ',\n 'ს',\n 'რ',\n 'ჟ',\n 'პ',\n 'ო',\n 'ჲ',\n 'ნ',\n 'მ',\n 'ლ',\n 'კ',\n 'ი',\n 'თ',\n 'ჱ',\n 'ზ',\n 'ვ',\n 'ე',\n 'დ',\n 'გ',\n 'ბ',\n 'ა'\n ]\n};\nvar createAdditiveCounter = function (value, min, max, symbols, fallback, suffix) {\n if (value < min || value > max) {\n return createCounterText(value, fallback, suffix.length > 0);\n }\n return (symbols.integers.reduce(function (string, integer, index) {\n while (value >= integer) {\n value -= integer;\n string += symbols.values[index];\n }\n return string;\n }, '') + suffix);\n};\nvar createCounterStyleWithSymbolResolver = function (value, codePointRangeLength, isNumeric, resolver) {\n var string = '';\n do {\n if (!isNumeric) {\n value--;\n }\n string = resolver(value) + string;\n value /= codePointRangeLength;\n } while (value * codePointRangeLength >= codePointRangeLength);\n return string;\n};\nvar createCounterStyleFromRange = function (value, codePointRangeStart, codePointRangeEnd, isNumeric, suffix) {\n var codePointRangeLength = codePointRangeEnd - codePointRangeStart + 1;\n return ((value < 0 ? '-' : '') +\n (createCounterStyleWithSymbolResolver(Math.abs(value), codePointRangeLength, isNumeric, function (codePoint) {\n return fromCodePoint$1(Math.floor(codePoint % codePointRangeLength) + codePointRangeStart);\n }) +\n suffix));\n};\nvar createCounterStyleFromSymbols = function (value, symbols, suffix) {\n if (suffix === void 0) { suffix = '. '; }\n var codePointRangeLength = symbols.length;\n return (createCounterStyleWithSymbolResolver(Math.abs(value), codePointRangeLength, false, function (codePoint) { return symbols[Math.floor(codePoint % codePointRangeLength)]; }) + suffix);\n};\nvar CJK_ZEROS = 1 << 0;\nvar CJK_TEN_COEFFICIENTS = 1 << 1;\nvar CJK_TEN_HIGH_COEFFICIENTS = 1 << 2;\nvar CJK_HUNDRED_COEFFICIENTS = 1 << 3;\nvar createCJKCounter = function (value, numbers, multipliers, negativeSign, suffix, flags) {\n if (value < -9999 || value > 9999) {\n return createCounterText(value, 4 /* CJK_DECIMAL */, suffix.length > 0);\n }\n var tmp = Math.abs(value);\n var string = suffix;\n if (tmp === 0) {\n return numbers[0] + string;\n }\n for (var digit = 0; tmp > 0 && digit <= 4; digit++) {\n var coefficient = tmp % 10;\n if (coefficient === 0 && contains(flags, CJK_ZEROS) && string !== '') {\n string = numbers[coefficient] + string;\n }\n else if (coefficient > 1 ||\n (coefficient === 1 && digit === 0) ||\n (coefficient === 1 && digit === 1 && contains(flags, CJK_TEN_COEFFICIENTS)) ||\n (coefficient === 1 && digit === 1 && contains(flags, CJK_TEN_HIGH_COEFFICIENTS) && value > 100) ||\n (coefficient === 1 && digit > 1 && contains(flags, CJK_HUNDRED_COEFFICIENTS))) {\n string = numbers[coefficient] + (digit > 0 ? multipliers[digit - 1] : '') + string;\n }\n else if (coefficient === 1 && digit > 0) {\n string = multipliers[digit - 1] + string;\n }\n tmp = Math.floor(tmp / 10);\n }\n return (value < 0 ? negativeSign : '') + string;\n};\nvar CHINESE_INFORMAL_MULTIPLIERS = '十百千萬';\nvar CHINESE_FORMAL_MULTIPLIERS = '拾佰仟萬';\nvar JAPANESE_NEGATIVE = 'マイナス';\nvar KOREAN_NEGATIVE = '마이너스';\nvar createCounterText = function (value, type, appendSuffix) {\n var defaultSuffix = appendSuffix ? '. ' : '';\n var cjkSuffix = appendSuffix ? '、' : '';\n var koreanSuffix = appendSuffix ? ', ' : '';\n var spaceSuffix = appendSuffix ? ' ' : '';\n switch (type) {\n case 0 /* DISC */:\n return '•' + spaceSuffix;\n case 1 /* CIRCLE */:\n return '◦' + spaceSuffix;\n case 2 /* SQUARE */:\n return '◾' + spaceSuffix;\n case 5 /* DECIMAL_LEADING_ZERO */:\n var string = createCounterStyleFromRange(value, 48, 57, true, defaultSuffix);\n return string.length < 4 ? \"0\" + string : string;\n case 4 /* CJK_DECIMAL */:\n return createCounterStyleFromSymbols(value, '〇一二三四五六七八九', cjkSuffix);\n case 6 /* LOWER_ROMAN */:\n return createAdditiveCounter(value, 1, 3999, ROMAN_UPPER, 3 /* DECIMAL */, defaultSuffix).toLowerCase();\n case 7 /* UPPER_ROMAN */:\n return createAdditiveCounter(value, 1, 3999, ROMAN_UPPER, 3 /* DECIMAL */, defaultSuffix);\n case 8 /* LOWER_GREEK */:\n return createCounterStyleFromRange(value, 945, 969, false, defaultSuffix);\n case 9 /* LOWER_ALPHA */:\n return createCounterStyleFromRange(value, 97, 122, false, defaultSuffix);\n case 10 /* UPPER_ALPHA */:\n return createCounterStyleFromRange(value, 65, 90, false, defaultSuffix);\n case 11 /* ARABIC_INDIC */:\n return createCounterStyleFromRange(value, 1632, 1641, true, defaultSuffix);\n case 12 /* ARMENIAN */:\n case 49 /* UPPER_ARMENIAN */:\n return createAdditiveCounter(value, 1, 9999, ARMENIAN, 3 /* DECIMAL */, defaultSuffix);\n case 35 /* LOWER_ARMENIAN */:\n return createAdditiveCounter(value, 1, 9999, ARMENIAN, 3 /* DECIMAL */, defaultSuffix).toLowerCase();\n case 13 /* BENGALI */:\n return createCounterStyleFromRange(value, 2534, 2543, true, defaultSuffix);\n case 14 /* CAMBODIAN */:\n case 30 /* KHMER */:\n return createCounterStyleFromRange(value, 6112, 6121, true, defaultSuffix);\n case 15 /* CJK_EARTHLY_BRANCH */:\n return createCounterStyleFromSymbols(value, '子丑寅卯辰巳午未申酉戌亥', cjkSuffix);\n case 16 /* CJK_HEAVENLY_STEM */:\n return createCounterStyleFromSymbols(value, '甲乙丙丁戊己庚辛壬癸', cjkSuffix);\n case 17 /* CJK_IDEOGRAPHIC */:\n case 48 /* TRAD_CHINESE_INFORMAL */:\n return createCJKCounter(value, '零一二三四五六七八九', CHINESE_INFORMAL_MULTIPLIERS, '負', cjkSuffix, CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS);\n case 47 /* TRAD_CHINESE_FORMAL */:\n return createCJKCounter(value, '零壹貳參肆伍陸柒捌玖', CHINESE_FORMAL_MULTIPLIERS, '負', cjkSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS);\n case 42 /* SIMP_CHINESE_INFORMAL */:\n return createCJKCounter(value, '零一二三四五六七八九', CHINESE_INFORMAL_MULTIPLIERS, '负', cjkSuffix, CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS);\n case 41 /* SIMP_CHINESE_FORMAL */:\n return createCJKCounter(value, '零壹贰叁肆伍陆柒捌玖', CHINESE_FORMAL_MULTIPLIERS, '负', cjkSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS);\n case 26 /* JAPANESE_INFORMAL */:\n return createCJKCounter(value, '〇一二三四五六七八九', '十百千万', JAPANESE_NEGATIVE, cjkSuffix, 0);\n case 25 /* JAPANESE_FORMAL */:\n return createCJKCounter(value, '零壱弐参四伍六七八九', '拾百千万', JAPANESE_NEGATIVE, cjkSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS);\n case 31 /* KOREAN_HANGUL_FORMAL */:\n return createCJKCounter(value, '영일이삼사오육칠팔구', '십백천만', KOREAN_NEGATIVE, koreanSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS);\n case 33 /* KOREAN_HANJA_INFORMAL */:\n return createCJKCounter(value, '零一二三四五六七八九', '十百千萬', KOREAN_NEGATIVE, koreanSuffix, 0);\n case 32 /* KOREAN_HANJA_FORMAL */:\n return createCJKCounter(value, '零壹貳參四五六七八九', '拾百千', KOREAN_NEGATIVE, koreanSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS);\n case 18 /* DEVANAGARI */:\n return createCounterStyleFromRange(value, 0x966, 0x96f, true, defaultSuffix);\n case 20 /* GEORGIAN */:\n return createAdditiveCounter(value, 1, 19999, GEORGIAN, 3 /* DECIMAL */, defaultSuffix);\n case 21 /* GUJARATI */:\n return createCounterStyleFromRange(value, 0xae6, 0xaef, true, defaultSuffix);\n case 22 /* GURMUKHI */:\n return createCounterStyleFromRange(value, 0xa66, 0xa6f, true, defaultSuffix);\n case 22 /* HEBREW */:\n return createAdditiveCounter(value, 1, 10999, HEBREW, 3 /* DECIMAL */, defaultSuffix);\n case 23 /* HIRAGANA */:\n return createCounterStyleFromSymbols(value, 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわゐゑをん');\n case 24 /* HIRAGANA_IROHA */:\n return createCounterStyleFromSymbols(value, 'いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす');\n case 27 /* KANNADA */:\n return createCounterStyleFromRange(value, 0xce6, 0xcef, true, defaultSuffix);\n case 28 /* KATAKANA */:\n return createCounterStyleFromSymbols(value, 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヰヱヲン', cjkSuffix);\n case 29 /* KATAKANA_IROHA */:\n return createCounterStyleFromSymbols(value, 'イロハニホヘトチリヌルヲワカヨタレソツネナラムウヰノオクヤマケフコエテアサキユメミシヱヒモセス', cjkSuffix);\n case 34 /* LAO */:\n return createCounterStyleFromRange(value, 0xed0, 0xed9, true, defaultSuffix);\n case 37 /* MONGOLIAN */:\n return createCounterStyleFromRange(value, 0x1810, 0x1819, true, defaultSuffix);\n case 38 /* MYANMAR */:\n return createCounterStyleFromRange(value, 0x1040, 0x1049, true, defaultSuffix);\n case 39 /* ORIYA */:\n return createCounterStyleFromRange(value, 0xb66, 0xb6f, true, defaultSuffix);\n case 40 /* PERSIAN */:\n return createCounterStyleFromRange(value, 0x6f0, 0x6f9, true, defaultSuffix);\n case 43 /* TAMIL */:\n return createCounterStyleFromRange(value, 0xbe6, 0xbef, true, defaultSuffix);\n case 44 /* TELUGU */:\n return createCounterStyleFromRange(value, 0xc66, 0xc6f, true, defaultSuffix);\n case 45 /* THAI */:\n return createCounterStyleFromRange(value, 0xe50, 0xe59, true, defaultSuffix);\n case 46 /* TIBETAN */:\n return createCounterStyleFromRange(value, 0xf20, 0xf29, true, defaultSuffix);\n case 3 /* DECIMAL */:\n default:\n return createCounterStyleFromRange(value, 48, 57, true, defaultSuffix);\n }\n};\n\nvar IGNORE_ATTRIBUTE = 'data-html2canvas-ignore';\nvar DocumentCloner = /** @class */ (function () {\n function DocumentCloner(context, element, options) {\n this.context = context;\n this.options = options;\n this.scrolledElements = [];\n this.referenceElement = element;\n this.counters = new CounterState();\n this.quoteDepth = 0;\n if (!element.ownerDocument) {\n throw new Error('Cloned element does not have an owner document');\n }\n this.documentElement = this.cloneNode(element.ownerDocument.documentElement, false);\n }\n DocumentCloner.prototype.toIFrame = function (ownerDocument, windowSize) {\n var _this = this;\n var iframe = createIFrameContainer(ownerDocument, windowSize);\n if (!iframe.contentWindow) {\n return Promise.reject(\"Unable to find iframe window\");\n }\n var scrollX = ownerDocument.defaultView.pageXOffset;\n var scrollY = ownerDocument.defaultView.pageYOffset;\n var cloneWindow = iframe.contentWindow;\n var documentClone = cloneWindow.document;\n /* Chrome doesn't detect relative background-images assigned in inline