diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..25d2636
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,28 @@
+name: Feder release action
+
+on:
+ release:
+ types: [released]
+ branches:
+ - main
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Build app
+ run: |
+ npm run build
+
+ - uses: JS-DevTools/npm-publish@v1
+ with:
+ token: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 02605f2..efab820 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,15 @@
.DS_Store
.nyc_output
+.vscode
+.ipynb_checkpoints
+*.egg-info
coverage/
node_modules/
+# test/data/*
+hnswlib_hnsw_random_1M.index
+images/
+dist
+*.csv
+*.index
+*.pyc
+test/bundle.js
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..b23d587
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "pwa-chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:8080",
+ "webRoot": "${workspaceFolder}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 273afe1..4895185 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,209 @@
-# feder
+# Feder
## What is feder
-Feder is built for visualize anns index files, so that we can have a better understanding of anns and high dimensional vectors. so far, we are focusing on Faiss index and HNSW index, we will cover more index types later.
-## Quick start
-### installation
-``` shell
-#install
-npm install @zilliz/Feder
+Feder is an javascript tool that built for understanding your embedding vectors, feder visualizes faiss, hnswlib and other anns index files, so that we can have a better understanding how anns work and what are high dimensional vector embeddings.
+
+So far, we are focusing on the Faiss (only ivf_flat) index file and HNSWlib (hnsw) index file, we will cover more index types later.
+
+Feder is written in **javascript**, and we also provide a python library **federpy**, which is based on federjs.
+
+> **_NOTE:_**
+
+- In IPython environment, it supports users to generate the corresponding visualization directly.
+- In other environments, it supports outputting visualizations as html files, which can be opened by the user through the browser with web service enabled.
+
+
+### Online demos
+- [Understanding vector embeddings with Feder by a reverse image search example](https://observablehq.com/@min-tian/reverse-image-search-feder-faiss-ivf_flat-visualizations)
+- [Javascript example (Observable)](https://observablehq.com/@min-tian/feder)
+- [Jupternotebook example (Colab)](https://colab.research.google.com/drive/12L_oJPR-yFDlORpPondsqGNTPVsSsUwi#scrollTo=N3qqBAYxYcbt)
+
+### How feder works
+- [Feder ivf layout](https://observablehq.com/@min-tian/feder-layout-ivf_flat/2)
+- [Feder hnsw layout ](https://observablehq.com/@min-tian/feder-layout-hnsw/2)
+
+### Wiki
+
+- [Usage](https://github.com/zilliztech/feder/wiki)
+
+### HNSW visualization screenshots
+
+![image](./fig/hnsw_search.png)
+
+### IVF_Flat visualization screenshots
+
+![image](./fig/ivfflat_coarse.png)
+![image](./fig/ivfflat_fine_polar.png)
+![image](./fig/ivfflat_fine_project.png)
+
+## Quick Start
+
+### Installation
+
+Use npm or yarn.
+
+```shell
+yarn install @zilliz/feder
```
-### basic usage
+### Material Preparation
+
+Make sure that you have built an index and dumped the index file by Faiss or HNSWlib.
+
+### Init Feder
+
+Specifying the dom container that you want to show the visualizations.
+
```js
-import { Feder } from '@zilliz/Feder';
+import { Feder } from '@zilliz/feder';
const feder = new Feder({
- file: 'faiss_file', // file path
- type: 'faiss', // faiss | hnsw
- domContainer, // attach dom to render
+ filePath: 'faiss_file', // file path
+ source: 'faiss', // faiss | hnswlib
+ domSelector: '#container', // attach dom to render
+ viewParams: {}, // optional
});
+```
+
+### Visualize the index structure.
-// you can call search to visualize the search process
-feder.search(target, params);
-// or reset to initialize state
-feder.reset();
+- HNSW - Feder will show the top-3 levels of the hnsw-tree.
+- IVF_Flat - Feder will show all the clusters.
+
+```js
+feder.overview();
```
-## API
-TBD
-## Examples
-### Use feder with d3
-TBD
+### Explore the search process.
-### Use feder with react
-TBD
+Set search parameters (optional) and Specify the query vector.
+
+```js
+feder
+ .setSearchParams({
+ k: 8, // hnsw, ivf_flat
+ ef: 100, // hnsw (ef_search)
+ nprobe: 8, // ivf_flat
+ })
+ .search(target_vector);
+```
+
+## Examples
-## How feder works
-TBD
+We prepare a simple case, which is the visualizations of the `hnsw` and `ivf_flat` with 17,000+ vectors that embedded from [VOC 2012](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar)).
+Only need enable a web service.
-## How to contribute
```shell
-# install dependencies
-npm install
-# create build
-npm run build
-# test
-npm run test
+git clone git@github.com:zilliztech/feder.git
+cd test
+python -m http.server
```
+
+Then open http://localhost:8000/
+
+It will show 4 visualizations:
+- `hnsw` overview
+- `hnsw` search view
+- `ivf_flat` overview
+- `ivf_flat` search view
+
+## Pipeline - explore a new dataset with feder
+
+### Step 1. Dataset preparation
+
+Put all images to **test/data/images/**. (example dataset [VOC 2012](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar))
+
+You can also generate random vectors without embedding for index building and skip to **step 3**.
+
+### Step 2. Generate embedding vectors
+
+Recommend to use [towhee](https://github.com/towhee-io/towhee), one line of code to generating embedding vectors!
+
+We have the [encoded vectors](https://assets.zilliz.com/voc_vectors_e8ec5a5eae.csv) ready for you.
+
+### Step 3. Build an index and dump it.
+
+You can use [faiss](https://github.com/facebookresearch/faiss) or [hnswlib](https://github.com/nmslib/hnswlib) to build the index.
+
+(\*Detailed procedures please refer to their tutorials.)
+
+Referring to **test/data/gen_hnswlib_index_\*.py** or **test/data/gen_faiss_index_\*.py**
+
+Or we have the [index file](https://assets.zilliz.com/hnswlib_hnsw_voc_17k_1f1dfd63a9.index) ready for you.
+
+### Step 4. Init Feder.
+
+```js
+import { Feder } from '@zilliz/feder';
+import * as d3 from 'd3';
+
+const domSelector = '#container';
+const filePath = [index_file_path];
+
+const mediaCallback = (rowId) => mediaUrl;
+
+const feder = new Feder({
+ filePath,
+ source: 'hnswlib',
+ domSelector,
+ viewParams: {
+ mediaType: 'img',
+ mediaCallback,
+ },
+});
+```
+
+If use the random_data, **no need** to specify the mediaType.
+
+```js
+import { Feder } from '@zilliz/feder';
+import * as d3 from 'd3';
+
+const domSelector = '#container';
+const filePath = [index_file_path];
+
+const feder = new Feder({
+ filePath,
+ source: 'hnswlib',
+ domSelector,
+});
+```
+
+### Step 5. Explore the index!
+
+Visualize the overview
+
+```js
+feder.overview();
+```
+
+or visualize the search process.
+
+```js
+feder.search(target_vector[, targetMediaUrl]);
+```
+
+or randomly select an vector as the target to visualize the search process.
+
+```js
+feder.searchRandTestVec();
+```
+
+More cases refer to the **test/test.js**
+
+## Blogs
+
+- [Visualize Your Approximate Nearest Neighbor Search with Feder](https://zilliz.com/blog/Visualize-Your-Approximate-Nearest-Neighbor-Search-with-Feder)
+- [Visualize Reverse Image Search with Feder](https://zilliz.com/blog/Visualize-Reverse-Image-Search-with-Feder)
+
+## Roadmap
+
+We're still in the early stages, we will support more types of anns index, and more unstructured data viewer, stay tuned.
+
+## Acknowledgments
+
+- [faiss](https://github.com/facebookresearch/faiss)
+- [hnswlib](https://github.com/nmslib/hnswlib)
+- [d3](https://github.com/d3/d3)
diff --git a/cjs/Feder-core.js b/cjs/Feder-core.js
deleted file mode 100644
index 41d7843..0000000
--- a/cjs/Feder-core.js
+++ /dev/null
@@ -1,29 +0,0 @@
-'use strict';
-/* Feder core class */
-module.exports = class Feder_core {
- constructor({ file, type, project_params }) {
- console.info('feder-core initialized')
- }
-
- get meta() {
- // define ivf-meta
- // define hnsw-meta
- }
- get id2Vector() {
- // id to vector map
- }
- search() {
- // define ivf-vis_records
- // define hnsw-vis_records
- }
- project() {
- Utils.project(...arguments)
- }
- setProjectParams() {}
- _parseFaissIVFFlat() {
- this.index = index
- }
- _parseHNSWlibHNSW() {
- this.index = index
- }
-}
diff --git a/cjs/Feder.js b/cjs/Feder.js
deleted file mode 100644
index e5f08b4..0000000
--- a/cjs/Feder.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict';
-/* Feder class */
-module.exports = class Feder {
- constructor({
- core = {},
- file = '',
- type = '',
- vis = { render: function () {} },
- domContainer,
- } = {}) {
- console.info('feder initialized');
- this.core = core;
- this.vis = vis;
- this.domContainer = domContainer;
- this.vis_records = null;
- this.meta = core.meta;
- this._render();
- }
- update() {
- // update core
-
- // render
- this._render();
- }
- search() {
- this.vis_records = core.search(target, params);
- this._render();
- }
- reset() {
- this.vis_records = null;
- this._render();
- }
- _render() {
- this.vis.render(this.meta, this.vis_records, this.domContainer);
- }
-}
diff --git a/cjs/Feder_core.js b/cjs/Feder_core.js
deleted file mode 100644
index b08096d..0000000
--- a/cjs/Feder_core.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-const Utils = require('./Utils.js');
-
-/* Feder core class */
-module.exports = class Feder_core {
- constructor({ file, type, project_params }) {
- console.info('feder-core initialized');
- }
-
- get meta() {
- // define ivf-meta
- // define hnsw-meta
- }
- get id2Vector() {
- // id to vector map
- }
- search() {
- // define ivf-vis_records
- // define hnsw-vis_records
- }
- project() {
- Utils.project(...arguments);
- }
- setProjectParams() {}
- _parseFaissIVFFlat() {
- this.index = index;
- }
- _parseHNSWlibHNSW() {
- this.index = index;
- }
-}
diff --git a/cjs/Feder_view.js b/cjs/Feder_view.js
deleted file mode 100644
index b93b5a2..0000000
--- a/cjs/Feder_view.js
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict';
-module.exports = class Feder_view {
- project() {}
- render(meta, vis_records, dom) {}
-}
diff --git a/cjs/Feder_view_HNSW.js b/cjs/Feder_view_HNSW.js
deleted file mode 100644
index 5924662..0000000
--- a/cjs/Feder_view_HNSW.js
+++ /dev/null
@@ -1,4 +0,0 @@
-'use strict';
-const Feder_view = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./Feder_view.js'));
-
-module.exports = class Feder_view_HNSW extends Feder_view {}
\ No newline at end of file
diff --git a/cjs/Feder_view_IVF.js b/cjs/Feder_view_IVF.js
deleted file mode 100644
index fc0e93e..0000000
--- a/cjs/Feder_view_IVF.js
+++ /dev/null
@@ -1,4 +0,0 @@
-'use strict';
-const Feder_view = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./Feder_view.js'));
-
-module.exports = class Feder_view_IVF extends Feder_view {}
diff --git a/cjs/Utils.js b/cjs/Utils.js
deleted file mode 100644
index a42d539..0000000
--- a/cjs/Utils.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-function project() {}
-exports.project = project
diff --git a/cjs/index.js b/cjs/index.js
deleted file mode 100644
index 125ab85..0000000
--- a/cjs/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-'use strict';
-const Feder_core = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./Feder_core.js'));
-const Feder_view = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./Feder_view.js'));
-const Feder_view_IVF = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./Feder_view_IVF.js'));
-const Feder_view_HNSW = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./Feder_view_HNSW.js'));
-const Feder = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./Feder.js'));
-const utils = require('./Utils.js');
-
-exports.Feder = Feder;
-exports.Feder_core = Feder_core;
-exports.Feder_view = Feder_view;
-exports.Feder_view_IVF = Feder_view_IVF;
-exports.Feder_view_HNSW = Feder_view_HNSW;
-exports.utils = utils;
diff --git a/cjs/package.json b/cjs/package.json
deleted file mode 100644
index 0292b99..0000000
--- a/cjs/package.json
+++ /dev/null
@@ -1 +0,0 @@
-{"type":"commonjs"}
\ No newline at end of file
diff --git a/docs/case_hnsw_overview_with_images.html b/docs/case_hnsw_overview_with_images.html
new file mode 100644
index 0000000..422b1d9
--- /dev/null
+++ b/docs/case_hnsw_overview_with_images.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+ Feder
+
+
+
+
+ ?????????
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/case_hnsw_search_with_images.html b/docs/case_hnsw_search_with_images.html
new file mode 100644
index 0000000..b1c4cba
--- /dev/null
+++ b/docs/case_hnsw_search_with_images.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+ Feder
+
+
+
+
+ ?????????
+
+
+
+
+
\ No newline at end of file
diff --git a/esm/Feder.js b/esm/Feder.js
deleted file mode 100644
index 4daad10..0000000
--- a/esm/Feder.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Feder class */
-export default class Feder {
- constructor({
- core = {},
- file = '',
- type = '',
- vis = { render: function () {} },
- domContainer,
- } = {}) {
- console.info('feder initialized');
- this.core = core;
- this.vis = vis;
- this.domContainer = domContainer;
- this.vis_records = null;
- this.meta = core.meta;
- this._render();
- }
- update() {
- // update core
-
- // render
- this._render();
- }
- search() {
- this.vis_records = core.search(target, params);
- this._render();
- }
- reset() {
- this.vis_records = null;
- this._render();
- }
- _render() {
- this.vis.render(this.meta, this.vis_records, this.domContainer);
- }
-}
diff --git a/esm/Feder_core.js b/esm/Feder_core.js
deleted file mode 100644
index 978f9b7..0000000
--- a/esm/Feder_core.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as Utils from './Utils.js';
-
-/* Feder core class */
-export default class Feder_core {
- constructor({ file, type, project_params }) {
- console.info('feder-core initialized');
- }
-
- get meta() {
- // define ivf-meta
- // define hnsw-meta
- }
- get id2Vector() {
- // id to vector map
- }
- search() {
- // define ivf-vis_records
- // define hnsw-vis_records
- }
- project() {
- Utils.project(...arguments);
- }
- setProjectParams() {}
- _parseFaissIVFFlat() {
- this.index = index;
- }
- _parseHNSWlibHNSW() {
- this.index = index;
- }
-}
diff --git a/esm/Feder_view.js b/esm/Feder_view.js
deleted file mode 100644
index 045e5af..0000000
--- a/esm/Feder_view.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export default class Feder_view {
- project() {}
- render(meta, vis_records, dom) {}
-}
diff --git a/esm/Feder_view_HNSW.js b/esm/Feder_view_HNSW.js
deleted file mode 100644
index 5257b72..0000000
--- a/esm/Feder_view_HNSW.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import Feder_view from './Feder_view.js';
-
-export default class Feder_view_HNSW extends Feder_view {}
\ No newline at end of file
diff --git a/esm/Feder_view_IVF.js b/esm/Feder_view_IVF.js
deleted file mode 100644
index c9f583f..0000000
--- a/esm/Feder_view_IVF.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import Feder_view from './Feder_view.js';
-
-export default class Feder_view_IVF extends Feder_view {}
diff --git a/esm/Utils.js b/esm/Utils.js
deleted file mode 100644
index fbdfd83..0000000
--- a/esm/Utils.js
+++ /dev/null
@@ -1 +0,0 @@
-export function project() {}
diff --git a/esm/index.js b/esm/index.js
deleted file mode 100644
index b29645e..0000000
--- a/esm/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import Feder_core from './Feder_core.js';
-import Feder_view from './Feder_view.js';
-import Feder_view_IVF from './Feder_view_IVF.js';
-import Feder_view_HNSW from './Feder_view_HNSW.js';
-import Feder from './Feder.js';
-import * as utils from './Utils.js';
-
-export {
- Feder,
- Feder_core,
- Feder_view,
- Feder_view_IVF,
- Feder_view_HNSW,
- utils,
-};
diff --git a/federjs/Feder.ts b/federjs/Feder.ts
new file mode 100644
index 0000000..e69de29
diff --git a/federjs/FederIndex/Types.ts b/federjs/FederIndex/Types.ts
new file mode 100644
index 0000000..843d314
--- /dev/null
+++ b/federjs/FederIndex/Types.ts
@@ -0,0 +1,24 @@
+export enum EDirectMapType {
+ NoMap = 0, // default
+ Array, // sequential ids (only for add, no add_with_ids)
+ Hashtable, // arbitrary ids
+}
+
+export interface TDirectMap {
+ dmType: EDirectMapType;
+ size: number;
+}
+
+export enum EMetricType {
+ METRIC_INNER_PRODUCT = 0, ///< maximum inner product search
+ METRIC_L2 = 1, ///< squared L2 search
+ METRIC_L1 = 2, ///< L1 (aka cityblock)
+ METRIC_Linf = 3, ///< infinity distance
+ METRIC_Lp = 4, ///< L_p distance, p is given by a faiss::Index
+ /// metric_arg
+
+ /// some additional metrics defined in scipy.spatial.distance
+ METRIC_Canberra = 20,
+ METRIC_BrayCurtis = 21,
+ METRIC_JensenShannon = 22,
+}
diff --git a/federjs/FederIndex/Utils.ts b/federjs/FederIndex/Utils.ts
new file mode 100644
index 0000000..d26f85d
--- /dev/null
+++ b/federjs/FederIndex/Utils.ts
@@ -0,0 +1,3 @@
+export const uint8toChars = (data: number[]) => {
+ return String.fromCharCode(...data);
+};
diff --git a/federjs/FederIndex/index.ts b/federjs/FederIndex/index.ts
new file mode 100644
index 0000000..576c3f3
--- /dev/null
+++ b/federjs/FederIndex/index.ts
@@ -0,0 +1,32 @@
+import { ESourceType, EIndexType, TVec, TSearchParams } from 'Types';
+
+import { Parser } from './parser';
+import { SearchHandler } from './searchHandler';
+
+export class FederIndex {
+ private index: any;
+ indexType: EIndexType;
+ private parser: Parser;
+ private searchHandler: SearchHandler;
+ constructor(sourceType: ESourceType) {
+ this.parser = new Parser(sourceType);
+ }
+ initByArrayBuffer(arrayBuffer: ArrayBuffer) {
+ this.index = this.parser.parse(arrayBuffer);
+ this.indexType = this.index.indexType;
+ this.searchHandler = new SearchHandler(this.indexType);
+ }
+ async getIndexType() {
+ return this.indexType;
+ }
+ async getIndexMeta() {
+ return '';
+ }
+ async getSearchRecords(target: TVec, searchParams: TSearchParams) {
+ return this.searchHandler.search({
+ index: this.index,
+ target,
+ params: searchParams,
+ });
+ }
+}
diff --git a/federjs/FederIndex/parser/FileReader.ts b/federjs/FederIndex/parser/FileReader.ts
new file mode 100644
index 0000000..d25e570
--- /dev/null
+++ b/federjs/FederIndex/parser/FileReader.ts
@@ -0,0 +1,77 @@
+export default class FileReader {
+ data: ArrayBuffer;
+ dataview: DataView;
+ p: number;
+ constructor(arrayBuffer: ArrayBuffer) {
+ this.data = arrayBuffer;
+ this.dataview = new DataView(arrayBuffer);
+
+ this.p = 0;
+ }
+ get isEmpty() {
+ return this.p >= this.data.byteLength;
+ }
+ readInt8() {
+ const int8 = this.dataview.getInt8(this.p);
+ this.p += 1;
+ return int8;
+ }
+ readUint8() {
+ const uint8 = this.dataview.getUint8(this.p);
+ this.p += 1;
+ return uint8;
+ }
+ readBool() {
+ const int8 = this.readInt8();
+ return Boolean(int8);
+ }
+ readUint16() {
+ const uint16 = this.dataview.getUint16(this.p, true);
+ this.p += 2;
+ return uint16;
+ }
+ readInt32() {
+ const int32 = this.dataview.getInt32(this.p, true);
+ this.p += 4;
+ return int32;
+ }
+ readUint32() {
+ const uint32 = this.dataview.getUint32(this.p, true);
+ this.p += 4;
+ return uint32;
+ }
+ readUint64() {
+ const left = this.readUint32();
+ const right = this.readUint32();
+ const int64 = left + Math.pow(2, 32) * right;
+ if (!Number.isSafeInteger(int64))
+ console.warn(int64, "Exceeds MAX_SAFE_INTEGER. Precision may be lost");
+ return int64;
+ }
+ readFloat64() {
+ const float64 = this.dataview.getFloat64(this.p, true);
+ this.p += 8;
+ return float64;
+ }
+ readDouble() {
+ return this.readFloat64();
+ }
+
+ readUint32Array(n) {
+ const res = Array(n)
+ .fill(0)
+ .map((_) => this.readUint32());
+ return res;
+ }
+ readFloat32Array(n) {
+ const res = new Float32Array(this.data.slice(this.p, this.p + n * 4));
+ this.p += n * 4;
+ return res;
+ }
+ readUint64Array(n) {
+ const res = Array(n)
+ .fill(0)
+ .map((_) => this.readUint64());
+ return res;
+ }
+}
diff --git a/federjs/FederIndex/parser/faissParser/FaissFileReader.ts b/federjs/FederIndex/parser/faissParser/FaissFileReader.ts
new file mode 100644
index 0000000..b7123fc
--- /dev/null
+++ b/federjs/FederIndex/parser/faissParser/FaissFileReader.ts
@@ -0,0 +1,19 @@
+import FileReader from "../FileReader";
+import { uint8toChars } from "FederIndex/Utils";
+
+export default class FaissFileReader extends FileReader {
+ constructor(arrayBuffer) {
+ super(arrayBuffer);
+ }
+ readH() {
+ const uint8Array = Array(4)
+ .fill(0)
+ .map((_) => this.readUint8());
+ const h = uint8toChars(uint8Array);
+ return h;
+ }
+ readDummy() {
+ const dummy = this.readUint64();
+ return dummy;
+ }
+}
diff --git a/federjs/FederIndex/parser/faissParser/index.js b/federjs/FederIndex/parser/faissParser/index.js
new file mode 100644
index 0000000..3b810d2
--- /dev/null
+++ b/federjs/FederIndex/parser/faissParser/index.js
@@ -0,0 +1,51 @@
+import FaissFileReader from './FaissFileReader.js';
+import readInvertedLists from './readInvertedLists.js';
+import readDirectMap from './readDirectMap.js';
+import readIndexHeader from './readIndexHeader.js';
+
+import { generateArray } from 'Utils';
+import { INDEX_TYPE, IndexHeader } from 'Types';
+
+const readIvfHeader = (reader, index) => {
+ readIndexHeader(reader, index);
+
+ index.nlist = reader.readUint64();
+ index.nprobe = reader.readUint64();
+
+ index.childIndex = readIndex(reader);
+
+ readDirectMap(reader, index);
+};
+
+const readXbVectors = (reader, index) => {
+ index.codeSize = reader.readUint64();
+
+ index.vectors = generateArray(index.ntotal).map((_) =>
+ reader.readFloat32Array(index.d)
+ );
+};
+
+const readIndex = (reader) => {
+ const index = {};
+ index.h = reader.readH();
+ if (index.h === IndexHeader.IVFFlat) {
+ index.indexType = INDEX_TYPE.ivf_flat;
+ readIvfHeader(reader, index);
+ readInvertedLists(reader, index);
+ } else if (index.h === IndexHeader.FlatIR || index.h === IndexHeader.FlatL2) {
+ index.indexType = INDEX_TYPE.flat;
+ readIndexHeader(reader, index);
+ readXbVectors(reader, index);
+ } else {
+ console.warn('[index type] not supported -', index.h);
+ }
+ return index;
+};
+
+const faissIndexParser = (arraybuffer) => {
+ const faissFileReader = new FaissFileReader(arraybuffer);
+ const index = readIndex(faissFileReader);
+ return index;
+};
+
+export default faissIndexParser;
diff --git a/federjs/FederIndex/parser/faissParser/readDirectMap.ts b/federjs/FederIndex/parser/faissParser/readDirectMap.ts
new file mode 100644
index 0000000..34598d9
--- /dev/null
+++ b/federjs/FederIndex/parser/faissParser/readDirectMap.ts
@@ -0,0 +1,24 @@
+import { EDirectMapType, TDirectMap } from "FederIndex/Types";
+
+const checkDmType = (dmType: EDirectMapType | number) => {
+ if (dmType !== EDirectMapType.NoMap) {
+ console.warn("[directmap_type] only support NoMap.");
+ }
+};
+
+const checkDmSize = (dmSize: number) => {
+ if (dmSize !== 0) {
+ console.warn("[directmap_size] should be 0.");
+ }
+};
+
+const readDirectMap = (reader, index) => {
+ const directMap = {} as TDirectMap;
+ directMap.dmType = reader.readUint8();
+ checkDmType(directMap.dmType);
+ directMap.size = reader.readUint64();
+ checkDmSize(directMap.size);
+ index.directMap = directMap;
+};
+
+export default readDirectMap;
diff --git a/federjs/FederIndex/parser/faissParser/readIndexHeader.ts b/federjs/FederIndex/parser/faissParser/readIndexHeader.ts
new file mode 100644
index 0000000..3ad36ef
--- /dev/null
+++ b/federjs/FederIndex/parser/faissParser/readIndexHeader.ts
@@ -0,0 +1,39 @@
+import { EMetricType } from 'FederIndex/Types';
+
+const checkMetricType = (metricType: EMetricType | number) => {
+ if (
+ metricType !== EMetricType.METRIC_L2 &&
+ metricType !== EMetricType.METRIC_INNER_PRODUCT
+ ) {
+ console.warn('[metric_type] only support l2 and inner_product.');
+ }
+};
+
+const checkDummy = (dummy_1, dummy_2) => {
+ if (dummy_1 !== dummy_2) {
+ console.warn('[dummy] not equal.', dummy_1, dummy_2);
+ }
+};
+
+const checkIsTrained = (isTrained) => {
+ if (!isTrained) {
+ console.warn('[is_trained] should be trained.', isTrained);
+ }
+};
+
+const readIndexHeader = (reader, index) => {
+ index.d = reader.readUint32();
+ index.ntotal = reader.readUint64();
+
+ const dummy_1 = reader.readDummy();
+ const dummy_2 = reader.readDummy();
+ checkDummy(dummy_1, dummy_2);
+
+ index.isTrained = reader.readBool();
+ checkIsTrained(index.isTrained);
+
+ index.metricType = reader.readUint32();
+ checkMetricType(index.metricType);
+};
+
+export default readIndexHeader;
diff --git a/federjs/FederIndex/parser/faissParser/readInvertedLists.js b/federjs/FederIndex/parser/faissParser/readInvertedLists.js
new file mode 100644
index 0000000..d2b0ec1
--- /dev/null
+++ b/federjs/FederIndex/parser/faissParser/readInvertedLists.js
@@ -0,0 +1,47 @@
+import { generateArray } from 'Utils';
+
+const checkInvH = (h) => {
+ if (h !== 'ilar') {
+ console.warn('[invlists h] not ilar.', h);
+ }
+};
+
+const checkInvListType = (listType) => {
+ if (listType !== 'full') {
+ console.warn('[inverted_lists list_type] only support full.', listType);
+ }
+};
+
+const readArrayInvLists = (reader, invlists) => {
+ invlists.listType = reader.readH();
+ checkInvListType(invlists.listType);
+
+ invlists.listSizesSize = reader.readUint64();
+ invlists.listSizes = generateArray(invlists.listSizesSize).map((_) =>
+ reader.readUint64()
+ );
+
+ const data = [];
+ generateArray(invlists.listSizesSize).forEach((_, i) => {
+ const vectors = generateArray(invlists.listSizes[i]).map((_) =>
+ reader.readFloat32Array(invlists.codeSize / 4)
+ );
+ const ids = reader.readUint64Array(invlists.listSizes[i]);
+ data.push({ ids, vectors });
+ });
+ invlists.data = data;
+};
+
+export const readInvertedLists = (reader, index) => {
+ const invlists = {};
+ invlists.h = reader.readH();
+ checkInvH(invlists.h);
+ invlists.nlist = reader.readUint64();
+ invlists.codeSize = reader.readUint64();
+
+ readArrayInvLists(reader, invlists);
+
+ index.invlists = invlists;
+};
+
+export default readInvertedLists;
diff --git a/federjs/FederIndex/parser/faissParser/types.ts b/federjs/FederIndex/parser/faissParser/types.ts
new file mode 100644
index 0000000..42b7774
--- /dev/null
+++ b/federjs/FederIndex/parser/faissParser/types.ts
@@ -0,0 +1,10 @@
+export enum EDirectMapType {
+ NoMap = 0, // default
+ Array, // sequential ids (only for add, no add_with_ids)
+ Hashtable, // arbitrary ids
+}
+
+export interface TDirectMap {
+ dmType: EDirectMapType;
+ size: number;
+}
diff --git a/federjs/FederIndex/parser/hnswlibParser/HNSWlibFileReader.ts b/federjs/FederIndex/parser/hnswlibParser/HNSWlibFileReader.ts
new file mode 100644
index 0000000..eb9b41e
--- /dev/null
+++ b/federjs/FederIndex/parser/hnswlibParser/HNSWlibFileReader.ts
@@ -0,0 +1,16 @@
+import FileReader from "../FileReader";
+
+export default class HNSWlibFileReader extends FileReader {
+ constructor(arrayBuffer: ArrayBuffer) {
+ super(arrayBuffer);
+ }
+ readIsDeleted() {
+ return this.readUint8();
+ }
+ readIsReused() {
+ return this.readUint8();
+ }
+ readLevelOCount() {
+ return this.readUint16();
+ }
+}
diff --git a/federjs/FederIndex/parser/hnswlibParser/index.ts b/federjs/FederIndex/parser/hnswlibParser/index.ts
new file mode 100644
index 0000000..6cd1985
--- /dev/null
+++ b/federjs/FederIndex/parser/hnswlibParser/index.ts
@@ -0,0 +1,102 @@
+import HNSWlibFileReader from "./HNSWlibFileReader.js";
+import { EIndexType } from "Types";
+import { TIndexStructureHnswDetail, TIndexStructureHnsw } from "./types";
+export { TIndexStructureHnsw } from "./types";
+
+export const hnswlibIndexParser = (arrayBuffer: ArrayBuffer) => {
+ const reader = new HNSWlibFileReader(arrayBuffer);
+ const index = {} as TIndexStructureHnswDetail;
+ index.offsetLevel0_ = reader.readUint64();
+ index.max_elements_ = reader.readUint64();
+ index.cur_element_count = reader.readUint64();
+ // index.ntotal = index.cur_element_count; // consistent with Faiss
+ index.size_data_per_element_ = reader.readUint64();
+ index.label_offset_ = reader.readUint64();
+ index.offsetData_ = reader.readUint64();
+ index.dim = (index.size_data_per_element_ - index.offsetData_ - 8) / 4;
+
+ index.maxlevel_ = reader.readUint32();
+ index.enterpoint_node_ = reader.readUint32();
+ index.maxM_ = reader.readUint64();
+ index.maxM0_ = reader.readUint64();
+ index.M = reader.readUint64();
+
+ index.mult_ = reader.readFloat64();
+ index.ef_construction_ = reader.readUint64();
+
+ index.size_links_per_element_ = index.maxM_ * 4 + 4;
+ index.size_links_level0_ = index.maxM0_ * 4 + 4;
+ index.revSize_ = 1.0 / index.mult_;
+ index.ef_ = 10;
+
+ read_data_level0_memory_(reader, index);
+
+ const linkListSizes = [];
+ const linkLists_ = [];
+ for (let i = 0; i < index.cur_element_count; i++) {
+ const linkListSize = reader.readUint32();
+ linkListSizes.push(linkListSize);
+ if (linkListSize === 0) {
+ linkLists_[i] = [];
+ } else {
+ const levelCount = linkListSize / 4 / (index.maxM_ + 1);
+ linkLists_[i] = Array(levelCount)
+ .fill(0)
+ .map((_) => reader.readUint32Array(index.maxM_ + 1))
+ .map((linkLists) => linkLists.slice(1, linkLists[0] + 1));
+ // .filter((a) => a.length > 0);
+ }
+ }
+ index.linkListSizes = linkListSizes;
+ index.linkLists_ = linkLists_;
+
+ console.assert(
+ reader.isEmpty,
+ "HNSWlib Parser Failed. Not empty when the parser completes."
+ );
+
+ return {
+ indexType: EIndexType.hnsw,
+ ntotal: index.cur_element_count,
+ vectors: index.vectors,
+ maxLevel: index.maxlevel_,
+ linkLists_level0_count: index.linkLists_level0_count,
+ linkLists_level_0: index.linkLists_level0,
+ linkLists_levels: index.linkLists_,
+ enterPoint: index.enterpoint_node_,
+ labels: index.externalLabel,
+ isDeleted: index.isDeleted,
+ numDeleted: index.num_deleted_,
+ M: index.M,
+ ef_construction: index.ef_construction_,
+ } as TIndexStructureHnsw;
+};
+
+const read_data_level0_memory_ = (
+ reader: HNSWlibFileReader,
+ index: TIndexStructureHnswDetail
+) => {
+ // size_data = links_level0[M0 + 1] * 4 + vector[dim * 4] * 4 + label[1] * 8;
+ const isDeleted = [];
+ const linkLists_level0_count = [];
+ const linkLists_level0 = [];
+ const vectors = [];
+ const externalLabel = [];
+ for (let i = 0; i < index.cur_element_count; i++) {
+ linkLists_level0_count.push(reader.readLevelOCount());
+ isDeleted.push(reader.readIsDeleted());
+ reader.readIsReused(); // Unknown use.
+ linkLists_level0.push(reader.readUint32Array(index.maxM0_));
+ vectors.push(reader.readFloat32Array(index.dim));
+ externalLabel.push(reader.readUint64());
+ // console.log(isDeleted, linkLists_level0_count);
+ }
+ index.isDeleted = isDeleted;
+ index.num_deleted_ = isDeleted.reduce((acc, cur) => acc + cur, 0);
+ index.linkLists_level0_count = linkLists_level0_count;
+ index.linkLists_level0 = linkLists_level0;
+ index.vectors = vectors;
+ index.externalLabel = externalLabel;
+};
+
+export default hnswlibIndexParser;
diff --git a/federjs/FederIndex/parser/hnswlibParser/types.ts b/federjs/FederIndex/parser/hnswlibParser/types.ts
new file mode 100644
index 0000000..567c21f
--- /dev/null
+++ b/federjs/FederIndex/parser/hnswlibParser/types.ts
@@ -0,0 +1,48 @@
+import { TVec, TId, EIndexType } from "Types";
+export type THnswlibLinkList = TId[];
+export type THnswlibLinkLists = THnswlibLinkList[];
+
+export interface TIndexStructureHnswDetail {
+ offsetLevel0_: number;
+ max_elements_: number;
+ cur_element_count: number;
+ size_data_per_element_: number;
+ label_offset_: number;
+ offsetData_: number;
+ dim: number;
+ maxlevel_: number;
+ enterpoint_node_: TId;
+ maxM_: number;
+ maxM0_: number;
+ M: number;
+ mult_: number;
+ ef_construction_: number;
+ size_links_per_element_: number;
+ size_links_level0_: number;
+ revSize_: number;
+ ef_: number;
+ linkListSizes: number[];
+ linkLists_: THnswlibLinkLists[];
+ vectors: TVec[];
+ linkLists_level0_count: number[];
+ linkLists_level0: THnswlibLinkLists;
+ externalLabel: TId[];
+ isDeleted: number[];
+ num_deleted_: number;
+}
+
+export interface TIndexStructureHnsw {
+ indexType: EIndexType;
+ ntotal: number;
+ vectors: TVec[];
+ maxLevel: number;
+ linkLists_level0_count: number[];
+ linkLists_level_0: THnswlibLinkLists;
+ linkLists_levels: THnswlibLinkLists[];
+ enterPoint: TId;
+ labels: TId[];
+ isDeleted: number[];
+ numDeleted: number;
+ M: number;
+ ef_construction: number;
+}
diff --git a/federjs/FederIndex/parser/index.ts b/federjs/FederIndex/parser/index.ts
new file mode 100644
index 0000000..695093c
--- /dev/null
+++ b/federjs/FederIndex/parser/index.ts
@@ -0,0 +1,17 @@
+import { TIndexStructureHnsw, hnswlibIndexParser } from './hnswlibParser';
+import { ESourceType } from 'Types';
+
+const parserMap = {
+ [ESourceType.hnswlib]: hnswlibIndexParser,
+ // [ESourceType.faiss]: ,
+ // [ESourceType.milvus]: ,
+};
+
+export type TIndexParseFunc = (arrayBuffer: ArrayBuffer) => TIndexStructureHnsw;
+
+export class Parser {
+ parse: TIndexParseFunc;
+ constructor(indexType: ESourceType) {
+ this.parse = parserMap[indexType];
+ }
+}
diff --git a/federjs/FederIndex/searchHandler/hnswSearch/index.ts b/federjs/FederIndex/searchHandler/hnswSearch/index.ts
new file mode 100644
index 0000000..f4285da
--- /dev/null
+++ b/federjs/FederIndex/searchHandler/hnswSearch/index.ts
@@ -0,0 +1,87 @@
+import { getDisFunc } from "Utils/distFunc";
+import { EMetricType, TSearchParams } from "Types";
+import { TSearchRecordsHnsw } from "Types/searchRecords";
+import searchLevelO from "./searchLevel0";
+
+export const hnswlibHNSWSearch = ({
+ index,
+ target,
+ params = {} as TSearchParams,
+}) => {
+ const { ef = 10, k = 8, metricType = EMetricType.METRIC_L2 } = params;
+ const disfunc = getDisFunc(metricType);
+
+ let topkResults = [];
+ const vis_records_all = [];
+
+ const {
+ enterPoint,
+ vectors,
+ maxLevel,
+ linkLists_levels,
+ linkLists_level_0,
+ numDeleted,
+ labels,
+ isDeleted,
+ } = index;
+
+ let curNodeId = enterPoint;
+ let curDist = disfunc(vectors[curNodeId], target);
+
+ for (let level = maxLevel; level > 0; level--) {
+ const vis_records = [];
+ vis_records.push([labels[curNodeId], labels[curNodeId], curDist]);
+ let changed = true;
+ while (changed) {
+ changed = false;
+
+ const curlinks = linkLists_levels[curNodeId][level - 1];
+
+ curlinks.forEach((candidateId) => {
+ const dist = disfunc(vectors[candidateId], target);
+ vis_records.push([labels[curNodeId], labels[candidateId], dist]);
+ if (dist < curDist) {
+ curDist = dist;
+ curNodeId = candidateId;
+ changed = true;
+ }
+ });
+ }
+ vis_records_all.push(vis_records);
+ }
+
+ const hasDeleted = numDeleted > 0;
+ const { top_candidates, vis_records_level_0 } = searchLevelO({
+ ep_id: curNodeId,
+ target,
+ vectors,
+ ef: Math.max(ef, k),
+ hasDeleted,
+ isDeleted,
+ linkLists_level_0,
+ disfunc,
+ labels,
+ });
+ vis_records_all.push(vis_records_level_0);
+
+ while (top_candidates.size > k) {
+ top_candidates.pop();
+ }
+
+ while (top_candidates.size > 0) {
+ const res = top_candidates.pop();
+ topkResults.push({
+ id: labels[res[1]],
+ internalId: res[1],
+ dis: -res[0],
+ });
+ }
+
+ topkResults = topkResults.reverse();
+
+ return {
+ searchRecords: vis_records_all,
+ topkResults,
+ searchParams: { k, ef },
+ } as TSearchRecordsHnsw;
+};
diff --git a/federjs/FederIndex/searchHandler/hnswSearch/searchLevel0.ts b/federjs/FederIndex/searchHandler/hnswSearch/searchLevel0.ts
new file mode 100644
index 0000000..a835758
--- /dev/null
+++ b/federjs/FederIndex/searchHandler/hnswSearch/searchLevel0.ts
@@ -0,0 +1,82 @@
+
+import PriorityQueue from 'Utils/PriorityQueue';
+
+export const searchLevelO = ({
+ ep_id,
+ target,
+ vectors,
+ ef,
+ isDeleted,
+ hasDeleted,
+ linkLists_level_0,
+ disfunc,
+ labels,
+}) => {
+ const top_candidates = new PriorityQueue([], (d) => d[0]);
+ const candidates = new PriorityQueue([], (d) => d[0]);
+ const vis_records_level_0 = [];
+
+ const visited = new Set();
+
+ let lowerBound;
+ if (!hasDeleted || !isDeleted[ep_id]) {
+ const dist = disfunc(vectors[ep_id], target);
+ lowerBound = dist;
+ top_candidates.add([-dist, ep_id]);
+ candidates.add([dist, ep_id]);
+ } else {
+ lowerBound = 9999999;
+ candidates.add([lowerBound, ep_id]);
+ }
+
+ visited.add(ep_id);
+ vis_records_level_0.push([labels[ep_id], labels[ep_id], lowerBound]);
+
+ while (!candidates.isEmpty) {
+ const curNodePair = candidates.top;
+ if (
+ curNodePair[0] > lowerBound &&
+ (top_candidates.size === ef || !hasDeleted)
+ ) {
+ break;
+ }
+ candidates.pop();
+
+ const curNodeId = curNodePair[1];
+ const curLinks = linkLists_level_0[curNodeId];
+
+ curLinks.forEach((candidateId) => {
+ if (!visited.has(candidateId)) {
+ visited.add(candidateId);
+
+ const dist = disfunc(vectors[candidateId], target);
+ vis_records_level_0.push([
+ labels[curNodeId],
+ labels[candidateId],
+ dist,
+ ]);
+
+ if (top_candidates.size < ef || lowerBound > dist) {
+ candidates.add([dist, candidateId]);
+
+ if (!hasDeleted || !isDeleted(candidateId)) {
+ top_candidates.add([-dist, candidateId]);
+ }
+
+ if (top_candidates.size > ef) {
+ top_candidates.pop();
+ }
+
+ if (!top_candidates.isEmpty) {
+ lowerBound = -top_candidates.top[0];
+ }
+ }
+ } else {
+ vis_records_level_0.push([labels[curNodeId], labels[candidateId], -1]);
+ }
+ });
+ }
+ return { top_candidates, vis_records_level_0 };
+};
+
+export default searchLevelO;
\ No newline at end of file
diff --git a/federjs/FederIndex/searchHandler/index.ts b/federjs/FederIndex/searchHandler/index.ts
new file mode 100644
index 0000000..3a61960
--- /dev/null
+++ b/federjs/FederIndex/searchHandler/index.ts
@@ -0,0 +1,24 @@
+import { hnswlibHNSWSearch } from "./hnswSearch";
+import { EIndexType, TVec, TSearchParams } from "Types";
+import { TSearchRecords } from "Types/searchRecords";
+
+const searchFuncMap = {
+ [EIndexType.hnsw]: hnswlibHNSWSearch,
+};
+
+export interface TSearchVectorAndParams {
+ index: any;
+ target: TVec;
+ params: TSearchParams;
+}
+
+export type TSearchFunc = (
+ searchParams: TSearchVectorAndParams
+) => TSearchRecords;
+
+export class SearchHandler {
+ search: TSearchFunc;
+ constructor(indexType: EIndexType) {
+ this.search = searchFuncMap[indexType];
+ }
+}
diff --git a/federjs/FederLayout/FederLayoutHandler.ts b/federjs/FederLayout/FederLayoutHandler.ts
new file mode 100644
index 0000000..a83d69b
--- /dev/null
+++ b/federjs/FederLayout/FederLayoutHandler.ts
@@ -0,0 +1,12 @@
+import { EViewType, TLayoutParams } from 'Types';
+import { TSearchRecords } from 'Types/searchRecords';
+import { TAcitonData, TVisDataAll } from 'Types/visData';
+
+export interface TFederLayoutHandler {
+ computeOverviewVisData(): TVisDataAll;
+ computeSearchViewVisData(
+ viewType: EViewType,
+ searchRecords: TSearchRecords,
+ layoutParams: TLayoutParams
+ ): TVisDataAll;
+}
diff --git a/federjs/FederLayout/codeStructure.ts b/federjs/FederLayout/codeStructure.ts
new file mode 100644
index 0000000..22d82ac
--- /dev/null
+++ b/federjs/FederLayout/codeStructure.ts
@@ -0,0 +1,65 @@
+// service
+
+import { EActionType, EViewType, EIndexType, TLayoutParams } from 'Types';
+import { TVisDataAll, TAcitonData } from 'Types/visData';
+import { FederIndex } from 'FederIndex';
+
+export class FederLayout {
+ indexType: EIndexType;
+ federIndex: FederIndex;
+ constructor(federIndex: FederIndex) {
+ this.federIndex = federIndex;
+ this.indexType = this.federIndex.indexType;
+ }
+
+ async getVisData({
+ actionType,
+ actionData,
+ viewType,
+ layoutParams,
+ }: {
+ actionType: EActionType;
+ actionData: TAcitonData;
+ viewType: EViewType;
+ layoutParams: TLayoutParams;
+ }): Promise {
+ const visData =
+ actionType === EActionType.search
+ ? await this.getSearchViewVisData({
+ actionData,
+ viewType,
+ layoutParams,
+ })
+ : await this.getOverviewVisData({ viewType, layoutParams });
+
+ return {
+ indexType: this.indexType,
+ actionType,
+ actionData,
+ viewType,
+ visData,
+ };
+ }
+
+ async getSearchViewVisData({
+ actionData,
+ viewType,
+ layoutParams,
+ }: {
+ actionData: TAcitonData;
+ viewType: EViewType;
+ layoutParams: TLayoutParams;
+ }) {}
+
+ async getOverviewVisData({
+ viewType,
+ layoutParams,
+ }: {
+ viewType: EViewType;
+ layoutParams: TLayoutParams;
+ }) {}
+}
+
+export class FederLayoutHnsw extends FederLayout {
+ getSearchViewVisData;
+}
diff --git a/federjs/FederLayout/index.ts b/federjs/FederLayout/index.ts
new file mode 100644
index 0000000..bd5b3a3
--- /dev/null
+++ b/federjs/FederLayout/index.ts
@@ -0,0 +1,73 @@
+import { FederIndex } from 'FederIndex';
+import {
+ EViewType,
+ EActionType,
+ EIndexType,
+ TVec,
+ TSearchParams,
+ TLayoutParams,
+} from 'Types';
+import { TVisDataAll, TAcitonData } from 'Types/visData';
+import { TFederLayoutHandler } from './FederLayoutHandler';
+import FederLayoutHnsw from './visDataHandler/hnsw';
+
+const federLayoutHandlerMap = {
+ [EIndexType.hnsw]: FederLayoutHnsw,
+ // [EIndexType.ivfflat]: FederLayoutIVFFlat;
+};
+
+export class FederLayout {
+ private federIndex: FederIndex;
+ indexType: EIndexType;
+ private federLayoutHandler: TFederLayoutHandler;
+ constructor(federIndex: FederIndex) {
+ this.federIndex = federIndex;
+ this.indexType = federIndex.indexType;
+ this.federLayoutHandler = new federLayoutHandlerMap[federIndex.indexType]();
+ }
+
+ async getOverviewVisData(viewType: EViewType, layoutParams: TLayoutParams) {
+ return {};
+ }
+
+ async getSearchViewVisData(
+ actionData: TAcitonData,
+ viewType: EViewType,
+ layoutParams: TLayoutParams
+ ) {
+ const searchRecords = await this.federIndex.getSearchRecords(
+ actionData.target,
+ actionData.searchParams
+ );
+ console.log('searchRecords', searchRecords);
+ return this.federLayoutHandler.computeSearchViewVisData(
+ viewType,
+ searchRecords,
+ layoutParams
+ );
+ }
+
+ async getVisData({
+ actionType,
+ actionData,
+ viewType = EViewType.default,
+ layoutParams = {},
+ }: {
+ actionType: EActionType;
+ actionData?: TAcitonData;
+ viewType?: EViewType;
+ layoutParams?: TLayoutParams;
+ }) {
+ const visData =
+ actionType === EActionType.search
+ ? await this.getSearchViewVisData(actionData, viewType, layoutParams)
+ : await this.getOverviewVisData(viewType, layoutParams);
+ return {
+ indexType: this.indexType,
+ actionType,
+ actionData,
+ viewType,
+ visData,
+ } as TVisDataAll;
+ }
+}
diff --git a/federjs/FederLayout/projector/index.ts b/federjs/FederLayout/projector/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/federjs/FederLayout/visDataHandler/hnsw/index.ts b/federjs/FederLayout/visDataHandler/hnsw/index.ts
new file mode 100644
index 0000000..2f7b0a4
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/index.ts
@@ -0,0 +1,57 @@
+import { TFederLayoutHandler } from 'FederLayout/FederLayoutHandler';
+import { TVec, TSearchParams, EViewType, TLayoutParams } from 'Types';
+import { TSearchRecords, TSearchRecordsHnsw } from 'Types/searchRecords';
+import { TVisData } from 'Types/visData';
+
+import { searchViewLayoutHandler } from './search';
+import { searchViewLayoutHandler3d } from './search/hnsw3d';
+
+const searchViewLayoutHandlerMap = {
+ [EViewType.default]: searchViewLayoutHandler,
+ [EViewType.hnsw3d]: searchViewLayoutHandler3d,
+};
+
+const defaultHnswLayoutParams = {
+ padding: [80, 200, 60, 220],
+ forceTime: 3000,
+ layerDotNum: 20,
+ shortenLineD: 8,
+ overviewLinkLineWidth: 2,
+ reachableLineWidth: 3,
+ shortestPathLineWidth: 4,
+ ellipseRation: 1.4,
+ shadowBlur: 4,
+ mouse2nodeBias: 3,
+ highlightRadiusExt: 0.5,
+ targetR: 3,
+ searchViewNodeBasicR: 1.5,
+ searchInterLevelTime: 300,
+ searchIntraLevelTime: 100,
+ HoveredPanelLine_1_x: 15,
+ HoveredPanelLine_1_y: -25,
+ HoveredPanelLine_2_x: 30,
+ hoveredPanelLineWidth: 2,
+ forceIterations: 100,
+ targetOrigin: [0, 0],
+};
+
+export default class FederLayoutHnsw implements TFederLayoutHandler {
+ constructor() {}
+
+ computeOverviewVisData() {
+ return {} as TVisData;
+ }
+
+ computeSearchViewVisData(
+ viewType: EViewType,
+ searchRecords: TSearchRecordsHnsw,
+ layoutParams: TLayoutParams
+ ) {
+ const searchViewLayoutHandler = searchViewLayoutHandlerMap[viewType];
+
+ return searchViewLayoutHandler(
+ searchRecords,
+ Object.assign({}, defaultHnswLayoutParams, layoutParams)
+ ) as TVisData;
+ }
+}
diff --git a/federjs/FederLayout/visDataHandler/hnsw/search/computeSearchViewTransition.ts b/federjs/FederLayout/visDataHandler/hnsw/search/computeSearchViewTransition.ts
new file mode 100644
index 0000000..949edb3
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/search/computeSearchViewTransition.ts
@@ -0,0 +1,86 @@
+import {
+ getNodeIdWithLevel,
+ getLinkIdWithLevel,
+ getEntryLinkIdWithLevel,
+} from './utils';
+
+import { EHnswLinkType } from 'Types';
+
+export const computeSearchViewTransition = ({
+ linksLevels,
+ entryNodesLevels,
+ interLevelGap = 1000,
+ intraLevelGap = 300,
+}) => {
+ let currentTime = 0;
+ const targetShowTime = [];
+ const nodeShowTime = {};
+ const linkShowTime = {};
+
+ let isPreLinkImportant = true;
+ let isSourceChanged = true;
+ let preSourceIdWithLevel = '';
+ for (let level = linksLevels.length - 1; level >= 0; level--) {
+ const links = linksLevels[level];
+ if (links.length === 0) {
+ const sourceId = entryNodesLevels[level].id;
+ const sourceIdWithLevel = getNodeIdWithLevel(sourceId, level);
+ nodeShowTime[sourceIdWithLevel] = currentTime;
+ } else {
+ links.forEach((link) => {
+ const sourceId = link.source.id;
+ const targetId = link.target.id;
+ const sourceIdWithLevel = getNodeIdWithLevel(sourceId, level);
+ const targetIdWithLevel = getNodeIdWithLevel(targetId, level);
+ const linkIdWithLevel = getLinkIdWithLevel(sourceId, targetId, level);
+ const isCurrentLinkImportant =
+ link.type === EHnswLinkType.Searched ||
+ link.type === EHnswLinkType.Fine;
+ isSourceChanged = preSourceIdWithLevel !== sourceIdWithLevel;
+
+ const isSourceEntry = !(sourceIdWithLevel in nodeShowTime);
+ if (isSourceEntry) {
+ if (level < linksLevels.length - 1) {
+ const entryLinkIdWithLevel = getEntryLinkIdWithLevel(
+ sourceId,
+ level
+ );
+ linkShowTime[entryLinkIdWithLevel] = currentTime;
+ const targetLinkIdWithLevel = getEntryLinkIdWithLevel(
+ 'target',
+ level
+ );
+ linkShowTime[targetLinkIdWithLevel] = currentTime;
+ currentTime += interLevelGap;
+ isPreLinkImportant = true;
+ }
+ targetShowTime[level] = currentTime;
+ nodeShowTime[sourceIdWithLevel] = currentTime;
+ }
+
+ // something wrong
+ if (isPreLinkImportant || isCurrentLinkImportant || isSourceChanged) {
+ currentTime += intraLevelGap;
+ } else {
+ currentTime += intraLevelGap * 0.5;
+ }
+
+ linkShowTime[linkIdWithLevel] = currentTime;
+
+ if (!(targetIdWithLevel in nodeShowTime)) {
+ nodeShowTime[targetIdWithLevel] = currentTime += intraLevelGap;
+ }
+
+ isPreLinkImportant = isCurrentLinkImportant;
+ preSourceIdWithLevel = sourceIdWithLevel;
+ });
+ }
+
+ currentTime += intraLevelGap;
+ isPreLinkImportant = true;
+ isSourceChanged = true;
+ }
+ return { targetShowTime, nodeShowTime, linkShowTime, duration: currentTime };
+};
+
+export default computeSearchViewTransition;
diff --git a/federjs/FederLayout/visDataHandler/hnsw/search/forceSearchView.ts b/federjs/FederLayout/visDataHandler/hnsw/search/forceSearchView.ts
new file mode 100644
index 0000000..5fe3197
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/search/forceSearchView.ts
@@ -0,0 +1,83 @@
+import * as d3 from "d3";
+import { EHnswLinkType } from "Types";
+import { deDupLink } from "./utils";
+
+const forceSearchView = (
+ visData,
+ targetOrigin = [0, 0],
+ forceIterations = 100
+) => {
+ return new Promise((resolve) => {
+ const nodeId2dist = {};
+ visData.forEach((levelData) =>
+ levelData.nodes.forEach((node) => (nodeId2dist[node.id] = node.dist || 0))
+ );
+ const nodeIds = Object.keys(nodeId2dist);
+ const nodes = nodeIds.map((nodeId) => ({
+ nodeId: nodeId,
+ dist: nodeId2dist[nodeId],
+ }));
+
+ const linksAll = visData.reduce((acc, cur) => acc.concat(cur.links), []);
+ const links = deDupLink(linksAll);
+ // console.log(nodes, links);
+ // console.log(links.length, linksAll.length);
+
+ const targetNode = {
+ nodeId: "target",
+ dist: 0,
+ fx: targetOrigin[0],
+ fy: targetOrigin[1],
+ };
+ nodes.push(targetNode);
+
+ const targetLinks = visData[0].fineIds.map((fineId) => ({
+ source: `${fineId}`,
+ target: "target",
+ type: EHnswLinkType.None,
+ }));
+ links.push(...targetLinks);
+ // console.log(nodes, links);
+
+ const rScale = d3
+ .scaleLinear()
+ .domain(
+ d3.extent(
+ nodes.filter((node) => node.dist > 0),
+ (node) => node.dist
+ )
+ )
+ .range([10, 1000])
+ .clamp(true);
+ const simulation = d3
+ .forceSimulation(nodes as any[])
+ .alphaDecay(1 - Math.pow(0.001, 1 / forceIterations))
+ .force(
+ "link",
+ d3
+ .forceLink(links)
+ .id((d: any) => `${d.nodeId}`)
+ .strength((d) => (d.type === EHnswLinkType.None ? 2 : 0.4))
+ )
+ .force(
+ "r",
+ d3
+ .forceRadial(
+ (node: any) => rScale(node.dist),
+ targetOrigin[0],
+ targetOrigin[1]
+ )
+ .strength(1)
+ )
+ .force("charge", d3.forceManyBody().strength(-10000))
+ .on("end", () => {
+ const id2forcePos = {};
+ nodes.forEach(
+ (node: any) => (id2forcePos[node.nodeId] = [node.x, node.y])
+ );
+ resolve(id2forcePos);
+ });
+ });
+};
+
+export default forceSearchView;
diff --git a/federjs/FederLayout/visDataHandler/hnsw/search/hnsw3d.ts b/federjs/FederLayout/visDataHandler/hnsw/search/hnsw3d.ts
new file mode 100644
index 0000000..0ed6869
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/search/hnsw3d.ts
@@ -0,0 +1,89 @@
+import { TSearchRecordsHnsw } from 'Types/searchRecords';
+import { EHnswLinkType, TCoord } from 'Types';
+import parseVisRecords from './parseVisRecords';
+import forceSearchView from './forceSearchView';
+import transformHandler from './transformHandler';
+import * as d3 from 'd3';
+
+export const searchViewLayoutHandler3d = async (
+ searchRecords: TSearchRecordsHnsw,
+ layoutParams: any
+) => {
+ const visData = parseVisRecords(searchRecords);
+ const {
+ targetR,
+ canvasScale = 1,
+ targetOrigin,
+ searchViewNodeBasicR,
+ forceIterations,
+ } = layoutParams;
+
+ const id2forcePos = await forceSearchView(
+ visData,
+ targetOrigin,
+ forceIterations
+ );
+
+ const searchNodesLevels = visData.map((levelData) => levelData.nodes);
+ searchNodesLevels.forEach((levelData) =>
+ levelData.forEach((node) => {
+ node.forcePos = id2forcePos[node.id];
+ node.x = node.forcePos[0];
+ node.y = node.forcePos[1];
+ })
+ );
+ const { layerPosLevels, transformFunc } = transformHandler(
+ searchNodesLevels.reduce((acc, node) => acc.concat(node), []),
+ layoutParams
+ );
+
+ const searchTarget = {
+ id: 'target',
+ r: targetR * canvasScale,
+ searchViewPosLevels: d3
+ .range(visData.length)
+ .map((i) => transformFunc(...(targetOrigin as TCoord), i)),
+ };
+
+ searchNodesLevels.forEach((nodes, level) => {
+ nodes.forEach((node) => {
+ node.searchViewPosLevels = d3
+ .range(level + 1)
+ .map((i) => transformFunc(...(node.forcePos as TCoord), i));
+ node.r = (searchViewNodeBasicR + node.type * 0.5) * canvasScale;
+ });
+ });
+
+ const id2searchNode = {};
+ searchNodesLevels.forEach((levelData) =>
+ levelData.forEach((node) => (id2searchNode[node.id] = node))
+ );
+
+ const searchLinksLevels = parseVisRecords(searchRecords).map((levelData) =>
+ levelData.links.filter((link) => link.type !== EHnswLinkType.None)
+ );
+ searchLinksLevels.forEach((levelData) =>
+ levelData.forEach((link) => {
+ const sourceId = link.source;
+ const targetId = link.target;
+ const sourceNode = id2searchNode[sourceId];
+ const targetNode = id2searchNode[targetId];
+ link.source = sourceNode;
+ link.target = targetNode;
+ })
+ );
+
+ const entryNodesLevels = visData.map((levelData) =>
+ levelData.entryIds.map((id) => id2searchNode[id])
+ );
+
+ return {
+ visData,
+ id2forcePos,
+ searchTarget,
+ entryNodesLevels,
+ searchNodesLevels,
+ searchLinksLevels,
+ searchRecords
+ };
+};
diff --git a/federjs/FederLayout/visDataHandler/hnsw/search/index.ts b/federjs/FederLayout/visDataHandler/hnsw/search/index.ts
new file mode 100644
index 0000000..a8cf724
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/search/index.ts
@@ -0,0 +1,106 @@
+import { TSearchRecordsHnsw } from "Types/searchRecords";
+import { EHnswLinkType, TCoord } from "Types";
+import parseVisRecords from "./parseVisRecords";
+import forceSearchView from "./forceSearchView";
+import transformHandler from "./transformHandler";
+import computeSearchViewTransition from "./computeSearchViewTransition";
+import * as d3 from "d3";
+
+export const searchViewLayoutHandler = async (
+ searchRecords: TSearchRecordsHnsw,
+ layoutParams: any
+) => {
+ const visData = parseVisRecords(searchRecords);
+
+ const {
+ targetR,
+ canvasScale = 1,
+ targetOrigin,
+ searchViewNodeBasicR,
+ searchInterLevelTime,
+ searchIntraLevelTime,
+ forceIterations,
+ } = layoutParams;
+
+ const id2forcePos = await forceSearchView(
+ visData,
+ targetOrigin,
+ forceIterations
+ );
+
+ const searchNodesLevels = visData.map((levelData) => levelData.nodes);
+ searchNodesLevels.forEach((levelData) =>
+ levelData.forEach((node) => {
+ node.forcePos = id2forcePos[node.id];
+ node.x = node.forcePos[0];
+ node.y = node.forcePos[1];
+ })
+ );
+ const { layerPosLevels, transformFunc } = transformHandler(
+ searchNodesLevels.reduce((acc, node) => acc.concat(node), []),
+ layoutParams
+ );
+
+ const searchTarget = {
+ id: "target",
+ r: targetR * canvasScale,
+ searchViewPosLevels: d3
+ .range(visData.length)
+ .map((i) => transformFunc(...(targetOrigin as TCoord), i)),
+ };
+
+ searchNodesLevels.forEach((nodes, level) => {
+ nodes.forEach((node) => {
+ node.searchViewPosLevels = d3
+ .range(level + 1)
+ .map((i) => transformFunc(...(node.forcePos as TCoord), i));
+ node.r = (searchViewNodeBasicR + node.type * 0.5) * canvasScale;
+ });
+ });
+
+ const id2searchNode = {};
+ searchNodesLevels.forEach((levelData) =>
+ levelData.forEach((node) => (id2searchNode[node.id] = node))
+ );
+
+ const searchLinksLevels = parseVisRecords(searchRecords).map((levelData) =>
+ levelData.links.filter((link) => link.type !== EHnswLinkType.None)
+ );
+ searchLinksLevels.forEach((levelData) =>
+ levelData.forEach((link) => {
+ const sourceId = link.source;
+ const targetId = link.target;
+ const sourceNode = id2searchNode[sourceId];
+ const targetNode = id2searchNode[targetId];
+ link.source = sourceNode;
+ link.target = targetNode;
+ })
+ );
+
+ const entryNodesLevels = visData.map((levelData) =>
+ levelData.entryIds.map((id) => id2searchNode[id])
+ );
+
+ const { targetShowTime, nodeShowTime, linkShowTime, duration } =
+ computeSearchViewTransition({
+ linksLevels: searchLinksLevels,
+ entryNodesLevels,
+ interLevelGap: searchInterLevelTime,
+ intraLevelGap: searchIntraLevelTime,
+ });
+
+ return {
+ visData,
+ id2forcePos,
+ searchTarget,
+ entryNodesLevels,
+ searchNodesLevels,
+ searchLinksLevels,
+ searchLayerPosLevels: layerPosLevels,
+ searchTargetShowTime: targetShowTime,
+ searchNodeShowTime: nodeShowTime,
+ searchLinkShowTime: linkShowTime,
+ searchTransitionDuration: duration,
+ searchParams: searchRecords.searchParams,
+ };
+};
diff --git a/federjs/FederLayout/visDataHandler/hnsw/search/parseVisRecords.ts b/federjs/FederLayout/visDataHandler/hnsw/search/parseVisRecords.ts
new file mode 100644
index 0000000..8873cb8
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/search/parseVisRecords.ts
@@ -0,0 +1,113 @@
+import { EHnswNodeType, EHnswLinkType, TId } from "Types";
+import { TSearchRecordsHnsw } from "Types/searchRecords";
+import { getLinkId, parseLinkId } from "./utils";
+
+export const parseVisRecords = ({
+ topkResults,
+ searchRecords,
+}: TSearchRecordsHnsw) => {
+ const visData = [];
+ const numLevels = searchRecords.length;
+ let fineIds = topkResults.map((d) => d.id);
+ let entryId = -1;
+ for (let i = numLevels - 1; i >= 0; i--) {
+ const level = numLevels - 1 - i;
+ if (level > 0) {
+ fineIds = [entryId];
+ }
+
+ const visRecordsLevel = searchRecords[i];
+
+ const id2nodeType = {};
+ const linkId2linkType = {};
+ const updateNodeType = (nodeId: TId, type: EHnswNodeType) => {
+ if (id2nodeType[nodeId]) {
+ id2nodeType[nodeId] = Math.max(id2nodeType[nodeId], type);
+ } else {
+ id2nodeType[nodeId] = type;
+ }
+ };
+ const updateLinkType = (
+ sourceId: TId,
+ targetId: TId,
+ type: EHnswLinkType
+ ) => {
+ const linkId = getLinkId(sourceId, targetId);
+ if (linkId2linkType[linkId]) {
+ linkId2linkType[linkId] = Math.max(linkId2linkType[linkId], type);
+ } else {
+ linkId2linkType[linkId] = type;
+ }
+ };
+ const id2dist = {};
+ const sourceMap = {};
+
+ visRecordsLevel.forEach((record) => {
+ const [sourceId, targetId, dist] = record;
+
+ if (sourceId === targetId) {
+ // entry
+ entryId = targetId;
+ id2dist[targetId] = dist;
+ } else {
+ updateNodeType(sourceId, EHnswNodeType.Candidate);
+ updateNodeType(targetId, EHnswNodeType.Coarse);
+
+ if (id2dist[targetId] >= 0) {
+ // visited
+ updateLinkType(sourceId, targetId, EHnswLinkType.Visited);
+ } else {
+ // not visited
+ id2dist[targetId] = dist;
+ updateLinkType(sourceId, targetId, EHnswLinkType.Extended);
+
+ sourceMap[targetId] = sourceId;
+
+ // only level-0 have "link_type - search"
+ if (level === 0) {
+ const preSourceId = sourceMap[sourceId];
+ if (preSourceId >= 0) {
+ updateLinkType(preSourceId, sourceId, EHnswLinkType.Searched);
+ }
+ }
+ }
+ }
+ });
+
+ fineIds.forEach((fineId) => {
+ updateNodeType(fineId, EHnswNodeType.Fine);
+
+ let t = fineId;
+ while (t in sourceMap) {
+ let s = sourceMap[t];
+ updateLinkType(s, t, EHnswLinkType.Fine);
+ t = s;
+ }
+ });
+
+ const nodes = Object.keys(id2nodeType).map((id) => ({
+ id: `${id}`,
+ type: id2nodeType[id],
+ dist: id2dist[id],
+ }));
+ const links = Object.keys(linkId2linkType).map((linkId) => {
+ const [source, target] = parseLinkId(linkId);
+ return {
+ source: `${source}`,
+ target: `${target}`,
+ type: linkId2linkType[linkId],
+ };
+ });
+ const visDataLevel = {
+ entryIds: [`${entryId}`],
+ fineIds: fineIds.map((id) => `${id}`),
+ links,
+ nodes,
+ };
+ visData.push(visDataLevel);
+ }
+
+ return visData;
+};
+
+export default parseVisRecords;
diff --git a/federjs/FederLayout/visDataHandler/hnsw/search/transformHandler.ts b/federjs/FederLayout/visDataHandler/hnsw/search/transformHandler.ts
new file mode 100644
index 0000000..5415f49
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/search/transformHandler.ts
@@ -0,0 +1,47 @@
+import * as d3 from 'd3';
+
+export const transformHandler = (
+ nodes,
+ { levelCount, width, height, padding, xBias = 0.65, yBias = 0.4, yOver = 0.1 }
+) => {
+ const layerWidth = width - padding[1] - padding[3];
+ const layerHeight =
+ (height - padding[0] - padding[2]) /
+ (levelCount - (levelCount - 1) * yOver);
+ const xRange = d3.extent(nodes, (node: any) => node.x) as [number, number];
+ const yRange = d3.extent(nodes, (node: any) => node.y) as [number, number];
+
+ const xOffset = padding[3] + layerWidth * xBias;
+ const transformFunc = (x, y, level) => {
+ const _x = (x - xRange[0]) / (xRange[1] - xRange[0]);
+ const _y = (y - yRange[0]) / (yRange[1] - yRange[0]);
+
+ const newX =
+ xOffset + _x * layerWidth * (1 - xBias) - _y * layerWidth * xBias;
+
+ const newY =
+ padding[0] +
+ layerHeight * (1 - yOver) * (levelCount - 1 - level) +
+ _x * layerHeight * (1 - yBias) +
+ _y * layerHeight * yBias;
+
+ return [newX, newY];
+ };
+ const layerPos = [
+ [layerWidth * xBias, 0],
+ [layerWidth, layerHeight * (1 - yBias)],
+ [layerWidth * (1 - xBias), layerHeight],
+ [0, layerHeight * yBias],
+ ];
+ const layerPosLevels = Array(levelCount).fill(0).map((_, level) =>
+ layerPos.map((coord) => [
+ coord[0] + padding[3],
+ coord[1] +
+ padding[0] +
+ layerHeight * (1 - yOver) * (levelCount - 1 - level),
+ ])
+ );
+ return { layerPosLevels, transformFunc };
+};
+
+export default transformHandler;
diff --git a/federjs/FederLayout/visDataHandler/hnsw/search/utils.ts b/federjs/FederLayout/visDataHandler/hnsw/search/utils.ts
new file mode 100644
index 0000000..7ff6d4b
--- /dev/null
+++ b/federjs/FederLayout/visDataHandler/hnsw/search/utils.ts
@@ -0,0 +1,39 @@
+import { TId } from "Types";
+
+const connection = "---";
+
+export const getLinkId = (sourceId: TId, targetId: TId) =>
+ `${sourceId}${connection}${targetId}`;
+
+export const parseLinkId = (linkId: string) =>
+ linkId.split(connection).map((d) => +d);
+
+export const getLinkIdWithLevel = (
+ sourceId: TId,
+ targetId: TId,
+ level: number
+) => `link-${level}-${sourceId}-${targetId}`;
+
+export const getNodeIdWithLevel = (nodeId: TId, level: number) =>
+ `node-${level}-${nodeId}`;
+
+export const getEntryLinkIdWithLevel = (nodeId: TId | string, level: number) =>
+ `inter-level-${level}-${nodeId}`;
+
+export const deDupLink = (
+ links: any[],
+ source = "source",
+ target = "target"
+) => {
+ const linkStringSet = new Set();
+ return links.filter((link) => {
+ const linkString = `${link[source]}---${link[target]}`;
+ const linkStringReverse = `${link[target]}---${link[source]}`;
+ if (linkStringSet.has(linkString) || linkStringSet.has(linkStringReverse)) {
+ return false;
+ } else {
+ linkStringSet.add(linkString);
+ return true;
+ }
+ });
+};
diff --git a/federjs/FederView/ViewHandler.ts b/federjs/FederView/ViewHandler.ts
new file mode 100644
index 0000000..83f5a00
--- /dev/null
+++ b/federjs/FederView/ViewHandler.ts
@@ -0,0 +1,8 @@
+import { TViewParams } from 'Types';
+import { TVisData } from 'Types/visData';
+
+export default interface TViewHandler {
+ node: HTMLElement;
+ init(visData: TVisData, viewParams: TViewParams): void;
+ render(): void;
+}
diff --git a/federjs/FederView/hnswView/HnswSearchHnsw3dView.ts b/federjs/FederView/hnswView/HnswSearchHnsw3dView.ts
new file mode 100644
index 0000000..89cd85a
--- /dev/null
+++ b/federjs/FederView/hnswView/HnswSearchHnsw3dView.ts
@@ -0,0 +1,1331 @@
+import { TViewParams } from 'Types';
+import { TVisData, TVisDataHnsw3d } from 'Types/visData';
+import TViewHandler from '../ViewHandler';
+import InfoPanel from 'FederView/InfoPanel';
+import * as THREE from 'three';
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+import { MeshLine, MeshLineMaterial, MeshLineRaycast } from 'three.meshline';
+import { MeshBasicMaterial } from 'three';
+import { hnswlibHNSWSearch } from 'FederIndex/searchHandler/hnswSearch';
+import anime from 'animejs/lib/anime.es.js';
+
+const defaultViewParams = {
+ width: 800,
+ height: 600,
+};
+
+export const HNSW_NODE_TYPE = {
+ Coarse: 1,
+ Candidate: 2,
+ Fine: 3,
+ Target: 4,
+};
+
+export default class HnswSearchHnsw3dView implements TViewHandler {
+ node: HTMLElement;
+ staticPanel: InfoPanel;
+ clickedPanel: InfoPanel;
+ hoveredPanel: InfoPanel;
+ canvas: HTMLCanvasElement;
+ visData: TVisDataHnsw3d;
+ static colors = {
+ candidateBlue: 0x175fff,
+ searchedYellow: 0xfffc85,
+ fineOrange: 0xf36e4b,
+ targetWhite: 0xffffff,
+ labelGreen: 0x7fff7c,
+ };
+ selectedLayer = -1;
+ lastSelectedLayer = -1;
+ intervalId: number;
+
+ //threejs stuff
+ longerLineMap = new Map();
+ longerDashedLineMap = new Map();
+ layerUi: HTMLDivElement;
+ scene: THREE.Scene;
+ camera: THREE.OrthographicCamera;
+ renderer: THREE.WebGLRenderer;
+ spheres: THREE.Mesh[] = [];
+ targetSpheres: THREE.Mesh[] = [];
+ planes: THREE.Mesh[] = [];
+ lines: THREE.Mesh[] = [];
+ controller: OrbitControls;
+ canvasWidth: number;
+ canvasHeight: number;
+ minX = Infinity;
+ minY = Infinity;
+ maxX = -Infinity;
+ maxY = -Infinity;
+ scenes: THREE.Scene[] = [];
+ pickingScene: THREE.Scene;
+ sphere2id = new Map();
+ static defaultCamera = {
+ position: {
+ isVector3: true,
+ x: 85.73523132349014,
+ y: 21.163507502655282,
+ z: 46.92095544735611,
+ },
+ rotation: {
+ isEuler: true,
+ x: -0.42372340871438785,
+ y: 1.0301035872063589,
+ z: 0.36899315353677475,
+ order: 'XYZ',
+ },
+ zoom: 0.14239574134637464,
+ };
+ dashedLines: THREE.Mesh[] = [];
+ orangeLines: THREE.Mesh[] = [];
+ pickingTarget: THREE.WebGLRenderTarget;
+ pickingMap: Map<
+ number,
+ THREE.Mesh
+ >;
+ objectsPerLayer: Map = new Map(); //存储每一层的mesh,包括节点、连接线、plane
+ moveObjects: THREE.Mesh[] = [];
+ playerUi: HTMLDivElement;
+ playBtn: HTMLDivElement;
+ resetBtn: HTMLDivElement;
+ prevBtn: HTMLDivElement;
+ nextBtn: HTMLDivElement;
+ originCamBtn: HTMLDivElement;
+ static stopHtml = `
+
+ `;
+ static playHtml = `
+
+ `;
+ played: boolean;
+ currentSceneIndex: number;
+ slider: HTMLInputElement;
+
+ get k() {
+ return this.visData.searchRecords.searchParams.k;
+ }
+
+ constructor(visData: TVisData, viewParams: TViewParams) {
+ this.staticPanel = new InfoPanel();
+ this.clickedPanel = new InfoPanel();
+ this.hoveredPanel = new InfoPanel();
+ this.init(visData, viewParams);
+ }
+
+ addCss() {
+ //create a div element
+ this.layerUi = document.createElement('div');
+ //flex column display
+ this.layerUi.style.display = 'flex';
+ this.layerUi.style.flexDirection = 'column';
+ this.layerUi.style.position = 'absolute';
+ this.layerUi.style.top = '0';
+ this.layerUi.style.right = '0';
+
+ this.layerUi.style.height = '100%';
+ //center justify
+ this.layerUi.style.justifyContent = 'center';
+ this.node.style.position = 'relative';
+ const style = document.createElement('style');
+ style.innerHTML = `
+ .layer-ui{
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: center;
+ pointer-events: none;
+ }
+ .layer-ui-item{
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ transition: transform 0.3s;
+ }
+
+ .layer-ui-item-inner-circle{
+ width:12px;
+ height:12px;
+ border-radius: 50%;
+ background-color: #fff;
+ position: relative;
+
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ .layer-ui-item-outer-circle{
+ width:32px;
+ height:32px;
+ border-radius: 50%;
+ border: 1px dashed #fff;
+ opacity: 0.3;
+ }
+ .layer-ui-item-outer-circle:hover{
+ boxShadow: 0 0 10px white;
+ opacity: 1;
+ border: 1px solid #fff;
+ }
+ .hnsw-search-hnsw3d-view-player-ui{
+ position: absolute;
+ bottom: -128;
+ flex-direction: column;
+ display: flex;
+ height:128px;
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row{
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ flex: 1;
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row-item{
+ display: flex;
+ flex-direction: row;
+ justify-content:left;
+ align-items: center;
+ flex: 1;
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row-item > .icon{
+ margin-right: 15px;
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row-item > .icon:hover{
+ cursor: pointer;
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row > input{
+ width: 100%;
+ background: rgba(255,255,255,0.3);
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row > input::-webkit-slider-thumb{
+ -webkit-appearance: none;
+ appearance: none;
+ visibility: hidden;
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row > input::-moz-range-thumb{
+ visibility: hidden;
+ }
+ //focus style
+ .hnsw-search-hnsw3d-view-player-ui-row > input:focus{
+ outline: none;
+ }
+ //track style
+ .hnsw-search-hnsw3d-view-player-ui-row > input::-webkit-slider-runnable-track{
+ width: 100%;
+ background: rgba(255,255,255);
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row > input::-moz-range-track{
+ width: 100%;
+ background: rgba(255,255,255,0.3);
+ }
+ .hnsw-search-hnsw3d-view-player-ui-row > input::-ms-track{
+ width: 100%;
+ background: rgba(255,255,255,0.3);
+ }
+ `;
+
+ //hover glow bloom
+ document.head.appendChild(style);
+ }
+
+ createCircleDom() {
+ const circleDom = document.createElement('div');
+ circleDom.classList.add('layer-ui-item');
+ const parentDiv = document.createElement('div');
+ parentDiv.classList.add('layer-ui-item-outer-circle');
+ //hover cursor pointer
+ parentDiv.style.cursor = 'pointer';
+ parentDiv.style.marginBottom = '16px';
+ parentDiv.style.transition = 'box-shadow 0.3s';
+ parentDiv.style.transition = 'opacity 0.3s';
+
+ const childDiv = document.createElement('div');
+ //
+ childDiv.classList.add('layer-ui-item-inner-circle');
+
+ parentDiv.appendChild(childDiv);
+
+ circleDom.appendChild(parentDiv);
+ return circleDom;
+ }
+
+ setUpLayerUi() {
+ this.addCss();
+ //create a div element
+ this.layerUi = document.createElement('div');
+ //flex column display
+ this.layerUi.style.display = 'flex';
+ this.layerUi.style.flexDirection = 'column';
+ this.layerUi.style.position = 'absolute';
+ this.layerUi.style.top = '0';
+ this.layerUi.style.right = '0';
+ this.node.style.width = '1200px';
+ this.layerUi.style.height = '100%';
+ //center justify
+ this.layerUi.style.justifyContent = 'center';
+ this.node.style.position = 'relative';
+
+ const adjustLayout = () => {
+ //make all div above ith layer translateY(100%)
+ const children = this.layerUi.children;
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ if (this.selectedLayer <= i) {
+ (child as HTMLDivElement).style.transform = 'translateY(0)';
+ } else {
+ (child as HTMLDivElement).style.transform = 'translateY(-100%)';
+ }
+ }
+ };
+
+ const highlightLayer = () => {
+ const children = this.layerUi.children;
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ if (this.selectedLayer === i) {
+ (child.children[0] as HTMLDivElement).style.boxShadow =
+ '0 0 10px white';
+ (child.children[0] as HTMLDivElement).style.opacity = '1';
+ (child.children[0] as HTMLDivElement).style.border = '1px solid #fff';
+ } else {
+ (child.children[0] as HTMLDivElement).style.boxShadow = 'none';
+ (child.children[0] as HTMLDivElement).style.opacity = '0.3';
+ (child.children[0] as HTMLDivElement).style.border =
+ '1px dashed #fff';
+ }
+ }
+ };
+
+ //create circleDoms represent layers
+ for (let i = 0; i < this.visData.searchRecords.searchRecords.length; i++) {
+ const circleDom = this.createCircleDom();
+ let toggled = false;
+ circleDom.children[0].addEventListener('click', () => {
+ if(this.played){
+ return;
+ }
+ if (this.selectedLayer !== i) {
+ this.selectedLayer = i;
+ } else {
+ this.selectedLayer = -1;
+ }
+ adjustLayout();
+ this.startTransition();
+ highlightLayer();
+ this.lastSelectedLayer = this.selectedLayer;
+ });
+ //add circleDom to layerUi
+ this.layerUi.appendChild(circleDom);
+ }
+ this.node.appendChild(this.layerUi);
+ }
+
+ startTransition() {
+ debugger;
+ if (this.lastSelectedLayer !== this.selectedLayer) {
+ //iterate all the scene objects
+ for (let i = 0; i < this.scene.children.length; i++) {
+ const child = this.scene.children[i];
+ //if the object userdata.longer is true
+ if (child.userData.longer) {
+ debugger;
+ const layer = parseInt(child.userData.layer.split('-')[1]);
+ if (this.selectedLayer === layer) {
+ child.visible = true;
+ } else {
+ child.visible = false;
+ }
+ } else if (typeof child.userData.layer === 'string') {
+ const layer = parseInt(child.userData.layer.split('-')[1]);
+ if (this.selectedLayer === layer) {
+ child.visible = false;
+ } else {
+ child.visible = true;
+ }
+ }
+ }
+
+ //计算diff
+ const lastOffsets = new Array(
+ this.visData.searchRecords.searchRecords.length
+ )
+ .fill(undefined)
+ .map((v, idx) => {
+ return idx < this.lastSelectedLayer ? 1200 : 0;
+ });
+ const offsets = new Array(this.visData.searchRecords.searchRecords.length)
+ .fill(undefined)
+ .map((v, idx) => {
+ return idx < this.selectedLayer ? 1200 : 0;
+ });
+ const diff = offsets.map((v, idx) => v - lastOffsets[idx]);
+ this.scene.children.forEach((obj, idx) => {
+ if (
+ obj instanceof THREE.Mesh &&
+ obj.userData.layer !== undefined &&
+ !obj.userData.longer
+ ) {
+ let layer = obj.userData.layer;
+ //layer type is string
+ if (typeof layer === 'string') {
+ layer = parseInt(layer.split('-')[0]);
+ }
+ const offset = diff[layer];
+
+ //animejs animation
+ anime({
+ targets: obj.position,
+ y: obj.position.y + offset,
+ duration: 200,
+ easing: 'easeInOutQuad',
+ });
+ }
+ });
+ this.pickingScene.children.forEach((pickingObj) => {
+ if (
+ pickingObj instanceof THREE.Mesh &&
+ pickingObj.userData.layer !== undefined
+ ) {
+ let layer = pickingObj.userData.layer;
+ if (typeof layer === 'string') {
+ layer = parseInt(layer.split('-')[0]);
+ }
+ const offset = diff[layer];
+ pickingObj.position.y += offset;
+ }
+ });
+ }
+ }
+
+ init(visData: TVisData, viewParams: TViewParams) {
+ this.visData = visData as TVisDataHnsw3d;
+ console.log(visData);
+ const searchRecords = this.visData.searchRecords;
+ console.log(searchRecords);
+ this.node = document.createElement('div');
+ this.setUpLayerUi();
+ this.node.className = 'hnsw-search-hnsw3d-view';
+ viewParams = Object.assign({}, defaultViewParams, viewParams);
+ this.canvasWidth = viewParams.width;
+ this.canvasHeight = viewParams.height;
+ this.node.style.width = `${this.canvasWidth}px`;
+ this.node.style.height = `${this.canvasHeight}px`;
+ this.setupCanvas();
+ this.setupRenderer();
+ this.setupScene();
+ this.parseSearchRecords();
+ this.setupPickingScene();
+ this.setupEventListeners();
+ this.setupCamera();
+ this.setupController();
+ this.setupPlayerUi();
+ }
+
+ setupPlayerUi() {
+ this.playerUi = document.createElement('div');
+ this.playerUi.className = 'hnsw-search-hnsw3d-view-player-ui';
+ this.node.appendChild(this.playerUi);
+ this.playerUi.style.width = `${this.canvasWidth}px`;
+ //white border
+ this.playerUi.style.border = '1px solid white';
+
+ //1st row
+ const row1 = document.createElement('div');
+ row1.className = 'hnsw-search-hnsw3d-view-player-ui-row';
+ this.playerUi.appendChild(row1);
+ //set row1 innnerHTML
+ row1.innerHTML = `
+
+
+
+ `;
+
+ this.playBtn = this.playerUi.querySelector('.play') as HTMLDivElement;
+ this.resetBtn = this.playerUi.querySelector('.replay') as HTMLDivElement;
+ this.prevBtn = this.playerUi.querySelector('.prev') as HTMLDivElement;
+ this.nextBtn = this.playerUi.querySelector('.next') as HTMLDivElement;
+ this.originCamBtn = this.playerUi.querySelector(
+ '.origin-cam'
+ ) as HTMLDivElement;
+ this.playBtn.addEventListener('click', () => {
+ console.log('play');
+
+ this.play();
+ });
+ this.resetBtn.addEventListener('click', () => {
+ this.currentSceneIndex = 0;
+ this.slider.value = '0';
+ });
+ this.prevBtn.addEventListener('click', () => {
+ console.log('prev');
+ this.currentSceneIndex = Math.max(0, this.currentSceneIndex - 1);
+ this.slider.value = this.currentSceneIndex.toString();
+ });
+ this.nextBtn.addEventListener('click', () => {
+ console.log('next');
+ this.currentSceneIndex = Math.min(
+ this.currentSceneIndex + 1,
+ this.scenes.length - 1
+ );
+ this.slider.value = `${this.currentSceneIndex}`;
+ });
+ this.originCamBtn.addEventListener('click', () => {
+ console.log('origin cam');
+ this.resetCamera();
+ });
+
+ //2nd row
+ const row2 = document.createElement('div');
+ row2.className = 'hnsw-search-hnsw3d-view-player-ui-row';
+ this.playerUi.appendChild(row2);
+ //create a slider
+ const slider = document.createElement('input');
+ slider.type = 'range';
+ slider.min = '0';
+ slider.max = (this.scenes.length - 1).toString();
+ slider.value = slider.max;
+ this.currentSceneIndex = this.scenes.length - 1;
+ slider.className = 'hnsw-search-hnsw3d-view-player-ui-slider';
+ slider.addEventListener('input', () => {
+ this.currentSceneIndex = parseInt(slider.value);
+ this.played = false;
+ this.playBtn.innerHTML = HnswSearchHnsw3dView.playHtml;
+ });
+ row2.appendChild(slider);
+ this.slider = slider;
+ }
+ play() {
+ this.played = !this.played;
+ if (this.played) {
+ this.playBtn.innerHTML = HnswSearchHnsw3dView.stopHtml;
+ this.intervalId = window.setInterval(() => {
+ if (this.played) {
+ this.currentSceneIndex++;
+ if (this.currentSceneIndex >= this.scenes.length) {
+ this.currentSceneIndex = 0;
+ }
+ //update slider
+ this.slider.value = this.currentSceneIndex.toString();
+ }
+ }, 300);
+ } else {
+ this.playBtn.innerHTML = HnswSearchHnsw3dView.playHtml;
+ window.clearInterval(this.intervalId);
+ }
+ }
+
+ createLine(
+ from: THREE.Vector3,
+ to: THREE.Vector3,
+ color: number | THREE.Texture,
+ width = 10
+ ) {
+ const line = new MeshLine();
+ line.setPoints([from.x, from.y, from.z, to.x, to.y, to.z]);
+ let material: MeshLineMaterial;
+ if (color instanceof THREE.Texture) {
+ material = new MeshLineMaterial({
+ useMap: true,
+ transparent: true,
+ map: color,
+ opacity: 1,
+ lineWidth: width,
+ });
+ } else {
+ material = new MeshLineMaterial({
+ color: new THREE.Color(color),
+ lineWidth: width,
+ });
+ }
+
+ const mesh = new THREE.Mesh(line, material);
+ return mesh;
+ }
+
+ createSphere(x: number, y: number, z: number, color: number) {
+ const geometry = new THREE.SphereGeometry(20);
+ const material = new THREE.MeshBasicMaterial({
+ color: new THREE.Color(color),
+ });
+ const sphere = new THREE.Mesh(geometry, material);
+ sphere.position.set(x, y, z);
+ return sphere;
+ }
+
+ stepTo(steps: number) {}
+
+ getPositionXZ(id: number) {
+ const { id2forcePos } = this.visData;
+ const pos = id2forcePos[id];
+ return { x: pos[0], z: pos[1] };
+ }
+
+ //change sphere color
+ changeSphereColor(sphere: THREE.Mesh, color: number) {
+ //clone sphere
+ const material = new THREE.MeshBasicMaterial({
+ color: new THREE.Color(color),
+ });
+ const geometry = sphere.geometry.clone();
+ const newSphere = new THREE.Mesh(geometry, material);
+ newSphere.name = sphere.name;
+ newSphere.position.copy(sphere.position);
+ // copy userData
+ newSphere.userData = sphere.userData;
+ this.scene.remove(sphere);
+ this.scene.add(newSphere);
+ // (newSphere.material as MeshBasicMaterial).color = new THREE.Color(color);
+ }
+
+ async wait(ms: number) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+ }
+
+ createGradientTexture(color1: number, color2: number) {
+ const canvas = document.createElement('canvas');
+ canvas.width = 256;
+ canvas.height = 256;
+ const ctx = canvas.getContext('2d');
+ const gradient = ctx.createLinearGradient(0, 0, 256, 0);
+ gradient.addColorStop(0, '#' + color1.toString(16));
+ gradient.addColorStop(1, '#' + color2.toString(16));
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, 256, 256);
+ const texture = new THREE.CanvasTexture(canvas);
+ return texture;
+ }
+
+ parseSearchRecords() {
+ let y0 = 0;
+ const { searchRecords } = this.visData.searchRecords;
+
+ //建立id与sphere的映射
+ let sphere2idArray: Map<
+ THREE.Mesh,
+ number
+ >[] = [];
+ let id2sphereArray: Map<
+ number,
+ THREE.Mesh
+ >[] = [];
+ let id2lineArray: Map>[] = [];
+
+ for (let i = 0; i < searchRecords.length; i++) {
+ let records = searchRecords[i];
+ const sphere2id = new Map<
+ THREE.Mesh,
+ number
+ >();
+ const id2sphere = new Map<
+ number,
+ THREE.Mesh
+ >();
+ const id2line = new Map>();
+
+ for (let j = 0; j < records.length; j++) {
+ const record = records[j];
+ const startNode = record[0];
+ const endNode = record[1];
+ const { x: x0, z: z0 } = this.getPositionXZ(startNode);
+ const { x: x1, z: z1 } = this.getPositionXZ(endNode);
+ if (!id2line.has(`${startNode}-${endNode}`)) {
+ const line = this.createLine(
+ new THREE.Vector3(x0, y0, z0),
+ new THREE.Vector3(x1, y0, z1),
+ 0x000000
+ );
+ line.userData = {
+ layer: i,
+ };
+ this.scene.add(line);
+ line.visible = false;
+ line.name = `${startNode}-${endNode}`;
+ id2line.set(`${startNode}-${endNode}`, line);
+ }
+ if (!id2sphere.has(startNode)) {
+ const startNodeSphere = this.createSphere(
+ x0,
+ y0,
+ z0,
+ HnswSearchHnsw3dView.colors.searchedYellow
+ );
+ startNodeSphere.visible = false;
+ startNodeSphere.name = `${startNode}`;
+ startNodeSphere.userData = {
+ layer: i,
+ };
+ this.scene.add(startNodeSphere);
+ id2sphere.set(startNode, startNodeSphere);
+ sphere2id.set(startNodeSphere, startNode);
+ }
+ if (!id2sphere.has(endNode)) {
+ const endNodeSphere = this.createSphere(
+ x1,
+ y0,
+ z1,
+ HnswSearchHnsw3dView.colors.searchedYellow
+ );
+ endNodeSphere.visible = false;
+ endNodeSphere.name = `${endNode}`;
+ endNodeSphere.userData = {
+ layer: i,
+ };
+ this.scene.add(endNodeSphere);
+ id2sphere.set(endNode, endNodeSphere);
+ sphere2id.set(endNodeSphere, endNode);
+ }
+ }
+ y0 += -400;
+ sphere2idArray.push(sphere2id);
+ id2sphereArray.push(id2sphere);
+ id2lineArray.push(id2line);
+ }
+
+ y0 = 0;
+ const topVisitedNodes: { id: number; distance: number }[] = [];
+ for (let i = 0; i < searchRecords.length; i++) {
+ const records = searchRecords[i];
+ const visited = new Set();
+
+ const firstRecord = records[0];
+ let lastVisited = firstRecord[0];
+ if (i === 0) {
+ const targetSphere = this.createSphere(
+ 0,
+ 0,
+ 0,
+ HnswSearchHnsw3dView.colors.targetWhite
+ );
+ targetSphere.userData = {
+ layer: i,
+ };
+ this.scene.add(targetSphere);
+ this.targetSpheres.push(targetSphere);
+ }
+ for (let j = 0; j < records.length; j++) {
+ const record = records[j];
+ const startNode = record[0];
+ const endNode = record[1];
+ const distance = record[2];
+ const { x: x0, z: z0 } = this.getPositionXZ(startNode);
+ const { x: x1, z: z1 } = this.getPositionXZ(endNode);
+ const startNodeSphere = id2sphereArray[i].get(startNode);
+ const endNodeSphere = id2sphereArray[i].get(endNode);
+ const line = id2lineArray[i].get(`${startNode}-${endNode}`);
+
+ if (startNodeSphere) {
+ if (
+ i === searchRecords.length - 1 &&
+ !topVisitedNodes.find((v) => v.id === endNode)
+ ) {
+ topVisitedNodes.push({ id: endNode, distance });
+ }
+ startNodeSphere.visible = true;
+ this.changeSphereColor(
+ startNodeSphere,
+ HnswSearchHnsw3dView.colors.searchedYellow
+ );
+ visited.add(startNode);
+ if (lastVisited !== startNode) {
+ const line = id2lineArray[i].get(`${lastVisited}-${startNode}`);
+ if (line) {
+ //create yellow texture
+ const texture = this.createGradientTexture(
+ 0xffffff00,
+ HnswSearchHnsw3dView.colors.searchedYellow
+ );
+ //delete line
+ this.scene.remove(line);
+ //create line connect to startNodeSphere from lastVisitedSphere
+ const { x: x2, z: z2 } = this.getPositionXZ(lastVisited);
+ const line2 = this.createLine(
+ new THREE.Vector3(x2, y0, z2),
+ new THREE.Vector3(x0, y0, z0),
+ texture
+ );
+ line2.userData = {
+ layer: i,
+ };
+ this.scene.add(line2);
+ id2lineArray[i].set(`${lastVisited}-${startNode}`, line2);
+ } else if (i === searchRecords.length - 1) {
+ //当发现需要回溯访问过的节点时
+ //find line whose name has startNode
+ const id2line = id2lineArray[i];
+ const key = Array.from(id2line.keys()).find((key) =>
+ key.includes(`-${startNode}`)
+ );
+ const line = id2line.get(key);
+ if (line) {
+ //create yellow texture
+ const texture = this.createGradientTexture(
+ 0xffffff00,
+ HnswSearchHnsw3dView.colors.searchedYellow
+ );
+ //delete line
+ this.scene.remove(line);
+ //parse line name
+ const [start, end] = key.split('-').map((v) => parseInt(v));
+ //create line connect start and end
+ const { x: x0, z: z0 } = this.getPositionXZ(start);
+ const { x: x1, z: z1 } = this.getPositionXZ(end);
+ const line2 = this.createLine(
+ new THREE.Vector3(x0, y0, z0),
+ new THREE.Vector3(x1, y0, z1),
+ texture
+ );
+ line2.userData = {
+ layer: i,
+ };
+ this.scene.add(line2);
+ line2.name = `${start}-${end}`;
+ id2lineArray[i].set(`${start}-${end}`, line2);
+ }
+ }
+ }
+ }
+ if (endNodeSphere && !visited.has(endNode)) {
+ endNodeSphere.visible = true;
+ this.changeSphereColor(
+ endNodeSphere,
+ HnswSearchHnsw3dView.colors.candidateBlue
+ );
+ }
+ if (line && !visited.has(endNode)) {
+ let texture = null;
+ texture = this.createGradientTexture(
+ 0xffffff00,
+ HnswSearchHnsw3dView.colors.candidateBlue
+ );
+ const newLine = this.createLine(
+ new THREE.Vector3(x0, y0, z0),
+ new THREE.Vector3(x1, y0, z1),
+ texture
+ );
+ newLine.userData = {
+ layer: i,
+ };
+
+ this.scene.remove(line);
+ this.scene.add(newLine);
+ id2lineArray[i].set(`${startNode}-${endNode}`, newLine);
+ }
+
+ lastVisited = startNode;
+
+ this.scenes.push(this.scene.clone());
+ }
+ let nextLevelMap = id2sphereArray[i + 1];
+ if (nextLevelMap) {
+ const nextLevelSphere = nextLevelMap.get(lastVisited);
+ if (nextLevelSphere) {
+ this.changeSphereColor(
+ nextLevelSphere,
+ HnswSearchHnsw3dView.colors.searchedYellow
+ );
+ nextLevelSphere.visible = true;
+ const currentLevelSphere = id2sphereArray[i].get(lastVisited);
+ if (currentLevelSphere) {
+ this.changeSphereColor(
+ currentLevelSphere,
+ HnswSearchHnsw3dView.colors.fineOrange
+ );
+ }
+ //create line connecting current level and next level
+ const orangeYellowTexture = this.createGradientTexture(
+ HnswSearchHnsw3dView.colors.fineOrange,
+ HnswSearchHnsw3dView.colors.searchedYellow
+ );
+ const { x: x0, z: z0 } = this.getPositionXZ(lastVisited);
+ const { x: x1, z: z1 } = this.getPositionXZ(lastVisited);
+ const line = this.createLine(
+ new THREE.Vector3(x0, y0, z0),
+ new THREE.Vector3(x1, y0 - 400, z1),
+ orangeYellowTexture
+ );
+ line.userData = {
+ layer: `${i}-${i + 1}`,
+ };
+ this.orangeLines.push(line);
+ const longerLine = this.createLine(
+ new THREE.Vector3(x0, y0 + 1200, z0),
+ new THREE.Vector3(x1, y0 - 400, z1),
+ orangeYellowTexture
+ );
+ longerLine.userData = {
+ layer: `${i}-${i + 1}`,
+ longer: true,
+ };
+ longerLine.visible = false;
+ this.longerLineMap.set(`${i}-${i + 1}`, longerLine);
+ this.scene.add(longerLine);
+ this.scene.add(line);
+ }
+ }
+ //处理最终的橙色情况
+ if (i === searchRecords.length - 1) {
+ //sort topVisitedNodes in ascending order
+ topVisitedNodes.sort((a, b) => a.distance - b.distance);
+ //get ef search parameter
+ const k = this.k;
+ //get top ef nodes
+ const topEfNodes = topVisitedNodes.slice(0, k);
+ //change color of top ef nodes to orange
+ for (let j = 0; j < topEfNodes.length; j++) {
+ const { id } = topEfNodes[j];
+ const sphere = id2sphereArray[i].get(id);
+ if (sphere) {
+ this.changeSphereColor(
+ sphere,
+ HnswSearchHnsw3dView.colors.fineOrange
+ );
+ }
+ }
+ this.scenes.push(this.scene.clone());
+ }
+
+ //layer切换创建新的target连接线
+ if (i < searchRecords.length - 1) {
+ //create a white sphere
+ const targetSphere = this.createSphere(
+ 0,
+ y0 - 400,
+ 0,
+ HnswSearchHnsw3dView.colors.targetWhite
+ );
+ targetSphere.userData = {
+ layer: i + 1,
+ };
+ this.targetSpheres.push(targetSphere);
+ this.scene.add(targetSphere);
+ const dashedMaterial = new MeshLineMaterial({
+ color: new THREE.Color(HnswSearchHnsw3dView.colors.targetWhite),
+ lineWidth: 10,
+ dashed: true,
+ dashArray: 0.1,
+ dashRatio: 0.5,
+ transparent: true,
+ });
+
+ const line = new MeshLine();
+ line.setPoints([0, y0, 0, 0, y0 - 400, 0]);
+ const mesh = new THREE.Mesh(line.geometry, dashedMaterial);
+ mesh.userData = {
+ layer: `${i}-${i + 1}`,
+ };
+ this.dashedLines.push(mesh);
+
+ const dashedMaterial2= new MeshLineMaterial({
+ color: new THREE.Color(HnswSearchHnsw3dView.colors.targetWhite),
+ lineWidth: 10,
+ dashed: true,
+ dashArray: 0.025,
+ dashRatio: 0.5,
+ transparent: true,
+ });
+ const longerDashedLineGeometry = new MeshLine();
+ longerDashedLineGeometry.setPoints([0, y0 + 1200, 0, 0, y0 - 400, 0]);
+ const longerDashedLine = new THREE.Mesh(
+ longerDashedLineGeometry,
+ dashedMaterial2
+ );
+ longerDashedLine.userData = {
+ layer: `${i}-${i + 1}`,
+ longer: true,
+ };
+ longerDashedLine.visible = false;
+ this.scene.add(longerDashedLine);
+ this.longerDashedLineMap.set(`${i}-${i + 1}`, longerDashedLine);
+
+ this.scene.add(mesh);
+ if (i === searchRecords.length - 2) {
+ const targetSphere = this.createSphere(
+ 0,
+ y0 - 400,
+ 0,
+ HnswSearchHnsw3dView.colors.targetWhite
+ );
+ targetSphere.userData = {
+ layer: i + 1,
+ };
+ this.targetSpheres.push(targetSphere);
+ this.scene.add(targetSphere);
+ }
+ }
+ y0 += -400;
+ }
+ }
+
+ createDashedLineTexture(
+ backgroundColor = 0x00000000,
+ color = 0xffffff00,
+ dashSize = 20,
+ gapSize = 10
+ ) {
+ const canvas = document.createElement('canvas');
+ canvas.width = 128;
+ canvas.height = 1;
+ const context = canvas.getContext('2d');
+ context.fillStyle = `#${backgroundColor.toString(16)}`;
+ context.fillRect(0, 0, canvas.width, canvas.height);
+ context.strokeStyle = `#${color.toString(16)}`;
+ context.lineWidth = 10;
+ context.beginPath();
+ context.setLineDash([dashSize, gapSize]);
+ context.moveTo(0, 0);
+ context.lineTo(canvas.width, canvas.height);
+ context.stroke();
+ const texture = new THREE.Texture(canvas);
+ texture.needsUpdate = true;
+ return texture;
+ }
+
+ setupPickingScene() {
+ // create picking scene
+ this.pickingScene = new THREE.Scene();
+ this.pickingTarget = new THREE.WebGLRenderTarget(
+ this.renderer.domElement.width,
+ this.renderer.domElement.height
+ );
+ this.pickingScene.background = new THREE.Color(0x000000);
+ let count = 1;
+ this.pickingMap = new Map();
+ const pickingMaterial = new THREE.MeshBasicMaterial({
+ vertexColors: true,
+ });
+ this.scene.traverse((obj) => {
+ if (obj instanceof THREE.Mesh) {
+ //check if the object is a sphere
+ if (obj.geometry.type === 'SphereGeometry') {
+ const geometry = obj.geometry.clone();
+ //apply vertex color to picking mesh
+ let color = new THREE.Color();
+ applyVertexColors(geometry, color.setHex(count));
+ const pickingMesh = new THREE.Mesh(geometry, pickingMaterial);
+ pickingMesh.position.copy(obj.position);
+ pickingMesh.rotation.copy(obj.rotation);
+ pickingMesh.scale.copy(obj.scale);
+ pickingMesh.userData = obj.userData;
+ pickingMesh.name = obj.name;
+ this.pickingScene.add(pickingMesh);
+ this.pickingMap.set(count, pickingMesh);
+ count++;
+ }
+ }
+ });
+ const pickingPlaneMaterial = new THREE.MeshBasicMaterial({
+ vertexColors: true,
+ });
+
+ //create a plane for each layer
+ for (let i = 0; i < this.visData.searchRecords.searchRecords.length; i++) {
+ let geometry = this.planes[i].clone().geometry.clone();
+ //set vertex color for plane
+ let color = new THREE.Color();
+ applyVertexColors(geometry, color.setHex(count));
+ const plane = new THREE.Mesh(geometry, pickingPlaneMaterial);
+ const { x, y, z } = this.planes[i].position;
+ const scale = this.planes[i].scale;
+ plane.scale.set(scale.x, scale.y, scale.z);
+ plane.position.set(x, y, z);
+ plane.rotation.x = -Math.PI / 2;
+ plane.scale.set(1.2, 1.2, 1.2);
+ plane.name = `plane${i}`;
+ plane.userData = { layer: i };
+ this.pickingScene.add(plane);
+ this.pickingMap.set(count, plane);
+ count += 0x000042;
+ }
+ }
+
+ //add event listener to the canvas
+ setupEventListeners() {
+ this.renderer.domElement.addEventListener('click', (event) => {
+ //pointer = { x: e.offsetX, y: e.offsetY };
+
+ let id = this.pick(event.offsetX, event.offsetY);
+ //console.log(id);
+ const obj = this.pickingMap.get(id);
+ console.log('picked', obj.userData, obj.name);
+ //if picking a plane
+ if (obj?.name?.startsWith('plane')) {
+ const layer = obj.userData.layer;
+ console.log(layer);
+ }
+ });
+ }
+
+ pick(x: number, y: number) {
+ if (x < 0 || y < 0) return -1;
+ const pixelRatio = this.renderer.getPixelRatio();
+ // console.log(pixelRatio, x, y);
+ // set the view offset to represent just a single pixel under the mouse
+ this.camera.setViewOffset(
+ this.canvas.clientWidth,
+ this.canvas.clientHeight,
+ x * pixelRatio,
+ y * pixelRatio,
+ 1,
+ 1
+ );
+ // render the scene
+ this.renderer.setRenderTarget(this.pickingTarget);
+ this.renderer.render(this.pickingScene, this.camera);
+ this.renderer.setRenderTarget(null);
+ //clear the view offset so the camera returns to normal
+ this.camera.clearViewOffset();
+ // get the pixel color under the mouse
+ const pixelBuffer = new Uint8Array(4);
+ this.renderer.readRenderTargetPixels(
+ this.pickingTarget,
+ 0,
+ 0,
+ 1,
+ 1,
+ pixelBuffer
+ );
+ const id = (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | pixelBuffer[2];
+ return id;
+ }
+
+ createPlaneGradientTexture() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 256;
+ canvas.height = 256;
+ const ctx = canvas.getContext('2d');
+ const gradient = ctx.createLinearGradient(0, 0, 0, 256);
+ gradient.addColorStop(0, 'rgba(30, 100, 255, 1)');
+ gradient.addColorStop(1, 'rgba(0, 35, 77, 0)');
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, 256, 256);
+ //draw white borders 1px
+ ctx.fillStyle = '#D9EAFF';
+ ctx.fillRect(0, 0, 1, 256);
+ ctx.fillRect(0, 0, 256, 1);
+ ctx.fillRect(0, 255, 256, 1);
+ ctx.fillRect(255, 0, 1, 256);
+ const texture = new THREE.Texture(canvas);
+ texture.needsUpdate = true;
+ return texture;
+ }
+
+ drawBorderLine(from: THREE.Vector3, to: THREE.Vector3, color: string) {
+ const line = new MeshLine();
+ line.setPoints([from.x, from.y, from.z, to.x, to.y, to.z]);
+ const material = new MeshLineMaterial({
+ color: new THREE.Color(color),
+ lineWidth: 0.01,
+ sizeAttenuation: false,
+ });
+ const mesh = new THREE.Mesh(line, material);
+ this.scene.add(mesh);
+ }
+
+ setupPlanes() {
+ let z0 = -30;
+ const { visData } = this.visData;
+ for (let i = visData.length - 1; i >= 0; i--) {
+ //add planes
+ const width = this.maxX - this.minX;
+ const height = this.maxY - this.minY;
+ const planeGeometry = new THREE.PlaneGeometry(width, height);
+ const texture = this.createPlaneGradientTexture();
+ const planeMaterial = new THREE.MeshBasicMaterial({
+ map: texture,
+ side: THREE.DoubleSide,
+ transparent: true,
+ opacity: 0.5,
+ depthWrite: false,
+ });
+ const plane = new THREE.Mesh(planeGeometry, planeMaterial);
+ plane.rotation.x = Math.PI / 2;
+ plane.position.z = (this.maxY + this.minY) / 2;
+ plane.position.x = (this.maxX + this.minX) / 2;
+ plane.scale.set(1.2, 1.2, 1.2);
+ plane.position.y = z0;
+ plane.name = `plane${i}`;
+ plane.userData = { layer: visData.length - 1 - i };
+ z0 += -400;
+ this.planes.push(plane);
+ }
+ console.log(this.planes);
+ }
+
+ computeBoundries() {
+ const { visData } = this.visData;
+ console.log(visData);
+ for (let i = visData.length - 1; i >= 0; i--) {
+ const { nodes } = visData[i];
+ for (let j = 0; j < nodes.length; j++) {
+ const node = nodes[j];
+ const { id, x, y, type } = node;
+ this.maxX = Math.max(this.maxX, x);
+ this.maxY = Math.max(this.maxY, y);
+ this.minX = Math.min(this.minX, x);
+ this.minY = Math.min(this.minY, y);
+ }
+ }
+ }
+
+ render() {
+ //render
+ const render = (t) => {
+ //update camera zoom
+ //change dashed line offset
+ if (this.dashedLines.length > 0) {
+ this.dashedLines.forEach((line) => {
+ line.material.uniforms.dashOffset.value -= 0.01;
+ });
+ this.longerDashedLineMap.forEach((line) => {
+ (line.material as any).uniforms.dashOffset.value -= 0.0025;
+ });
+ }
+ this.controller.update();
+ if (this.currentSceneIndex !== undefined) {
+ this.scene = this.scenes[this.currentSceneIndex];
+ }
+ this.renderer.render(this.scene, this.camera);
+
+ requestAnimationFrame(render);
+ };
+ requestAnimationFrame(render);
+ }
+
+ private setupController() {
+ this.controller = new OrbitControls(this.camera, this.canvas);
+ this.controller.enableZoom = true;
+ this.controller.enablePan = true;
+ this.controller.enableDamping = true;
+ this.controller.enableRotate = true;
+ }
+
+ private setupCamera() {
+ this.camera = new THREE.OrthographicCamera(
+ -this.canvas.width / 2,
+ this.canvas.width / 2,
+ this.canvas.height / 2,
+ -this.canvas.height / 2,
+ -4000,
+ 4000
+ );
+ //default camera position
+ this.camera.position.set(
+ HnswSearchHnsw3dView.defaultCamera.position.x,
+ HnswSearchHnsw3dView.defaultCamera.position.y,
+ HnswSearchHnsw3dView.defaultCamera.position.z
+ );
+ this.camera.rotation.set(
+ HnswSearchHnsw3dView.defaultCamera.rotation.x,
+ HnswSearchHnsw3dView.defaultCamera.rotation.y,
+ HnswSearchHnsw3dView.defaultCamera.rotation.z
+ );
+ this.camera.zoom = HnswSearchHnsw3dView.defaultCamera.zoom;
+ this.camera.updateProjectionMatrix();
+ }
+
+ private resetCamera() {
+ this.camera.position.set(
+ HnswSearchHnsw3dView.defaultCamera.position.x,
+ HnswSearchHnsw3dView.defaultCamera.position.y,
+ HnswSearchHnsw3dView.defaultCamera.position.z
+ );
+ this.camera.rotation.set(
+ HnswSearchHnsw3dView.defaultCamera.rotation.x,
+ HnswSearchHnsw3dView.defaultCamera.rotation.y,
+ HnswSearchHnsw3dView.defaultCamera.rotation.z
+ );
+ this.camera.zoom = HnswSearchHnsw3dView.defaultCamera.zoom;
+ this.camera.updateProjectionMatrix();
+ }
+
+ private setupScene() {
+ this.scene = new THREE.Scene();
+ this.computeBoundries();
+ this.setupPlanes();
+ this.scene.add(...this.planes);
+ }
+
+ private setupRenderer() {
+ this.renderer = new THREE.WebGLRenderer({
+ canvas: this.canvas,
+ antialias: true,
+ });
+ this.renderer.setSize(this.canvas.width, this.canvas.height);
+ }
+
+ setupCanvas() {
+ this.canvas = document.createElement('canvas');
+ this.node.appendChild(this.canvas);
+ this.canvas.width = this.canvasWidth;
+ this.canvas.height = this.canvasHeight;
+ }
+}
+/**
+ *
+ * @param {THREE.Geometry} geometry
+ * @param {THREE.Color } color
+ */
+function applyVertexColors(geometry: THREE.BufferGeometry, color: THREE.Color) {
+ const positions = geometry.getAttribute('position');
+ const colors = [];
+ for (let i = 0; i < positions.count; i++) {
+ colors.push(color.r, color.g, color.b);
+ }
+ geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
+}
diff --git a/federjs/FederView/hnswView/HnswSearchView.ts b/federjs/FederView/hnswView/HnswSearchView.ts
new file mode 100644
index 0000000..723fed7
--- /dev/null
+++ b/federjs/FederView/hnswView/HnswSearchView.ts
@@ -0,0 +1,21 @@
+import { TViewParams } from 'Types';
+import { TVisData } from 'Types/visData';
+import TViewHandler from '../ViewHandler';
+
+import InfoPanel from 'FederView/InfoPanel';
+
+export default class HnswSearchView implements TViewHandler {
+ node: HTMLElement;
+ staticPanel: InfoPanel;
+ clickedPanel: InfoPanel;
+ hoveredPanel: InfoPanel;
+ constructor(visData: TVisData, viewParams: TViewParams) {
+ this.staticPanel = new InfoPanel();
+ this.clickedPanel = new InfoPanel();
+ this.hoveredPanel = new InfoPanel();
+
+ this.init(visData, viewParams);
+ }
+ init(visData: TVisData, viewParams: TViewParams) {}
+ render() {}
+}
\ No newline at end of file
diff --git a/federjs/FederView/index.ts b/federjs/FederView/index.ts
new file mode 100644
index 0000000..6315762
--- /dev/null
+++ b/federjs/FederView/index.ts
@@ -0,0 +1,30 @@
+import { TVisDataAll } from 'Types/visData';
+import { TViewParams, EIndexType, EActionType, EViewType } from 'Types';
+
+import HnswSearchHnsw3dView from './hnswView/HnswSearchHnsw3dView';
+import HnswSearchView from './hnswView/HnswSearchView';
+import ViewHandler from './ViewHandler';
+
+const viewMap = {
+ [EIndexType.hnsw + EActionType.search + EViewType.hnsw3d]:
+ HnswSearchHnsw3dView,
+ [EIndexType.hnsw + EActionType.search + EViewType.default]:
+ HnswSearchView,
+};
+export class FederView {
+ node: HTMLElement;
+ view: ViewHandler;
+ constructor(
+ { indexType, actionType, viewType, visData }: TVisDataAll,
+ viewParams: TViewParams
+ ) {
+ this.view = new viewMap[indexType + actionType + viewType](
+ visData,
+ viewParams
+ );
+ }
+
+ render() {
+ this.view.render();
+ }
+}
diff --git a/federjs/FederView/infoPanel/index.ts b/federjs/FederView/infoPanel/index.ts
new file mode 100644
index 0000000..723689b
--- /dev/null
+++ b/federjs/FederView/infoPanel/index.ts
@@ -0,0 +1,12 @@
+import { TCoord, TViewParams } from 'Types';
+
+export interface TInfoPanelStyles {}
+
+export interface TInfoPanelContext {}
+
+export default class infoPanel {
+ constructor(styles: TInfoPanelStyles = {}, viewParams: TViewParams = {}) {}
+ init() {}
+ setContext(context: TInfoPanelContext = null) {}
+ setPosition(pos: TCoord = null) {}
+}
diff --git a/federjs/Types/index.ts b/federjs/Types/index.ts
new file mode 100644
index 0000000..3b2c953
--- /dev/null
+++ b/federjs/Types/index.ts
@@ -0,0 +1,64 @@
+export type TVec = number[];
+export type TId = number;
+export type TCoord = [number, number];
+
+export enum ESourceType {
+ faiss = 'faiss',
+ hnswlib = 'hnswlib',
+ milvus = 'milvus',
+}
+
+export enum EIndexType {
+ hnsw = 'hnsw',
+ ivfflat = 'ivfflat',
+}
+
+export enum EMetricType {
+ METRIC_INNER_PRODUCT = 0, ///< maximum inner product search
+ METRIC_L2 = 1, ///< squared L2 search
+ METRIC_L1 = 2, ///< L1 (aka cityblock)
+ METRIC_Linf = 3, ///< infinity distance
+ METRIC_Lp = 4, ///< L_p distance, p is given by a faiss::Index
+ /// metric_arg
+
+ /// some additional metrics defined in scipy.spatial.distance
+ METRIC_Canberra = 20,
+ METRIC_BrayCurtis = 21,
+ METRIC_JensenShannon = 22,
+}
+
+export enum EActionType {
+ overview = 'overview',
+ search = 'search',
+}
+
+export enum EViewType {
+ default = 'default',
+ hnsw3d = 'hnsw3d',
+}
+
+export interface TSearchParams {
+ k: number;
+ ef?: number;
+ nprobe?: number;
+ metricType?: EMetricType;
+}
+
+export enum EHnswNodeType {
+ Coarse = 1,
+ Candidate = 2,
+ Fine = 3,
+ Target = 4,
+}
+
+export enum EHnswLinkType {
+ None = 0,
+ Visited = 1,
+ Extended = 2,
+ Searched = 3,
+ Fine = 4,
+}
+
+export type TViewParams = any;
+
+export type TLayoutParams = any;
diff --git a/federjs/Types/indexMeta/index.ts b/federjs/Types/indexMeta/index.ts
new file mode 100644
index 0000000..f2cbf78
--- /dev/null
+++ b/federjs/Types/indexMeta/index.ts
@@ -0,0 +1,21 @@
+import {
+ IIndexMetaHnsw,
+ TIndexMetaHnswGraphNode,
+ TIndexMetaHnswGraph,
+} from "./indexMetaHnsw";
+
+import {
+ TIndexMetaIvfflat,
+ TIndexMetaIvfflatCluster,
+} from "./indexMetaIvfflat";
+
+type TIndexMeta = TIndexMetaIvfflat | IIndexMetaHnsw;
+
+export {
+ TIndexMeta,
+ IIndexMetaHnsw,
+ TIndexMetaHnswGraphNode,
+ TIndexMetaHnswGraph,
+ TIndexMetaIvfflat,
+ TIndexMetaIvfflatCluster,
+};
diff --git a/federjs/Types/indexMeta/indexMetaHnsw.ts b/federjs/Types/indexMeta/indexMetaHnsw.ts
new file mode 100644
index 0000000..6f233e0
--- /dev/null
+++ b/federjs/Types/indexMeta/indexMetaHnsw.ts
@@ -0,0 +1,22 @@
+import { TId } from "Types";
+
+export interface TIndexMetaHnswGraphNode {
+ id: TId;
+ links: TId[];
+}
+
+export interface TIndexMetaHnswGraph {
+ level: number;
+ nodes: TIndexMetaHnswGraphNode[];
+}
+
+export interface IIndexMetaHnsw {
+ efConstruction: number;
+ M: number;
+ ntotal: number;
+ ndeleted: number;
+ nLevels: number;
+ entryPointId: TId;
+ nOverviewLevels: number;
+ overviewGraphLayers: TIndexMetaHnswGraph[];
+}
\ No newline at end of file
diff --git a/federjs/Types/indexMeta/indexMetaIvfflat.ts b/federjs/Types/indexMeta/indexMetaIvfflat.ts
new file mode 100644
index 0000000..da1c1b1
--- /dev/null
+++ b/federjs/Types/indexMeta/indexMetaIvfflat.ts
@@ -0,0 +1,14 @@
+import { TVec, TId } from "Types";
+
+export interface TIndexMetaIvfflatCluster {
+ clusterId: TId;
+ ids: TId[];
+ centroidVectors: TVec[];
+}
+
+export interface TIndexMetaIvfflat {
+ nlist: number;
+ ntotal: number;
+ ndeleted: number;
+ clusters: TIndexMetaIvfflatCluster[];
+}
diff --git a/federjs/Types/searchRecords/index.ts b/federjs/Types/searchRecords/index.ts
new file mode 100644
index 0000000..0fe6926
--- /dev/null
+++ b/federjs/Types/searchRecords/index.ts
@@ -0,0 +1,23 @@
+import {
+ TSearchRecordsHnswLayerItem,
+ TSearchRecordsHnswLayer,
+ TSearchRecordsHnsw,
+} from "./searchRecordsHnsw";
+
+import {
+ TSearchRecordsIvfflatCoarseCluster,
+ TSearchRecordsIvfflatFineNode,
+ TSearchRecordsIvfflat,
+} from "./searchRecordsIvfflat";
+
+type TSearchRecords = TSearchRecordsIvfflat | TSearchRecordsHnsw;
+
+export {
+ TSearchRecords,
+ TSearchRecordsHnswLayerItem,
+ TSearchRecordsHnswLayer,
+ TSearchRecordsHnsw,
+ TSearchRecordsIvfflatCoarseCluster,
+ TSearchRecordsIvfflatFineNode,
+ TSearchRecordsIvfflat,
+};
diff --git a/federjs/Types/searchRecords/searchRecordsHnsw.ts b/federjs/Types/searchRecords/searchRecordsHnsw.ts
new file mode 100644
index 0000000..1b5aa5c
--- /dev/null
+++ b/federjs/Types/searchRecords/searchRecordsHnsw.ts
@@ -0,0 +1,16 @@
+import { TId } from "Types";
+
+export type TSearchRecordsHnswLayerItem = [TId, TId, number];
+
+export type TSearchRecordsHnswLayer = TSearchRecordsHnswLayerItem[];
+
+export interface TSearchRecordsHnswTopkResultItem {
+ id: TId;
+ distance: number;
+}
+
+export interface TSearchRecordsHnsw {
+ searchRecords: TSearchRecordsHnswLayer[];
+ topkResults: TSearchRecordsHnswTopkResultItem[];
+ searchParams: any;
+}
diff --git a/federjs/Types/searchRecords/searchRecordsIvfflat.ts b/federjs/Types/searchRecords/searchRecordsIvfflat.ts
new file mode 100644
index 0000000..ee98736
--- /dev/null
+++ b/federjs/Types/searchRecords/searchRecordsIvfflat.ts
@@ -0,0 +1,21 @@
+import { TVec, TId } from "Types";
+
+export interface TSearchRecordsIvfflatCoarseCluster {
+ clusterId: TId;
+ distance: number;
+ vector: TVec;
+}
+
+export interface TSearchRecordsIvfflatFineNode {
+ id: TId;
+ clusterId: TId;
+ distance: number;
+ vector: TVec;
+}
+
+export interface TSearchRecordsIvfflat {
+ coarseSearchRecords: TSearchRecordsIvfflatCoarseCluster[];
+ nprobeClusterIds: TId[];
+ fineSearchRecords: TSearchRecordsIvfflatFineNode[];
+ topKVectorIds: TId[];
+}
diff --git a/federjs/Types/visData/index.ts b/federjs/Types/visData/index.ts
new file mode 100644
index 0000000..8d8d858
--- /dev/null
+++ b/federjs/Types/visData/index.ts
@@ -0,0 +1,128 @@
+import { EActionType, EIndexType, EViewType, TSearchParams, TVec } from 'Types';
+
+export type TVisData = any;
+export interface TVisDataAll {
+ indexType: EIndexType;
+ actionType: EActionType;
+ actionData?: TAcitonData;
+ viewType: EViewType;
+ visData: TVisData;
+}
+
+export interface TAcitonData {
+ target: TVec;
+ targetUrl?: string;
+ searchParams: TSearchParams;
+}
+
+export interface TopkResult {
+ id: number;
+ internalId: number;
+ dis: number;
+}
+
+export interface SearchParams {
+ k: number;
+ ef: number;
+}
+
+export interface SearchRecords {
+ searchRecords: number[][][];
+ topkResults: TopkResult[];
+ searchParams: SearchParams;
+}
+
+export interface TVisDataHnsw3d {
+ visData: VisDaum[];
+ id2forcePos: Id2forcePos;
+ searchTarget: SearchTarget;
+ entryNodesLevels: EntryNodesLevel[][];
+ searchNodesLevels: SearchNodesLevel[][];
+ searchLinksLevels: SearchLinksLevel[][];
+ searchRecords: SearchRecords;
+}
+
+export interface VisDaum {
+ entryIds: string[];
+ fineIds: string[];
+ links: Link[];
+ nodes: Node[];
+}
+
+export interface Link {
+ source: any;
+ target: any;
+ type: number;
+ index?: number;
+}
+
+export interface Node {
+ id: string;
+ type: number;
+ dist: number;
+ forcePos: number[];
+ x: number;
+ y: number;
+ searchViewPosLevels: any[][];
+ r: number;
+}
+
+export interface Id2forcePos {
+ [key: string]: number[];
+}
+
+export interface SearchTarget {
+ id: string;
+ r: number;
+ searchViewPosLevels: any[][];
+}
+
+export interface EntryNodesLevel {
+ id: string;
+ type: number;
+ dist: number;
+ forcePos: number[];
+ x: number;
+ y: number;
+ searchViewPosLevels: any[][];
+ r: number;
+}
+
+export interface SearchNodesLevel {
+ id: string;
+ type: number;
+ dist: number;
+ forcePos: number[];
+ x: number;
+ y: number;
+ searchViewPosLevels: any[][];
+ r: number;
+}
+
+export interface SearchLinksLevel {
+ source: Source;
+ target: Target;
+ type: number;
+}
+
+export interface Source {
+ id: string;
+ type: number;
+ dist: number;
+ forcePos: number[];
+ x: number;
+ y: number;
+ searchViewPosLevels: any[][];
+ r: number;
+}
+
+export interface Target {
+ id: string;
+ type: number;
+ dist: number;
+ forcePos: number[];
+ x: number;
+ y: number;
+ searchViewPosLevels: any[][];
+ r: number;
+}
diff --git a/federjs/Utils/PriorityQueue.js b/federjs/Utils/PriorityQueue.js
new file mode 100644
index 0000000..b95c76a
--- /dev/null
+++ b/federjs/Utils/PriorityQueue.js
@@ -0,0 +1,78 @@
+//Minimum Heap
+class PriorityQueue {
+ constructor(arr = [], key = null) {
+ if (typeof key == 'string') {
+ this._key = (item) => item[key];
+ } else this._key = key;
+ this._tree = [];
+ arr.forEach((d) => this.add(d));
+ }
+ add(item) {
+ this._tree.push(item);
+ let id = this._tree.length - 1;
+ while (id) {
+ const fatherId = Math.floor((id - 1) / 2);
+ if (this._getValue(id) >= this._getValue(fatherId)) break;
+ else {
+ this._swap(fatherId, id);
+ id = fatherId;
+ }
+ }
+ }
+ get top() {
+ return this._tree[0];
+ }
+ pop() {
+ if (this.isEmpty) {
+ return 'empty';
+ }
+ const item = this.top;
+ if (this._tree.length > 1) {
+ const lastItem = this._tree.pop();
+ let id = 0;
+ this._tree[id] = lastItem;
+ while (!this._isLeaf(id)) {
+ const curValue = this._getValue(id);
+ const leftId = id * 2 + 1;
+ const leftValue = this._getValue(leftId);
+ const rightId = leftId >= this._tree.length - 1 ? leftId : id * 2 + 2;
+ const rightValue = this._getValue(rightId);
+ const minValue = Math.min(leftValue, rightValue);
+ if (curValue <= minValue) break;
+ else {
+ const minId = leftValue < rightValue ? leftId : rightId;
+ this._swap(minId, id);
+ id = minId;
+ }
+ }
+ } else {
+ this._tree = [];
+ }
+ return item;
+ }
+ get isEmpty() {
+ return this._tree.length === 0;
+ }
+ get size() {
+ return this._tree.length;
+ }
+ get _firstLeaf() {
+ return Math.floor(this._tree.length / 2);
+ }
+ _isLeaf(id) {
+ return id >= this._firstLeaf;
+ }
+ _getValue(id) {
+ if (this._key) {
+ return this._key(this._tree[id]);
+ } else {
+ return this._tree[id];
+ }
+ }
+ _swap(id0, id1) {
+ const tree = this._tree;
+ [tree[id0], tree[id1]] = [tree[id1], tree[id0]];
+ }
+}
+
+export default PriorityQueue;
diff --git a/federjs/Utils/distFunc.ts b/federjs/Utils/distFunc.ts
new file mode 100644
index 0000000..b2317f9
--- /dev/null
+++ b/federjs/Utils/distFunc.ts
@@ -0,0 +1,24 @@
+import { EMetricType, TVec } from "Types";
+
+export const getDisL2 = (vec1: TVec, vec2: TVec) => {
+ return Math.sqrt(
+ vec1
+ .map((num, i) => num - vec2[i])
+ .map((num) => num * num)
+ .reduce((a, c) => a + c, 0)
+ );
+};
+
+export const getDisIR = (vec1: TVec, vec2: TVec) => {
+ return vec1.map((num, i) => num * vec2[i]).reduce((acc, cur) => acc + cur, 0);
+};
+
+export const getDisFunc = (metricType: EMetricType) => {
+ if (metricType === EMetricType.METRIC_L2) {
+ return getDisL2;
+ } else if (metricType === EMetricType.METRIC_INNER_PRODUCT) {
+ return getDisIR;
+ }
+ console.warn("[getDisFunc] wrong metric_type, use L2 (default).", metricType);
+ return getDisL2;
+};
diff --git a/federjs/index.ts b/federjs/index.ts
new file mode 100644
index 0000000..454668d
--- /dev/null
+++ b/federjs/index.ts
@@ -0,0 +1,5 @@
+import { FederIndex } from "FederIndex";
+import { FederLayout } from "FederLayout";
+import { FederView } from "FederView";
+
+export { FederIndex, FederLayout, FederView };
diff --git a/federjs_old/Feder.js b/federjs_old/Feder.js
new file mode 100644
index 0000000..36fb76a
--- /dev/null
+++ b/federjs_old/Feder.js
@@ -0,0 +1,142 @@
+import FederCore from './FederCore';
+import FederView from './FederView';
+import { FEDER_CORE_REQUEST } from 'Types';
+
+export default class Feder {
+ constructor({
+ coreUrl = null,
+ filePath = '',
+ source = '',
+ domSelector = null,
+ viewParams = {},
+ }) {
+ this.federView = new FederView({ domSelector, viewParams });
+ this.viewParams = viewParams;
+ if (!coreUrl) {
+ this.initCoreAndViewPromise = fetch(filePath, { mode: 'cors' })
+ .then((res) => res.arrayBuffer())
+ .then((data) => {
+ const core = new FederCore({ data, source, viewParams });
+ this.core = core;
+ const indexType = core.indexType;
+ const indexMeta = core.indexMeta;
+ const getVectorById = (id) =>
+ id in core.id2vector ? core.id2vector[id] : null;
+ this.core.getVectorById = getVectorById;
+ this.federView.initView({
+ indexType,
+ indexMeta,
+ getVectorById,
+ });
+ });
+ } else {
+ const getUrl = (path) => `${coreUrl}/${path}?`;
+
+ const requestData = (path, params = {}) =>
+ fetch(getUrl(path) + new URLSearchParams(params), {
+ mode: 'cors',
+ })
+ .then((res) => res.json())
+ .then((res) => {
+ if (res.message === 'succeed') return res.data;
+ else throw new Error(res);
+ });
+
+ this.initCoreAndViewPromise = new Promise(async (resolve) => {
+ const indexType = await requestData(FEDER_CORE_REQUEST.get_index_type);
+ const indexMeta = await requestData(FEDER_CORE_REQUEST.get_index_meta);
+ const getVectorById = (id) =>
+ requestData(FEDER_CORE_REQUEST.get_vector_by_id, { id });
+ this.core = {
+ indexType,
+ indexMeta,
+ getVectorById,
+ getTestIdAndVec: () =>
+ requestData(FEDER_CORE_REQUEST.get_test_id_and_vector),
+ search: (target) =>
+ requestData(FEDER_CORE_REQUEST.search, { target }),
+ setSearchParams: (params) =>
+ requestData(FEDER_CORE_REQUEST.set_search_params, params),
+ };
+ this.federView.initView({ indexType, indexMeta, getVectorById });
+ resolve();
+ });
+ }
+
+ this.setSearchParamsPromise = null;
+ }
+
+ overview() {
+ return this.federView.overview(this.initCoreAndViewPromise);
+ }
+ search(target = null, targetMediaUrl = null) {
+ if (target) {
+ const searchResPromise = Promise.all([
+ this.initCoreAndViewPromise,
+ this.setSearchParamsPromise,
+ ]).then(async () => {
+ const searchRes = await this.core.search(target);
+ console.log(searchRes);
+ this.searchRes = searchRes;
+ this.targetMediaUrl = targetMediaUrl;
+ return { searchRes, targetMediaUrl };
+ });
+ return this.federView.search({ searchResPromise });
+ } else {
+ if (!this.searchRes) {
+ console.error('No target');
+ return;
+ }
+ const searchRes = this.searchRes;
+ const targetMediaUrl = this.targetMediaUrl;
+ return this.federView.search({ searchRes, targetMediaUrl });
+ }
+ }
+ searchById(testId) {
+ const searchResPromise = this.initCoreAndViewPromise.then(async () => {
+ const testVec = await this.core.getVectorById(testId);
+ const targetMediaUrl =
+ this.viewParams && this.viewParams.mediaCallback
+ ? this.viewParams.mediaCallback(testId)
+ : null;
+ const searchRes = await this.core.search(testVec);
+ console.log(searchRes);
+ this.searchRes = searchRes;
+ return { searchRes, targetMediaUrl };
+ });
+ return this.federView.search({ searchResPromise });
+ }
+ searchRandTestVec() {
+ const searchResPromise = new Promise(async (resolve) => {
+ this.initCoreAndViewPromise && (await this.initCoreAndViewPromise);
+ let { testId, testVec } = await this.core.getTestIdAndVec();
+ while (isNaN(testId)) {
+ [testId, testVec] = await this.core.getTestIdAndVec();
+ }
+ console.log('random test vector:', testId, testVec);
+ const targetMediaUrl =
+ this.viewParams && this.viewParams.mediaCallback
+ ? this.viewParams.mediaCallback(testId)
+ : null;
+ const searchRes = await this.core.search(testVec);
+ console.log(searchRes);
+ this.searchRes = searchRes;
+ resolve({ searchRes, targetMediaUrl });
+ });
+
+ return this.federView.search({ searchResPromise });
+ }
+
+ setSearchParams(params) {
+ this.setSearchParamsPromise = new Promise(async (resolve) => {
+ this.initCoreAndViewPromise && (await this.initCoreAndViewPromise);
+ if (!this.core) {
+ console.error('No feder-core');
+ } else {
+ await this.core.setSearchParams(params);
+ }
+ resolve();
+ });
+ return this;
+ }
+}
diff --git a/federjs_old/FederCore/index.js b/federjs_old/FederCore/index.js
new file mode 100644
index 0000000..0fcd8ca
--- /dev/null
+++ b/federjs_old/FederCore/index.js
@@ -0,0 +1,89 @@
+import getIndexParser from './parser';
+import getMetaHandler from './metaHandler';
+import getIndexSearchHandler from './searchHandler';
+import getProjectorHandler from './projector';
+import seedrandom from 'seedrandom';
+import { INDEX_TYPE } from 'Types';
+export default class FederCore {
+ constructor({
+ data, // arraybuffer
+ source,
+ viewParams,
+ }) {
+ try {
+ this.viewParams = viewParams;
+ const index = this.parserIndex(data, source);
+ this.index = index;
+ this.indexType = index.indexType;
+
+ const { indexMeta, id2vector } = this.extractMeta(index, viewParams);
+ this.indexMeta = indexMeta;
+ this.id2vector = id2vector;
+
+ this.indexSearchHandler = getIndexSearchHandler(index.indexType);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ parserIndex(data, source) {
+ const indexParser = getIndexParser(source);
+ const index = indexParser(data);
+ return index;
+ }
+ extractMeta(index, viewParams = {}) {
+ const metaHandler = getMetaHandler(index.indexType);
+ const meta = metaHandler(index, viewParams);
+ return meta;
+ }
+ getTestIdAndVec() {
+ const ids = Object.keys(this.id2vector);
+ const r = Math.floor(Math.random() * ids.length);
+ const testId = ids[r];
+ const testVec = this.id2vector[testId];
+ return [testId, testVec];
+ }
+
+ setSearchParams(params) {
+ const newSearchParams = Object.assign({}, this.searchParams, params);
+ this.searchParams = newSearchParams;
+ }
+ search(target) {
+ const searchRes = this.indexSearchHandler({
+ index: this.index,
+ params: this.searchParams,
+ target,
+ });
+
+ if (this.index.indexType === INDEX_TYPE.ivf_flat) {
+ const {
+ fineSearchWithProjection = true,
+ projectMethod = 'umap',
+ projectSeed = null,
+ projectParams = {},
+ } = this.viewParams;
+ if (fineSearchWithProjection) {
+ const ids = searchRes.fine.map((item) => item.id);
+ const params = projectParams;
+ if (!!projectSeed) {
+ params.random = seedrandom(projectSeed);
+ }
+ this.initProjector({
+ method: projectMethod,
+ params,
+ });
+ const projections = this.projectByIds(ids);
+ searchRes.fine.map((item, i) => (item.projection = projections[i]));
+ }
+ }
+
+ return searchRes;
+ }
+
+ initProjector({ method, params = {} }) {
+ this.project = getProjectorHandler({ method, params });
+ }
+ projectByIds(ids) {
+ const vectors = ids.map((id) => this.id2vector[id]);
+ return this.project(vectors);
+ }
+}
diff --git a/federjs_old/FederCore/metaHandler/hnswMeta/hnswOverviewData.js b/federjs_old/FederCore/metaHandler/hnswMeta/hnswOverviewData.js
new file mode 100644
index 0000000..4cbb0c5
--- /dev/null
+++ b/federjs_old/FederCore/metaHandler/hnswMeta/hnswOverviewData.js
@@ -0,0 +1,54 @@
+const getHnswlibHNSWOverviewData = ({ index, overviewLevelCount = 3 }) => {
+ const { maxLevel, linkLists_levels, labels, enterPoint } = index;
+ const highlevel = Math.min(maxLevel, overviewLevelCount);
+ const lowlevel = maxLevel - highlevel;
+
+ const highLevelNodes = linkLists_levels
+ .map((linkLists_levels_item, internalId) =>
+ linkLists_levels_item.length > lowlevel
+ ? {
+ internalId,
+ id: labels[internalId],
+ linksLevels: linkLists_levels_item.slice(
+ lowlevel,
+ linkLists_levels_item.length
+ ),
+ path: [],
+ }
+ : null
+ )
+ .filter((d) => d);
+ // console.log('highLevelNodes', highLevelNodes)
+
+ const internalId2node = {};
+ highLevelNodes.forEach((node) => {
+ internalId2node[node.internalId] = node;
+ });
+
+ let queue = [enterPoint];
+ let start = 0;
+ internalId2node[enterPoint].path = [];
+ for (let level = highlevel - 1; level >= 0; level--) {
+ while (start < queue.length) {
+ const curNodeId = queue[start];
+ const curNode = internalId2node[curNodeId];
+ const candidateNodes = curNode.linksLevels[level];
+ candidateNodes.forEach((candidateNodeId) => {
+ const candidateNode = internalId2node[candidateNodeId];
+ if (candidateNode.path.length === 0 && candidateNodeId !== enterPoint) {
+ candidateNode.path = [...curNode.path, curNodeId];
+ queue.push(candidateNodeId);
+ }
+ });
+ start += 1;
+ }
+ queue = highLevelNodes
+ .filter((node) => node.linksLevels.length > level)
+ .map((node) => node.internalId);
+ start = 0;
+ }
+
+ return highLevelNodes;
+};
+
+export default getHnswlibHNSWOverviewData;
diff --git a/federjs_old/FederCore/metaHandler/hnswMeta/index.js b/federjs_old/FederCore/metaHandler/hnswMeta/index.js
new file mode 100644
index 0000000..2b40d7b
--- /dev/null
+++ b/federjs_old/FederCore/metaHandler/hnswMeta/index.js
@@ -0,0 +1,26 @@
+import getHnswlibHNSWOverviewData from './hnswOverviewData';
+export const getHnswMeta = (index, { overviewLevelCount = 3 }) => {
+ const { labels, vectors } = index;
+
+ const id2vector = {};
+ labels.forEach((id, i) => {
+ id2vector[id] = vectors[i];
+ });
+
+ const overviewNodes = getHnswlibHNSWOverviewData({
+ index,
+ overviewLevelCount,
+ });
+ const indexMeta = {
+ ntotal: index.ntotal,
+ M: index.M,
+ ef_construction: index.ef_construction,
+ overviewLevelCount,
+ levelCount: index.maxLevel + 1,
+ overviewNodes,
+ };
+
+ return { id2vector, indexMeta };
+};
+
+export default getHnswMeta;
diff --git a/federjs_old/FederCore/metaHandler/index.js b/federjs_old/FederCore/metaHandler/index.js
new file mode 100644
index 0000000..f7b6bfc
--- /dev/null
+++ b/federjs_old/FederCore/metaHandler/index.js
@@ -0,0 +1,17 @@
+import { INDEX_TYPE } from 'Types';
+import getHnswMeta from './hnswMeta';
+import getIvfflatMeta from './ivfflatMeta';
+
+
+const metaHandlerMap = {
+ [INDEX_TYPE.hnsw]: getHnswMeta,
+ [INDEX_TYPE.ivf_flat]: getIvfflatMeta,
+};
+
+export const getMetaHandler = (indexType) => {
+ if (indexType in metaHandlerMap) {
+ return metaHandlerMap[indexType];
+ } else throw `No meta handler for [${indexType}]`;
+};
+
+export default getMetaHandler;
diff --git a/federjs_old/FederCore/metaHandler/ivfflatMeta/index.js b/federjs_old/FederCore/metaHandler/ivfflatMeta/index.js
new file mode 100644
index 0000000..0957e2c
--- /dev/null
+++ b/federjs_old/FederCore/metaHandler/ivfflatMeta/index.js
@@ -0,0 +1,42 @@
+import { getIvfListId } from 'Utils';
+import getProjectorHandler from 'FederCore/projector';
+import seedrandom from 'seedrandom';
+
+export const getIvfflatMeta = (index, params) => {
+ const id2vector = {};
+ const inv = index.invlists;
+ for (let list_no = 0; list_no < inv.nlist; list_no++) {
+ inv.data[list_no].ids.forEach((id, ofs) => {
+ id2vector[id] = inv.data[list_no].vectors[ofs];
+ });
+ }
+ index.childIndex.vectors.forEach(
+ (vector, i) => (id2vector[getIvfListId(i)] = vector)
+ );
+
+ const indexMeta = {};
+ indexMeta.ntotal = index.ntotal;
+ indexMeta.nlist = index.nlist;
+ indexMeta.listIds = index.invlists.data.map((d) => d.ids);
+ indexMeta.listSizes = index.invlists.data.map((d) => d.ids.length);
+
+ const {
+ coarseSearchWithProjection = true,
+ projectMethod = 'umap',
+ projectSeed = null,
+ projectParams = {},
+ } = params;
+ if (coarseSearchWithProjection) {
+ const params = Object.assign({}, projectParams);
+ if (!!projectSeed) {
+ params.random = seedrandom(projectSeed);
+ }
+ const project = getProjectorHandler({ method: projectMethod, params });
+ const vectors = index.childIndex.vectors;
+ indexMeta.listCentroidProjections = project(vectors);
+ }
+
+ return { id2vector, indexMeta };
+};
+
+export default getIvfflatMeta;
diff --git a/federjs_old/FederCore/parser/FileReader.js b/federjs_old/FederCore/parser/FileReader.js
new file mode 100644
index 0000000..90f900f
--- /dev/null
+++ b/federjs_old/FederCore/parser/FileReader.js
@@ -0,0 +1,72 @@
+import { generateArray } from 'Utils';
+
+export default class FileReader {
+ constructor(arrayBuffer) {
+ this.data = arrayBuffer;
+ this.dataview = new DataView(arrayBuffer);
+
+ this.p = 0;
+ }
+ get isEmpty() {
+ return this.p >= this.data.byteLength;
+ }
+ readInt8() {
+ const int8 = this.dataview.getInt8(this.p, true);
+ this.p += 1;
+ return int8;
+ }
+ readUint8() {
+ const uint8 = this.dataview.getUint8(this.p, true);
+ this.p += 1;
+ return uint8;
+ }
+ readBool() {
+ const int8 = this.readInt8();
+ return Boolean(int8);
+ }
+ readUint16() {
+ const uint16 = this.dataview.getUint16(this.p, true);
+ this.p += 2;
+ return uint16;
+ }
+ readInt32() {
+ const int32 = this.dataview.getInt32(this.p, true);
+ this.p += 4;
+ return int32;
+ }
+ readUint32() {
+ const uint32 = this.dataview.getUint32(this.p, true);
+ this.p += 4;
+ return uint32;
+ }
+ readUint64() {
+ const left = this.readUint32();
+ const right = this.readUint32();
+ const int64 = left + Math.pow(2, 32) * right;
+ if (!Number.isSafeInteger(int64))
+ console.warn(int64, 'Exceeds MAX_SAFE_INTEGER. Precision may be lost');
+ return int64;
+ }
+ readFloat64() {
+ const float64 = this.dataview.getFloat64(this.p, true);
+ this.p += 8;
+ return float64;
+ }
+ readDouble() {
+ return this.readFloat64();
+ }
+
+ readUint32Array(n) {
+ const res = generateArray(n).map((_) => this.readUint32());
+ return res;
+ }
+ readFloat32Array(n) {
+ const res = new Float32Array(this.data.slice(this.p, this.p + n * 4));
+ this.p += n * 4;
+ return res;
+ }
+ readUint64Array(n) {
+ const res = generateArray(n).map((_) => this.readUint64());
+ return res;
+ }
+}
diff --git a/federjs_old/FederCore/parser/faissIndexParser/FaissFileReader.js b/federjs_old/FederCore/parser/faissIndexParser/FaissFileReader.js
new file mode 100644
index 0000000..5c3a73b
--- /dev/null
+++ b/federjs_old/FederCore/parser/faissIndexParser/FaissFileReader.js
@@ -0,0 +1,17 @@
+import FileReader from '../FileReader.js';
+import { uint8toChars, generateArray } from 'Utils';
+
+export default class FaissFileReader extends FileReader {
+ constructor(arrayBuffer) {
+ super(arrayBuffer);
+ }
+ readH() {
+ const uint8Array = generateArray(4).map((_) => this.readUint8());
+ const h = uint8toChars(uint8Array);
+ return h;
+ }
+ readDummy() {
+ const dummy = this.readUint64();
+ return dummy;
+ }
+}
diff --git a/federjs_old/FederCore/parser/faissIndexParser/index.js b/federjs_old/FederCore/parser/faissIndexParser/index.js
new file mode 100644
index 0000000..3b810d2
--- /dev/null
+++ b/federjs_old/FederCore/parser/faissIndexParser/index.js
@@ -0,0 +1,51 @@
+import FaissFileReader from './FaissFileReader.js';
+import readInvertedLists from './readInvertedLists.js';
+import readDirectMap from './readDirectMap.js';
+import readIndexHeader from './readIndexHeader.js';
+
+import { generateArray } from 'Utils';
+import { INDEX_TYPE, IndexHeader } from 'Types';
+
+const readIvfHeader = (reader, index) => {
+ readIndexHeader(reader, index);
+
+ index.nlist = reader.readUint64();
+ index.nprobe = reader.readUint64();
+
+ index.childIndex = readIndex(reader);
+
+ readDirectMap(reader, index);
+};
+
+const readXbVectors = (reader, index) => {
+ index.codeSize = reader.readUint64();
+
+ index.vectors = generateArray(index.ntotal).map((_) =>
+ reader.readFloat32Array(index.d)
+ );
+};
+
+const readIndex = (reader) => {
+ const index = {};
+ index.h = reader.readH();
+ if (index.h === IndexHeader.IVFFlat) {
+ index.indexType = INDEX_TYPE.ivf_flat;
+ readIvfHeader(reader, index);
+ readInvertedLists(reader, index);
+ } else if (index.h === IndexHeader.FlatIR || index.h === IndexHeader.FlatL2) {
+ index.indexType = INDEX_TYPE.flat;
+ readIndexHeader(reader, index);
+ readXbVectors(reader, index);
+ } else {
+ console.warn('[index type] not supported -', index.h);
+ }
+ return index;
+};
+
+const faissIndexParser = (arraybuffer) => {
+ const faissFileReader = new FaissFileReader(arraybuffer);
+ const index = readIndex(faissFileReader);
+ return index;
+};
+
+export default faissIndexParser;
diff --git a/federjs_old/FederCore/parser/faissIndexParser/readDirectMap.js b/federjs_old/FederCore/parser/faissIndexParser/readDirectMap.js
new file mode 100644
index 0000000..2927181
--- /dev/null
+++ b/federjs_old/FederCore/parser/faissIndexParser/readDirectMap.js
@@ -0,0 +1,24 @@
+import { DirectMapType } from 'Types';
+
+const checkDmType = (dmType) => {
+ if (dmType !== DirectMapType.NoMap) {
+ console.warn('[directmap_type] only support NoMap.');
+ }
+};
+
+const checkDmSize = (dmSize) => {
+ if (dmSize !== 0) {
+ console.warn('[directmap_size] should be 0.');
+ }
+};
+
+const readDirectMap = (reader, index) => {
+ const directMap = {};
+ directMap.dmType = reader.readUint8();
+ checkDmType(directMap.dmType);
+ directMap.size = reader.readUint64();
+ checkDmSize(directMap.size);
+ index.directMap = directMap;
+};
+
+export default readDirectMap;
diff --git a/federjs_old/FederCore/parser/faissIndexParser/readIndexHeader.js b/federjs_old/FederCore/parser/faissIndexParser/readIndexHeader.js
new file mode 100644
index 0000000..8c5102e
--- /dev/null
+++ b/federjs_old/FederCore/parser/faissIndexParser/readIndexHeader.js
@@ -0,0 +1,39 @@
+import { MetricType } from 'Types';
+
+const checkMetricType = (metricType) => {
+ if (
+ metricType !== MetricType.METRIC_L2 &&
+ metricType !== MetricType.METRIC_INNER_PRODUCT
+ ) {
+ console.warn('[metric_type] only support l2 and inner_product.');
+ }
+};
+
+const checkDummy = (dummy_1, dummy_2) => {
+ if (dummy_1 !== dummy_2) {
+ console.warn('[dummy] not equal.', dummy_1, dummy_2);
+ }
+};
+
+const checkIsTrained = (isTrained) => {
+ if (!isTrained) {
+ console.warn('[is_trained] should be trained.', isTrained);
+ }
+};
+
+const readIndexHeader = (reader, index) => {
+ index.d = reader.readUint32();
+ index.ntotal = reader.readUint64();
+
+ const dummy_1 = reader.readDummy();
+ const dummy_2 = reader.readDummy();
+ checkDummy(dummy_1, dummy_2);
+
+ index.isTrained = reader.readBool();
+ checkIsTrained(index.isTrained);
+
+ index.metricType = reader.readUint32();
+ checkMetricType(index.metricType);
+};
+
+export default readIndexHeader;
diff --git a/federjs_old/FederCore/parser/faissIndexParser/readInvertedLists.js b/federjs_old/FederCore/parser/faissIndexParser/readInvertedLists.js
new file mode 100644
index 0000000..d2b0ec1
--- /dev/null
+++ b/federjs_old/FederCore/parser/faissIndexParser/readInvertedLists.js
@@ -0,0 +1,47 @@
+import { generateArray } from 'Utils';
+
+const checkInvH = (h) => {
+ if (h !== 'ilar') {
+ console.warn('[invlists h] not ilar.', h);
+ }
+};
+
+const checkInvListType = (listType) => {
+ if (listType !== 'full') {
+ console.warn('[inverted_lists list_type] only support full.', listType);
+ }
+};
+
+const readArrayInvLists = (reader, invlists) => {
+ invlists.listType = reader.readH();
+ checkInvListType(invlists.listType);
+
+ invlists.listSizesSize = reader.readUint64();
+ invlists.listSizes = generateArray(invlists.listSizesSize).map((_) =>
+ reader.readUint64()
+ );
+
+ const data = [];
+ generateArray(invlists.listSizesSize).forEach((_, i) => {
+ const vectors = generateArray(invlists.listSizes[i]).map((_) =>
+ reader.readFloat32Array(invlists.codeSize / 4)
+ );
+ const ids = reader.readUint64Array(invlists.listSizes[i]);
+ data.push({ ids, vectors });
+ });
+ invlists.data = data;
+};
+
+export const readInvertedLists = (reader, index) => {
+ const invlists = {};
+ invlists.h = reader.readH();
+ checkInvH(invlists.h);
+ invlists.nlist = reader.readUint64();
+ invlists.codeSize = reader.readUint64();
+
+ readArrayInvLists(reader, invlists);
+
+ index.invlists = invlists;
+};
+
+export default readInvertedLists;
diff --git a/federjs_old/FederCore/parser/hnswlibIndexParser/HNSWlibFileReader.js b/federjs_old/FederCore/parser/hnswlibIndexParser/HNSWlibFileReader.js
new file mode 100644
index 0000000..358a45c
--- /dev/null
+++ b/federjs_old/FederCore/parser/hnswlibIndexParser/HNSWlibFileReader.js
@@ -0,0 +1,16 @@
+import FileReader from '../FileReader.js';
+
+export default class HNSWlibFileReader extends FileReader {
+ constructor(arrayBuffer) {
+ super(arrayBuffer);
+ }
+ readIsDeleted() {
+ return this.readUint8()
+ }
+ readIsReused() {
+ return this.readUint8()
+ }
+ readLevelOCount() {
+ return this.readUint16();
+ }
+}
diff --git a/federjs_old/FederCore/parser/hnswlibIndexParser/index.js b/federjs_old/FederCore/parser/hnswlibIndexParser/index.js
new file mode 100644
index 0000000..53be995
--- /dev/null
+++ b/federjs_old/FederCore/parser/hnswlibIndexParser/index.js
@@ -0,0 +1,97 @@
+import HNSWlibFileReader from './HNSWlibFileReader.js';
+import { INDEX_TYPE } from 'Types';
+import { generateArray } from 'Utils';
+
+export const hnswlibIndexParser = (arrayBuffer) => {
+ const reader = new HNSWlibFileReader(arrayBuffer);
+ const index = {};
+ index.offsetLevel0_ = reader.readUint64();
+ index.max_elements_ = reader.readUint64();
+ index.cur_element_count = reader.readUint64();
+ // index.ntotal = index.cur_element_count; // consistent with Faiss
+ index.size_data_per_element_ = reader.readUint64();
+ index.label_offset_ = reader.readUint64();
+ index.offsetData_ = reader.readUint64();
+ index.dim = (index.size_data_per_element_ - index.offsetData_ - 8) / 4;
+
+ index.maxlevel_ = reader.readUint32();
+ index.enterpoint_node_ = reader.readUint32();
+ index.maxM_ = reader.readUint64();
+ index.maxM0_ = reader.readUint64();
+ index.M = reader.readUint64();
+
+ index.mult_ = reader.readFloat64();
+ index.ef_construction_ = reader.readUint64();
+
+ index.size_links_per_element_ = index.maxM_ * 4 + 4;
+ index.size_links_level0_ = index.maxM0_ * 4 + 4;
+ index.revSize_ = 1.0 / index.mult_;
+ index.ef_ = 10;
+
+ read_data_level0_memory_(reader, index);
+
+ const linkListSizes = [];
+ const linkLists_ = [];
+ for (let i = 0; i < index.cur_element_count; i++) {
+ const linkListSize = reader.readUint32();
+ linkListSizes.push(linkListSize);
+ if (linkListSize === 0) {
+ linkLists_[i] = [];
+ } else {
+ const levelCount = linkListSize / 4 / (index.maxM_ + 1);
+ linkLists_[i] = generateArray(levelCount)
+ .map((_) => reader.readUint32Array(index.maxM_ + 1))
+ .map((linkLists) => linkLists.slice(1, linkLists[0] + 1))
+ // .filter((a) => a.length > 0);
+ }
+ }
+ index.linkListSizes = linkListSizes;
+ index.linkLists_ = linkLists_;
+
+ console.assert(
+ reader.isEmpty,
+ 'HNSWlib Parser Failed. Not empty when the parser completes.'
+ );
+
+ return {
+ indexType: INDEX_TYPE.hnsw,
+ ntotal: index.cur_element_count,
+ vectors: index.vectors,
+ maxLevel: index.maxlevel_,
+ linkLists_level0_count: index.linkLists_level0_count,
+ linkLists_level_0: index.linkLists_level0,
+ linkLists_levels: index.linkLists_,
+ enterPoint: index.enterpoint_node_,
+ labels: index.externalLabel,
+ isDeleted: index.isDeleted,
+ numDeleted: index.num_deleted_,
+ M: index.M,
+ ef_construction: index.ef_construction_,
+ };
+};
+
+const read_data_level0_memory_ = (reader, index) => {
+ // size_data = links_level0[M0 + 1] * 4 + vector[dim * 4] * 4 + label[1] * 8;
+ const isDeleted = [];
+ const linkLists_level0_count = [];
+ const linkLists_level0 = [];
+ const vectors = [];
+ const externalLabel = [];
+ for (let i = 0; i < index.cur_element_count; i++) {
+ linkLists_level0_count.push(reader.readLevelOCount());
+ isDeleted.push(reader.readIsDeleted());
+ reader.readIsReused(); // Unknown use.
+ linkLists_level0.push(reader.readUint32Array(index.maxM0_));
+ vectors.push(reader.readFloat32Array(index.dim));
+ externalLabel.push(reader.readUint64());
+ // console.log(isDeleted, linkLists_level0_count);
+ }
+ index.isDeleted = isDeleted;
+ index.num_deleted_ = isDeleted.reduce((acc, cur) => acc + cur, 0);
+ index.linkLists_level0_count = linkLists_level0_count;
+ index.linkLists_level0 = linkLists_level0;
+ index.vectors = vectors;
+ index.externalLabel = externalLabel;
+};
+
+export default hnswlibIndexParser;
diff --git a/federjs_old/FederCore/parser/index.js b/federjs_old/FederCore/parser/index.js
new file mode 100644
index 0000000..728d9b8
--- /dev/null
+++ b/federjs_old/FederCore/parser/index.js
@@ -0,0 +1,16 @@
+import { SOURCE_TYPE } from 'Types';
+import hnswlibIndexParser from './hnswlibIndexParser';
+import faissIndexParser from "./faissIndexParser";
+
+const indexParserMap = {
+ [SOURCE_TYPE.hnswlib]: hnswlibIndexParser,
+ [SOURCE_TYPE.faiss]: faissIndexParser,
+};
+
+export const getIndexParser = (sourceType) => {
+ if (sourceType in indexParserMap) {
+ return indexParserMap[sourceType];
+ } else throw `No index parser for [${sourceType}]`;
+};
+
+export default getIndexParser;
diff --git a/federjs_old/FederCore/projector/index.js b/federjs_old/FederCore/projector/index.js
new file mode 100644
index 0000000..e22a2b6
--- /dev/null
+++ b/federjs_old/FederCore/projector/index.js
@@ -0,0 +1,17 @@
+import umapProject from './umap';
+
+import { PROJECT_METHOD } from 'Types';
+
+const projectorMap = {
+ [PROJECT_METHOD.umap]: umapProject,
+};
+
+export const getProjector = ({ method, params = {} }) => {
+ if (method in projectorMap) {
+ return projectorMap[method](params);
+ } else {
+ console.error(`No projector for [${method}]`)
+ }
+};
+
+export default getProjector;
diff --git a/federjs_old/FederCore/projector/umap.js b/federjs_old/FederCore/projector/umap.js
new file mode 100644
index 0000000..73da5a0
--- /dev/null
+++ b/federjs_old/FederCore/projector/umap.js
@@ -0,0 +1,32 @@
+import { UMAP } from 'umap-js';
+
+const fixedParams = {
+ nComponents: 2,
+};
+
+export const UMAP_PROJECT_PARAMETERS = {
+ nComponents:
+ 'The number of components (dimensions) to project the data to. (default 2)',
+ nEpochs:
+ 'The number of epochs to optimize embeddings via SGD. (computed automatically)',
+ nNeighbors:
+ 'The number of nearest neighbors to construct the fuzzy manifold. (default 15)',
+ minDist:
+ 'The effective minimum distance between embedded points, used with spread to control the clumped/dispersed nature of the embedding. (default 0.1)',
+ spread:
+ 'The effective scale of embedded points, used with minDist to control the clumped/dispersed nature of the embedding. (default 1.0)',
+ random:
+ 'A pseudo-random-number generator for controlling stochastic processes. (default Math.random())',
+ distanceFn: 'A custom distance function to use. (default L2)',
+ url: 'https://github.com/PAIR-code/umap-js',
+};
+
+export const umapProject = (projectParams = {}) => {
+ const params = Object.assign({}, projectParams, fixedParams);
+ return (vectors) => {
+ const umap = new UMAP(params);
+ return umap.fit(vectors);
+ };
+};
+
+export default umapProject;
diff --git a/federjs_old/FederCore/searchHandler/hnswSearch/index.js b/federjs_old/FederCore/searchHandler/hnswSearch/index.js
new file mode 100644
index 0000000..e1b0a91
--- /dev/null
+++ b/federjs_old/FederCore/searchHandler/hnswSearch/index.js
@@ -0,0 +1,78 @@
+import { getDisFunc } from 'Utils';
+import { MetricType } from 'Types';
+import searchLevelO from './searchLevel0';
+
+const hnswlibHNSWSearch = ({ index, target, params = {} }) => {
+ const { ef = 10, k = 8, metricType = MetricType.METRIC_L2 } = params;
+ const disfunc = getDisFunc(metricType);
+
+ let topkResults = [];
+ const vis_records_all = [];
+
+ const {
+ enterPoint,
+ vectors,
+ maxLevel,
+ linkLists_levels,
+ linkLists_level_0,
+ numDeleted,
+ labels,
+ } = index;
+
+ let curNodeId = enterPoint;
+ let curDist = disfunc(vectors[curNodeId], target);
+
+ for (let level = maxLevel; level > 0; level--) {
+ const vis_records = [];
+ vis_records.push([labels[curNodeId], labels[curNodeId], curDist]);
+ let changed = true;
+ while (changed) {
+ changed = false;
+
+ const curlinks = linkLists_levels[curNodeId][level - 1];
+
+ curlinks.forEach((candidateId) => {
+ const dist = disfunc(vectors[candidateId], target);
+ vis_records.push([labels[curNodeId], labels[candidateId], dist]);
+ if (dist < curDist) {
+ curDist = dist;
+ curNodeId = candidateId;
+ changed = true;
+ }
+ });
+ }
+ vis_records_all.push(vis_records);
+ }
+
+ const hasDeleted = numDeleted > 0;
+ const { top_candidates, vis_records_level_0 } = searchLevelO({
+ ep_id: curNodeId,
+ target,
+ vectors,
+ ef: Math.max(ef, k),
+ hasDeleted,
+ linkLists_level_0,
+ disfunc,
+ labels,
+ });
+ vis_records_all.push(vis_records_level_0);
+
+ while (top_candidates.size > k) {
+ top_candidates.pop();
+ }
+
+ while (top_candidates.size > 0) {
+ const res = top_candidates.pop();
+ topkResults.push({
+ id: labels[res[1]],
+ internalId: res[1],
+ dis: -res[0],
+ });
+ }
+
+ topkResults = topkResults.reverse();
+
+ return { vis_records: vis_records_all, topkResults, searchParams: { k, ef } };
+};
+
+export default hnswlibHNSWSearch;
diff --git a/federjs_old/FederCore/searchHandler/hnswSearch/searchLevel0.js b/federjs_old/FederCore/searchHandler/hnswSearch/searchLevel0.js
new file mode 100644
index 0000000..a835758
--- /dev/null
+++ b/federjs_old/FederCore/searchHandler/hnswSearch/searchLevel0.js
@@ -0,0 +1,82 @@
+
+import PriorityQueue from 'Utils/PriorityQueue';
+
+export const searchLevelO = ({
+ ep_id,
+ target,
+ vectors,
+ ef,
+ isDeleted,
+ hasDeleted,
+ linkLists_level_0,
+ disfunc,
+ labels,
+}) => {
+ const top_candidates = new PriorityQueue([], (d) => d[0]);
+ const candidates = new PriorityQueue([], (d) => d[0]);
+ const vis_records_level_0 = [];
+
+ const visited = new Set();
+
+ let lowerBound;
+ if (!hasDeleted || !isDeleted[ep_id]) {
+ const dist = disfunc(vectors[ep_id], target);
+ lowerBound = dist;
+ top_candidates.add([-dist, ep_id]);
+ candidates.add([dist, ep_id]);
+ } else {
+ lowerBound = 9999999;
+ candidates.add([lowerBound, ep_id]);
+ }
+
+ visited.add(ep_id);
+ vis_records_level_0.push([labels[ep_id], labels[ep_id], lowerBound]);
+
+ while (!candidates.isEmpty) {
+ const curNodePair = candidates.top;
+ if (
+ curNodePair[0] > lowerBound &&
+ (top_candidates.size === ef || !hasDeleted)
+ ) {
+ break;
+ }
+ candidates.pop();
+
+ const curNodeId = curNodePair[1];
+ const curLinks = linkLists_level_0[curNodeId];
+
+ curLinks.forEach((candidateId) => {
+ if (!visited.has(candidateId)) {
+ visited.add(candidateId);
+
+ const dist = disfunc(vectors[candidateId], target);
+ vis_records_level_0.push([
+ labels[curNodeId],
+ labels[candidateId],
+ dist,
+ ]);
+
+ if (top_candidates.size < ef || lowerBound > dist) {
+ candidates.add([dist, candidateId]);
+
+ if (!hasDeleted || !isDeleted(candidateId)) {
+ top_candidates.add([-dist, candidateId]);
+ }
+
+ if (top_candidates.size > ef) {
+ top_candidates.pop();
+ }
+
+ if (!top_candidates.isEmpty) {
+ lowerBound = -top_candidates.top[0];
+ }
+ }
+ } else {
+ vis_records_level_0.push([labels[curNodeId], labels[candidateId], -1]);
+ }
+ });
+ }
+ return { top_candidates, vis_records_level_0 };
+};
+
+export default searchLevelO;
\ No newline at end of file
diff --git a/federjs_old/FederCore/searchHandler/index.js b/federjs_old/FederCore/searchHandler/index.js
new file mode 100644
index 0000000..58cde6c
--- /dev/null
+++ b/federjs_old/FederCore/searchHandler/index.js
@@ -0,0 +1,17 @@
+import hnswSearchHandler from './hnswSearch';
+import ivfflatSearchHandler from './ivfflatSearch';
+import { INDEX_TYPE } from 'Types';
+
+const indexSearchHandlerMap = {
+ [INDEX_TYPE.hnsw]: hnswSearchHandler,
+ [INDEX_TYPE.ivf_flat]: ivfflatSearchHandler,
+};
+
+export const getIndexSearchHandler = (indexType) => {
+ if (indexType in indexSearchHandlerMap) {
+ return indexSearchHandlerMap[indexType];
+ } else throw `No search handler for [${indexType}]`;
+};
+
+export default getIndexSearchHandler;
+
diff --git a/federjs_old/FederCore/searchHandler/ivfflatSearch/faissFlatSearch.js b/federjs_old/FederCore/searchHandler/ivfflatSearch/faissFlatSearch.js
new file mode 100644
index 0000000..d480f77
--- /dev/null
+++ b/federjs_old/FederCore/searchHandler/ivfflatSearch/faissFlatSearch.js
@@ -0,0 +1,13 @@
+import { getDisFunc } from 'Utils';
+
+export const faissFlatSearch = ({ index, target}) => {
+ const disFunc = getDisFunc(index.metricType);
+ const distances = index.vectors.map((vec, id) => ({
+ id,
+ dis: disFunc(vec, target),
+ }));
+ distances.sort((a, b) => a.dis - b.dis);
+ return distances;
+};
+
+export default faissFlatSearch;
diff --git a/federjs_old/FederCore/searchHandler/ivfflatSearch/faissIVFSearch.js b/federjs_old/FederCore/searchHandler/ivfflatSearch/faissIVFSearch.js
new file mode 100644
index 0000000..1904b2c
--- /dev/null
+++ b/federjs_old/FederCore/searchHandler/ivfflatSearch/faissIVFSearch.js
@@ -0,0 +1,23 @@
+import { getDisFunc } from 'Utils';
+
+export const faissIVFSearch = ({ index, csListIds, target }) => {
+ const disFunc = getDisFunc(index.metricType);
+ const distances = index.invlists.data.reduce(
+ (acc, cur, listId) =>
+ acc.concat(
+ csListIds.includes(listId)
+ ? cur.ids.map((id, ofs) => ({
+ id,
+ listId,
+ dis: disFunc(cur.vectors[ofs], target),
+ // vec: cur.vectors[ofs]
+ }))
+ : []
+ ),
+ []
+ );
+ distances.sort((a, b) => a.dis - b.dis);
+ return distances;
+};
+
+export default faissIVFSearch;
diff --git a/federjs_old/FederCore/searchHandler/ivfflatSearch/index.js b/federjs_old/FederCore/searchHandler/ivfflatSearch/index.js
new file mode 100644
index 0000000..48c0b1f
--- /dev/null
+++ b/federjs_old/FederCore/searchHandler/ivfflatSearch/index.js
@@ -0,0 +1,39 @@
+import faissFlatSearch from './faissFlatSearch.js';
+import faissIVFSearch from './faissIVFSearch.js';
+
+export const faissIVFFlatSearch = ({ index, target, params = {} }) => {
+ const { nprobe = 8, k = 10 } = params;
+
+ // cs: coarse-search
+ // fs: fine-search
+ const csAllListIdsAndDistances = faissFlatSearch({
+ index: index.childIndex,
+ target,
+ });
+ const csRes = csAllListIdsAndDistances.slice(
+ 0,
+ Math.min(index.nlist, nprobe)
+ );
+ const csListIds = csRes.map((res) => res.id);
+
+ const fsAllIdsAndDistances = faissIVFSearch({
+ index,
+ csListIds,
+ target,
+ });
+ // console.log('fsResProjections', fsResProjections);
+ const fsRes = fsAllIdsAndDistances.slice(0, Math.min(index.ntotal, k));
+
+ const coarse = csAllListIdsAndDistances;
+ const fine = fsAllIdsAndDistances;
+ const res = {
+ coarse,
+ fine,
+ csResIds: csListIds,
+ fsResIds: fsRes.map((d) => d.id),
+ };
+
+ return res;
+};
+
+export default faissIVFFlatSearch;
diff --git a/federjs_old/FederView/BaseView.js b/federjs_old/FederView/BaseView.js
new file mode 100644
index 0000000..abfa801
--- /dev/null
+++ b/federjs_old/FederView/BaseView.js
@@ -0,0 +1,109 @@
+import * as d3 from 'd3';
+import { renderLoading, finishLoading } from './loading';
+// import { VIEW_TYPE } from 'Types';
+
+export default class BaseView {
+ constructor({ viewParams, getVectorById }) {
+ this.viewParams = viewParams;
+
+ const { width, height, canvasScale, mediaType, mediaCallback } = viewParams;
+ this.clientWidth = width;
+ this.width = width * canvasScale;
+ this.clientHeight = height;
+ this.height = height * canvasScale;
+ this.getVectorById = getVectorById;
+ this.canvasScale = canvasScale;
+ this.mediaType = mediaType;
+ this.mediaCallback = mediaCallback;
+ }
+
+ // override
+ initInfoPanel() {}
+ renderOverview() {}
+ renderSearchView() {}
+ searchViewHandler() {}
+ getOverviewEventHandler() {}
+ getSearchViewEventHandler() {}
+
+ async overview(dom) {
+ const canvas = initCanvas(
+ dom,
+ this.clientWidth,
+ this.clientHeight,
+ this.canvasScale
+ );
+ const ctx = canvas.getContext('2d');
+ const infoPanel = this.initInfoPanel(dom);
+
+ this.overviewLayoutPromise && (await this.overviewLayoutPromise);
+ finishLoading(dom);
+ this.renderOverview(ctx, infoPanel);
+ const eventHandlers = this.getOverviewEventHandler(ctx, infoPanel);
+ addMouseListener(canvas, this.canvasScale, eventHandlers);
+ }
+
+ async search(dom, { searchRes, targetMediaUrl }) {
+ const canvas = initCanvas(
+ dom,
+ this.clientWidth,
+ this.clientHeight,
+ this.canvasScale
+ );
+ const ctx = canvas.getContext('2d');
+ const infoPanel = this.initInfoPanel(dom);
+
+ const searchViewLayoutData = await this.searchViewHandler(searchRes);
+ finishLoading(dom);
+ this.renderSearchView(
+ ctx,
+ infoPanel,
+ searchViewLayoutData,
+ targetMediaUrl,
+ dom
+ );
+ const eventHandlers = this.getSearchViewEventHandler(
+ ctx,
+ searchViewLayoutData,
+ infoPanel
+ );
+ addMouseListener(canvas, this.canvasScale, eventHandlers);
+ }
+}
+
+const addMouseListener = (
+ element,
+ canvasScale,
+ { mouseMoveHandler, mouseClickHandler, mouseLeaveHandler } = {}
+) => {
+ element.addEventListener('mousemove', (e) => {
+ const { offsetX, offsetY } = e;
+ const x = offsetX * canvasScale;
+ const y = offsetY * canvasScale;
+ mouseMoveHandler && mouseMoveHandler({ x, y });
+ });
+ element.addEventListener('click', (e) => {
+ const { offsetX, offsetY } = e;
+ const x = offsetX * canvasScale;
+ const y = offsetY * canvasScale;
+ mouseClickHandler && mouseClickHandler({ x, y });
+ });
+ element.addEventListener('mouseleave', () => {
+ mouseLeaveHandler && mouseLeaveHandler();
+ });
+};
+
+const initCanvas = (dom, clientWidth, clientHeight, canvasScale) => {
+ renderLoading(dom, clientWidth, clientHeight);
+
+ const domD3 = d3.select(dom);
+ domD3.selectAll('canvas').remove();
+
+ const canvas = domD3
+ .append('canvas')
+ .attr('width', clientWidth)
+ .attr('height', clientHeight);
+ const ctx = canvas.node().getContext('2d');
+ ctx.scale(1 / canvasScale, 1 / canvasScale);
+
+ return canvas.node();
+};
diff --git a/federjs_old/FederView/HnswView/InfoPanel/index.js b/federjs_old/FederView/HnswView/InfoPanel/index.js
new file mode 100644
index 0000000..683f55e
--- /dev/null
+++ b/federjs_old/FederView/HnswView/InfoPanel/index.js
@@ -0,0 +1,425 @@
+import * as d3 from 'd3';
+
+import {
+ ZYellow,
+ ZBlue,
+ whiteColor,
+ blackColor,
+ drawPath,
+ hexWithOpacity,
+} from 'Utils/renderUtils';
+
+import { showVectors } from 'Utils';
+import { HNSW_NODE_TYPE } from 'Types';
+
+export const overviewPanelId = 'feder-info-overview-panel';
+export const selectedPanelId = 'feder-info-selected-panel';
+export const hoveredPanelId = 'feder-info-hovered-panel';
+
+const panelBackgroundColor = hexWithOpacity(blackColor, 0.6);
+
+export default class InfoPanel {
+ constructor({ dom, width, height }) {
+ this.dom = dom;
+ this.width = width;
+ this.height = height;
+
+ const overviewPanel = document.createElement('div');
+ overviewPanel.setAttribute('id', overviewPanelId);
+ overviewPanel.className =
+ overviewPanel.className + ' panel-border panel hide';
+ const overviewPanelStyle = {
+ position: 'absolute',
+ // left: `${canvas.width - 10}px`,
+ left: '16px',
+ top: '10px',
+ width: '280px',
+ 'max-height': `${height - 20}px`,
+ overflow: 'auto',
+ borderColor: whiteColor,
+ backgroundColor: panelBackgroundColor,
+ };
+ Object.assign(overviewPanel.style, overviewPanelStyle);
+ dom.appendChild(overviewPanel);
+
+ const selectedPanel = document.createElement('div');
+ selectedPanel.setAttribute('id', selectedPanelId);
+ selectedPanel.className =
+ selectedPanel.className + ' panel-border panel hide';
+ const selectedPanelStyle = {
+ position: 'absolute',
+ // left: `${canvas.width - 10}px`,
+ right: '16px',
+ top: '10px',
+ 'max-width': '180px',
+ 'max-height': `${height - 20}px`,
+ overflow: 'auto',
+ borderColor: ZYellow,
+ backgroundColor: panelBackgroundColor,
+ };
+ Object.assign(selectedPanel.style, selectedPanelStyle);
+ dom.appendChild(selectedPanel);
+
+ const hoveredPanel = document.createElement('div');
+ hoveredPanel.setAttribute('id', hoveredPanelId);
+ hoveredPanel.className = hoveredPanel.className + ' hide';
+ const hoveredPanelStyle = {
+ position: 'absolute',
+ left: 0,
+ // right: '30px',
+ top: 0,
+ width: '240px',
+ display: 'flex',
+ pointerEvents: 'none',
+ };
+ Object.assign(hoveredPanel.style, hoveredPanelStyle);
+ dom.appendChild(hoveredPanel);
+
+ this.initStyle();
+ }
+
+ initStyle() {
+ const style = document.createElement('style');
+ style.type = 'text/css';
+ style.innerHTML = `
+ .panel-border {
+ border-style: dashed;
+ border-width: 1px;
+ }
+ .panel {
+ padding: 6px 8px;
+ font-size: 12px;
+ }
+ .hide {
+ opacity: 0;
+ }
+ .panel-item {
+ margin-bottom: 6px;
+ }
+ .panel-img {
+ width: 150px;
+ height: 100px;
+ background-size: cover;
+ margin-bottom: 12px;
+ border-radius: 4px;
+ border: 1px solid ${ZYellow};
+ }
+ .panel-item-display-flex {
+ display: flex;
+ }
+ .panel-item-title {
+ font-weight: 600;
+ margin-bottom: 3px;
+ }
+ .panel-item-text {
+ font-weight: 400;
+ font-size: 10px;
+ word-break: break-all;
+ }
+ .panel-item-text-flex {
+ margin-left: 8px;
+ }
+ .panel-item-text-margin {
+ margin: 0 6px;
+ }
+ .text-no-wrap {
+ white-space: nowrap;
+ }
+ `;
+ document.getElementsByTagName('head').item(0).appendChild(style);
+ }
+
+ renderSelectedPanel(itemList = [], color = '#000') {
+ const panel = d3.select(this.dom).select(`#${selectedPanelId}`);
+ panel.style('color', color);
+ if (itemList.length === 0) panel.classed('hide', true);
+ else {
+ this.renderPanel(panel, itemList);
+ }
+ }
+ renderHoveredPanel({
+ itemList = [],
+ canvasScale = 1,
+ color = '#000',
+ x = 0,
+ y = 0,
+ isLeft = false,
+ } = {}) {
+ const panel = d3.select(this.dom).select(`#${hoveredPanelId}`);
+ if (itemList.length === 0) panel.classed('hide', true);
+ else {
+ panel.style('color', color);
+ // panel.style.left = x + 'px';
+ // panel.style.top = y + 'px';
+ if (isLeft) {
+ panel.style('left', null);
+ panel.style('right', this.width - x / canvasScale + 'px');
+ panel.style('flex-direction', 'row-reverse');
+ } else {
+ panel.style('left', x / canvasScale + 'px');
+ panel.style('flex-direction', 'row');
+ }
+
+ panel.style('transform', `translateY(-6px)`);
+
+ panel.style('top', y / canvasScale + 'px');
+ this.renderPanel(panel, itemList);
+ }
+ }
+ renderOverviewPanel(itemList = [], color) {
+ const panel = d3.select(this.dom).select(`#${overviewPanelId}`);
+ panel.style('color', color);
+ if (itemList.length === 0) panel.classed('hide', true);
+ else {
+ this.renderPanel(panel, itemList);
+ }
+ }
+ renderPanel(panel, itemList) {
+ panel.classed('hide', false);
+ panel.selectAll('*').remove();
+
+ itemList.forEach((item) => {
+ const div = panel.append('div');
+ div.classed('panel-item', true);
+ item.isFlex && div.classed('panel-item-display-flex', true);
+ if (item.isImg && item.imgUrl) {
+ div.classed('panel-img', true);
+ div.style('background-image', `url(${item.imgUrl})`);
+ }
+ if (item.title) {
+ const title = div.append('div');
+ title.classed('panel-item-title', true);
+ title.text(item.title);
+ }
+ if (item.text) {
+ const title = div.append('div');
+ title.classed('panel-item-text', true);
+ item.isFlex && title.classed('panel-item-text-flex', true);
+ item.textWithMargin && title.classed('panel-item-text-margin', true);
+ item.noWrap && title.classed('text-no-wrap', true);
+ title.text(item.text);
+ }
+ });
+ }
+
+ updateOverviewOverviewInfo({ indexMeta, nodesCount, linksCount }) {
+ const overviewInfo = [
+ {
+ title: 'HNSW',
+ },
+ {
+ title: `M = ${indexMeta.M}, ef_construction = ${indexMeta.ef_construction}`,
+ },
+ {
+ title: `${indexMeta.ntotal} vectors, including ${indexMeta.levelCount} levels (only show the top 3 levels).`,
+ },
+ ];
+ for (let level = indexMeta.overviewLevelCount - 1; level >= 0; level--) {
+ overviewInfo.push({
+ isFlex: true,
+ title: `Level ${
+ level + indexMeta.levelCount - indexMeta.overviewLevelCount
+ }`,
+ text: `${nodesCount[level]} vectors, ${linksCount[level]} links`,
+ });
+ }
+ this.renderOverviewPanel(overviewInfo, whiteColor);
+ }
+ async updateOverviewClickedInfo(
+ node,
+ level,
+ { indexMeta, mediaType, mediaCallback, getVectorById }
+ ) {
+ const itemList = [];
+ if (node) {
+ itemList.push({
+ title: `Level ${
+ level + indexMeta.levelCount - indexMeta.overviewLevelCount
+ }`,
+ });
+ itemList.push({
+ title: `Row No. ${node.id}`,
+ });
+ mediaType === 'img' &&
+ itemList.push({
+ isImg: true,
+ imgUrl: mediaCallback(node.id),
+ });
+ itemList.push({
+ title: `Shortest path from the entry:`,
+ text: `${[...node.path, node.id].join(' => ')}`,
+ });
+ itemList.push({
+ title: `Linked vectors:`,
+ text: `${node.linksLevels[level].join(', ')}`,
+ });
+ itemList.push({
+ title: `Vectors:`,
+ text: `${showVectors(await getVectorById(node.id))}`,
+ });
+ }
+ this.renderSelectedPanel(itemList, ZYellow);
+ }
+ updateOverviewHoveredInfo(
+ hoveredNode,
+ { isLeft, endX, endY },
+ { mediaType, mediaCallback, canvasScale }
+ ) {
+ if (!!hoveredNode) {
+ const itemList = [];
+ itemList.push({
+ text: `No. ${hoveredNode.id}`,
+ textWithMargin: true,
+ noWrap: true,
+ });
+ mediaType === 'img' &&
+ itemList.push({
+ isImg: true,
+ imgUrl: mediaCallback(hoveredNode.id),
+ });
+
+ this.renderHoveredPanel({
+ itemList,
+ color: ZYellow,
+ x: endX,
+ y: endY,
+ isLeft,
+ canvasScale,
+ });
+ } else {
+ this.renderHoveredPanel();
+ }
+ }
+
+ updateSearchViewOverviewInfo(
+ {
+ targetMediaUrl,
+ id2forcePos,
+ searchNodesLevels,
+ searchLinksLevels,
+ searchParams,
+ },
+ { indexMeta }
+ ) {
+ const overviewInfo = [
+ {
+ title: 'HNSW - Search',
+ },
+ {
+ isImg: true,
+ imgUrl: targetMediaUrl,
+ },
+ {
+ title: `M = ${indexMeta.M}, ef_construction = ${indexMeta.ef_construction}.`,
+ },
+ {
+ title: `k = ${searchParams.k}, ef_search = ${searchParams.ef}.`,
+ },
+ {
+ title: `${indexMeta.ntotal} vectors, including ${indexMeta.levelCount} levels.`,
+ },
+ {
+ title: `During the search, a total of ${
+ Object.keys(id2forcePos).length - 1
+ } of these vectors were visited.`,
+ },
+ ];
+ for (let level = indexMeta.levelCount - 1; level >= 0; level--) {
+ const nodes = searchNodesLevels[level];
+ const links = searchLinksLevels[level];
+ const minDist =
+ level > 0
+ ? nodes
+ .find((node) => node.type === HNSW_NODE_TYPE.Fine)
+ .dist.toFixed(3)
+ : d3.min(
+ nodes.filter((node) => node.type === HNSW_NODE_TYPE.Fine),
+ (node) => node.dist.toFixed(3)
+ );
+ overviewInfo.push({
+ isFlex: true,
+ title: `Level ${level}`,
+ text: `${nodes.length} vectors, ${links.length} links, min-dist: ${minDist}`,
+ });
+ }
+ this.renderOverviewPanel(overviewInfo, whiteColor);
+ }
+ updateSearchViewHoveredInfo(
+ { hoveredNode, hoveredLevel },
+ {
+ mediaType,
+ mediaCallback,
+ width,
+ padding,
+
+ HoveredPanelLine_1_x,
+ HoveredPanelLine_1_y,
+ HoveredPanelLine_2_x,
+ canvasScale,
+ }
+ ) {
+ if (!hoveredNode) {
+ this.renderHoveredPanel();
+ } else {
+ const [x, y] = hoveredNode.searchViewPosLevels[hoveredLevel];
+ const originX = (width - padding[1] - padding[3]) / 2 + padding[3];
+ const isLeft = originX > x;
+ const k = isLeft ? -1 : 1;
+ const endX =
+ x +
+ HoveredPanelLine_1_x * canvasScale * k +
+ HoveredPanelLine_2_x * canvasScale * k;
+ const endY = y + HoveredPanelLine_1_y * canvasScale * k;
+
+ const itemList = [];
+ itemList.push({
+ text: `No. ${hoveredNode.id}`,
+ textWithMargin: true,
+ noWrap: true,
+ });
+ mediaType === 'img' &&
+ itemList.push({
+ isImg: true,
+ imgUrl: mediaCallback(hoveredNode.id),
+ });
+
+ this.renderHoveredPanel({
+ itemList,
+ color: ZYellow,
+ x: endX,
+ y: endY,
+ isLeft,
+ canvasScale,
+ });
+ }
+ }
+ async updateSearchViewClickedInfo(
+ { clickedNode, clickedLevel },
+ { mediaType, mediaCallback, getVectorById }
+ ) {
+ const itemList = [];
+ if (!clickedNode) {
+ this.renderSelectedPanel([], ZYellow);
+ } else {
+ itemList.push({
+ title: `Level ${clickedLevel}`,
+ });
+ itemList.push({
+ title: `Row No. ${clickedNode.id}`,
+ });
+ itemList.push({
+ title: `Distance to the target: ${clickedNode.dist.toFixed(3)}`,
+ });
+ mediaType === 'img' &&
+ itemList.push({
+ isImg: true,
+ imgUrl: mediaCallback(clickedNode.id),
+ });
+ itemList.push({
+ title: `Vector:`,
+ text: `${showVectors(await getVectorById(clickedNode.id))}`,
+ });
+ }
+ this.renderSelectedPanel(itemList, ZYellow);
+ }
+}
diff --git a/federjs_old/FederView/HnswView/index.js b/federjs_old/FederView/HnswView/index.js
new file mode 100644
index 0000000..5d6e740
--- /dev/null
+++ b/federjs_old/FederView/HnswView/index.js
@@ -0,0 +1,257 @@
+import * as d3 from 'd3';
+import BaseView from '../BaseView.js';
+import overviewLayoutHandler from './layout/overviewLayout';
+import mouse2node from './layout/mouse2node';
+import renderOverview from './render/renderOverview';
+import getOverviewShortestPathData from './layout/overviewShortestPath';
+import searchViewLayoutHandler from './layout/searchViewLayout';
+import TimeControllerView from './render/TimeControllerView';
+import TimerController from './render/TimerController';
+import renderHoverLine from './render/renderHoverLine';
+import renderSearchViewTransition from './render/renderSearchViewTransition';
+import InfoPanel from './InfoPanel';
+
+const defaultHnswViewParams = {
+ padding: [80, 200, 60, 220],
+ forceTime: 3000,
+ layerDotNum: 20,
+ shortenLineD: 8,
+ overviewLinkLineWidth: 2,
+ reachableLineWidth: 3,
+ shortestPathLineWidth: 4,
+ ellipseRation: 1.4,
+ shadowBlur: 4,
+ mouse2nodeBias: 3,
+ highlightRadiusExt: 0.5,
+ targetR: 3,
+ searchViewNodeBasicR: 1.5,
+ searchInterLevelTime: 300,
+ searchIntraLevelTime: 100,
+ HoveredPanelLine_1_x: 15,
+ HoveredPanelLine_1_y: -25,
+ HoveredPanelLine_2_x: 30,
+ hoveredPanelLineWidth: 2,
+ forceIterations: 100,
+ targetOrigin: [0, 0],
+};
+export default class HnswView extends BaseView {
+ constructor({ indexMeta, viewParams, getVectorById }) {
+ super({
+ indexMeta,
+ viewParams,
+ getVectorById,
+ });
+ for (let key in defaultHnswViewParams) {
+ this[key] =
+ key in viewParams ? viewParams[key] : defaultHnswViewParams[key];
+ }
+ this.padding = this.padding.map((num) => num * this.canvasScale);
+
+ this.overviewHandler(indexMeta);
+ }
+ initInfoPanel(dom) {
+ const infoPanel = new InfoPanel({
+ dom,
+ width: this.viewParams.width,
+ height: this.viewParams.height,
+ });
+ return infoPanel;
+ }
+ overviewHandler(indexMeta) {
+ console.log(indexMeta);
+ this.indexMeta = indexMeta;
+ Object.assign(this, indexMeta);
+
+ const internalId2overviewNode = {};
+ this.overviewNodes.forEach(
+ (node) => (internalId2overviewNode[node.internalId] = node)
+ );
+ this.internalId2overviewNode = internalId2overviewNode;
+
+ this.overviewLayoutPromise = overviewLayoutHandler(this).then(
+ ({
+ overviewLayerPosLevels,
+ overviewNodesLevels,
+ overviewLinksLevels,
+ }) => {
+ this.overviewLayerPosLevels = overviewLayerPosLevels;
+ this.overviewNodesLevels = overviewNodesLevels;
+ this.overviewLinksLevels = overviewLinksLevels;
+ }
+ );
+ }
+ renderOverview(ctx, infoPanel) {
+ const indexMeta = this.indexMeta;
+ const nodesCount = this.overviewNodesLevels.map(
+ (nodesLevel) => nodesLevel.length
+ );
+ const linksCount = this.overviewLinksLevels.map(
+ (linksLevel) => linksLevel.length
+ );
+ const overviewInfo = { indexMeta, nodesCount, linksCount };
+ infoPanel.updateOverviewOverviewInfo(overviewInfo);
+
+ // this.searchTransitionTimer && this.searchTransitionTimer.stop();
+ renderOverview(ctx, this);
+ }
+ getOverviewEventHandler(ctx, infoPanel) {
+ let clickedNode = null;
+ let clickedLevel = null;
+ let hoveredNode = null;
+ let hoveredLevel = null;
+ let overviewHighlightData = null;
+ const mouseMoveHandler = ({ x, y }) => {
+ const mouse = [x, y];
+ const { mouseLevel, mouseNode } = mouse2node(
+ {
+ mouse,
+ layerPosLevels: this.overviewLayerPosLevels,
+ nodesLevels: this.overviewNodesLevels,
+ posAttr: 'overviewPosLevels',
+ },
+ this
+ );
+ const hoveredNodeChanged =
+ hoveredLevel !== mouseLevel || hoveredNode !== mouseNode;
+ hoveredNode = mouseNode;
+ hoveredLevel = mouseLevel;
+
+ if (hoveredNodeChanged) {
+ if (!clickedNode) {
+ overviewHighlightData = getOverviewShortestPathData(
+ mouseNode,
+ mouseLevel,
+ this
+ );
+ }
+ renderOverview(ctx, this, overviewHighlightData);
+ const hoveredPanelPos = renderHoverLine(
+ ctx,
+ {
+ hoveredNode,
+ hoveredLevel,
+ clickedNode,
+ clickedLevel,
+ },
+ this
+ );
+ infoPanel.updateOverviewHoveredInfo(mouseNode, hoveredPanelPos, this);
+ }
+ };
+ const mouseClickHandler = ({ x, y }) => {
+ const mouse = [x, y];
+ const { mouseLevel, mouseNode } = mouse2node(
+ {
+ mouse,
+ layerPosLevels: this.overviewLayerPosLevels,
+ nodesLevels: this.overviewNodesLevels,
+ posAttr: 'overviewPosLevels',
+ },
+ this
+ );
+ const clickedNodeChanged =
+ clickedLevel !== mouseLevel || clickedNode !== mouseNode;
+ clickedNode = mouseNode;
+ clickedLevel = mouseLevel;
+
+ if (clickedNodeChanged) {
+ overviewHighlightData = getOverviewShortestPathData(
+ mouseNode,
+ mouseLevel,
+ this
+ );
+ renderOverview(ctx, this, overviewHighlightData);
+ infoPanel.updateOverviewClickedInfo(mouseNode, mouseLevel, this);
+ }
+ };
+ return { mouseMoveHandler, mouseClickHandler };
+ }
+
+ searchViewHandler(searchRes) {
+ return searchViewLayoutHandler(searchRes, this);
+ }
+ renderSearchView(ctx, infoPanel, searchViewLayoutData, targetMediaUrl, dom) {
+ searchViewLayoutData.targetMediaUrl = targetMediaUrl;
+ infoPanel.updateSearchViewOverviewInfo(searchViewLayoutData, this);
+ const timeControllerView = new TimeControllerView(dom);
+
+ const callback = ({ t, p }) => {
+ renderSearchViewTransition(ctx, searchViewLayoutData, this, {
+ t,
+ p,
+ });
+ timeControllerView.moveSilderBar(p);
+ };
+ const timer = new TimerController({
+ duration: searchViewLayoutData.searchTransitionDuration,
+ callback,
+ playCallback: () => timeControllerView.play(),
+ pauseCallback: () => timeControllerView.pause(),
+ });
+ timeControllerView.setTimer(timer);
+ timer.start();
+ searchViewLayoutData.searchTransitionTimer = timer;
+ }
+ getSearchViewEventHandler(ctx, searchViewLayoutData, infoPanel) {
+ searchViewLayoutData.clickedLevel = null;
+ searchViewLayoutData.clickedNode = null;
+ searchViewLayoutData.hoveredLevel = null;
+ searchViewLayoutData.hoveredNode = null;
+
+ const mouseMoveHandler = ({ x, y }) => {
+ const mouse = [x, y];
+ const { mouseLevel, mouseNode } = mouse2node(
+ {
+ mouse,
+ layerPosLevels: searchViewLayoutData.searchLayerPosLevels,
+ nodesLevels: searchViewLayoutData.searchNodesLevels,
+ posAttr: 'searchViewPosLevels',
+ },
+ this
+ );
+ const hoveredNodeChanged =
+ searchViewLayoutData.hoveredLevel !== mouseLevel ||
+ searchViewLayoutData.hoveredNode !== mouseNode;
+ searchViewLayoutData.hoveredNode = mouseNode;
+ searchViewLayoutData.hoveredLevel = mouseLevel;
+
+ if (hoveredNodeChanged) {
+ infoPanel.updateSearchViewHoveredInfo(searchViewLayoutData, this);
+ if (!searchViewLayoutData.searchTransitionTimer.isPlaying) {
+ renderSearchViewTransition(ctx, searchViewLayoutData, this, {
+ t: searchViewLayoutData.searchTransitionTimer.tAlready,
+ });
+ }
+ }
+ };
+
+ const mouseClickHandler = ({ x, y }) => {
+ const mouse = [x, y];
+ const { mouseLevel, mouseNode } = mouse2node(
+ {
+ mouse,
+ layerPosLevels: searchViewLayoutData.searchLayerPosLevels,
+ nodesLevels: searchViewLayoutData.searchNodesLevels,
+ posAttr: 'searchViewPosLevels',
+ },
+ this
+ );
+ const clickedNodeChanged =
+ searchViewLayoutData.clickedLevel !== mouseLevel ||
+ searchViewLayoutData.clickedNode !== mouseNode;
+ searchViewLayoutData.clickedNode = mouseNode;
+ searchViewLayoutData.clickedLevel = mouseLevel;
+
+ if (clickedNodeChanged) {
+ infoPanel.updateSearchViewClickedInfo(searchViewLayoutData, this);
+ if (!searchViewLayoutData.searchTransitionTimer.isPlaying) {
+ renderSearchViewTransition(ctx, searchViewLayoutData, this, {
+ t: searchViewLayoutData.searchTransitionTimer.tAlready,
+ });
+ }
+ }
+ };
+
+ return { mouseMoveHandler, mouseClickHandler };
+ }
+}
diff --git a/federjs_old/FederView/HnswView/layout/computeSearchViewTransition.js b/federjs_old/FederView/HnswView/layout/computeSearchViewTransition.js
new file mode 100644
index 0000000..9d914c9
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/computeSearchViewTransition.js
@@ -0,0 +1,86 @@
+import {
+ getNodeIdWithLevel,
+ getLinkIdWithLevel,
+ getEntryLinkIdWithLevel,
+} from 'Utils';
+
+import { HNSW_LINK_TYPE } from 'Types';
+
+export const computeSearchViewTransition = ({
+ linksLevels,
+ entryNodesLevels,
+ interLevelGap = 1000,
+ intraLevelGap = 300,
+}) => {
+ let currentTime = 0;
+ const targetShowTime = [];
+ const nodeShowTime = {};
+ const linkShowTime = {};
+
+ let isPreLinkImportant = true;
+ let isSourceChanged = true;
+ let preSourceIdWithLevel = '';
+ for (let level = linksLevels.length - 1; level >= 0; level--) {
+ const links = linksLevels[level];
+ if (links.length === 0) {
+ const sourceId = entryNodesLevels[level].id;
+ const sourceIdWithLevel = getNodeIdWithLevel(sourceId, level);
+ nodeShowTime[sourceIdWithLevel] = currentTime;
+ } else {
+ links.forEach((link) => {
+ const sourceId = link.source.id;
+ const targetId = link.target.id;
+ const sourceIdWithLevel = getNodeIdWithLevel(sourceId, level);
+ const targetIdWithLevel = getNodeIdWithLevel(targetId, level);
+ const linkIdWithLevel = getLinkIdWithLevel(sourceId, targetId, level);
+ const isCurrentLinkImportant =
+ link.type === HNSW_LINK_TYPE.Searched ||
+ link.type === HNSW_LINK_TYPE.Fine;
+ isSourceChanged = preSourceIdWithLevel !== sourceIdWithLevel;
+
+ const isSourceEntry = !(sourceIdWithLevel in nodeShowTime);
+ if (isSourceEntry) {
+ if (level < linksLevels.length - 1) {
+ const entryLinkIdWithLevel = getEntryLinkIdWithLevel(
+ sourceId,
+ level
+ );
+ linkShowTime[entryLinkIdWithLevel] = currentTime;
+ const targetLinkIdWithLevel = getEntryLinkIdWithLevel(
+ 'target',
+ level
+ );
+ linkShowTime[targetLinkIdWithLevel] = currentTime;
+ currentTime += interLevelGap;
+ isPreLinkImportant = true;
+ }
+ targetShowTime[level] = currentTime;
+ nodeShowTime[sourceIdWithLevel] = currentTime;
+ }
+
+ // something wrong
+ if (isPreLinkImportant || isCurrentLinkImportant || isSourceChanged) {
+ currentTime += intraLevelGap;
+ } else {
+ currentTime += intraLevelGap * 0.5;
+ }
+
+ linkShowTime[linkIdWithLevel] = currentTime;
+
+ if (!(targetIdWithLevel in nodeShowTime)) {
+ nodeShowTime[targetIdWithLevel] = currentTime += intraLevelGap;
+ }
+
+ isPreLinkImportant = isCurrentLinkImportant;
+ preSourceIdWithLevel = sourceIdWithLevel;
+ });
+ }
+
+ currentTime += intraLevelGap;
+ isPreLinkImportant = true;
+ isSourceChanged = true;
+ }
+ return { targetShowTime, nodeShowTime, linkShowTime, duration: currentTime };
+};
+
+export default computeSearchViewTransition;
diff --git a/federjs_old/FederView/HnswView/layout/forceSearchView.js b/federjs_old/FederView/HnswView/layout/forceSearchView.js
new file mode 100644
index 0000000..6863b79
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/forceSearchView.js
@@ -0,0 +1,81 @@
+import * as d3 from 'd3';
+import { HNSW_LINK_TYPE } from 'Types';
+import { deDupLink } from 'Utils';
+
+const forceSearchView = (
+ visData,
+ targetOrigin = [0, 0],
+ forceIterations = 100
+) => {
+ return new Promise((resolve) => {
+ const nodeId2dist = {};
+ visData.forEach((levelData) =>
+ levelData.nodes.forEach((node) => (nodeId2dist[node.id] = node.dist || 0))
+ );
+ const nodeIds = Object.keys(nodeId2dist);
+ const nodes = nodeIds.map((nodeId) => ({
+ nodeId: nodeId,
+ dist: nodeId2dist[nodeId],
+ }));
+
+ const linksAll = visData.reduce((acc, cur) => acc.concat(cur.links), []);
+ const links = deDupLink(linksAll);
+ // console.log(nodes, links);
+ // console.log(links.length, linksAll.length);
+
+ const targetNode = {
+ nodeId: 'target',
+ dist: 0,
+ fx: targetOrigin[0],
+ fy: targetOrigin[1],
+ };
+ nodes.push(targetNode);
+
+ const targetLinks = visData[0].fineIds.map((fineId) => ({
+ source: `${fineId}`,
+ target: 'target',
+ type: HNSW_LINK_TYPE.None,
+ }));
+ links.push(...targetLinks);
+ // console.log(nodes, links);
+
+ const rScale = d3
+ .scaleLinear()
+ .domain(
+ d3.extent(
+ nodes.filter((node) => node.dist > 0),
+ (node) => node.dist
+ )
+ )
+ .range([10, 1000])
+ .clamp(true);
+ const simulation = d3
+ .forceSimulation(nodes)
+ .alphaDecay(1 - Math.pow(0.001, 1 / forceIterations))
+ .force(
+ 'link',
+ d3
+ .forceLink(links)
+ .id((d) => `${d.nodeId}`)
+ .strength((d) => (d.type === HNSW_LINK_TYPE.None ? 2 : 0.4))
+ )
+ .force(
+ 'r',
+ d3
+ .forceRadial(
+ (node) => rScale(node.dist),
+ targetOrigin[0],
+ targetOrigin[1]
+ )
+ .strength(1)
+ )
+ .force('charge', d3.forceManyBody().strength(-10000))
+ .on('end', () => {
+ const id2forcePos = {};
+ nodes.forEach((node) => (id2forcePos[node.nodeId] = [node.x, node.y]));
+ resolve(id2forcePos);
+ });
+ });
+};
+
+export default forceSearchView;
diff --git a/federjs_old/FederView/HnswView/layout/mouse2node.js b/federjs_old/FederView/HnswView/layout/mouse2node.js
new file mode 100644
index 0000000..e340615
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/mouse2node.js
@@ -0,0 +1,27 @@
+import * as d3 from 'd3';
+import { dist2 } from 'Utils';
+
+export default function mouse2node(
+ { mouse, layerPosLevels, nodesLevels, posAttr },
+ { mouse2nodeBias, canvasScale }
+) {
+ const mouseLevel = layerPosLevels.findIndex((points) =>
+ d3.polygonContains(points, mouse)
+ );
+ let mouseNode;
+ if (mouseLevel >= 0) {
+ const allDis = nodesLevels[mouseLevel].map((node) =>
+ dist2(node[posAttr][mouseLevel], mouse)
+ );
+ const minDistIndex = d3.minIndex(allDis);
+ const minDist = allDis[minDistIndex];
+ const clearestNode = nodesLevels[mouseLevel][minDistIndex];
+ mouseNode =
+ minDist < Math.pow((clearestNode.r + mouse2nodeBias) * canvasScale, 2)
+ ? clearestNode
+ : null;
+ } else {
+ mouseNode = null;
+ }
+ return { mouseLevel, mouseNode };
+}
diff --git a/federjs_old/FederView/HnswView/layout/overviewLayout.js b/federjs_old/FederView/HnswView/layout/overviewLayout.js
new file mode 100644
index 0000000..d08620c
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/overviewLayout.js
@@ -0,0 +1,103 @@
+import * as d3 from 'd3';
+import transformHandler from './transformHandler';
+
+export const overviewLayoutHandler = ({
+ overviewNodes,
+ overviewLevelCount,
+ width,
+ height,
+ padding,
+ forceIterations,
+ M,
+}) => {
+ return new Promise(async (resolve) => {
+ const overviewNodesLevels = [];
+ const overviewLinksLevels = [];
+ for (let level = overviewLevelCount - 1; level >= 0; level--) {
+ const nodes = overviewNodes.filter(
+ (node) => node.linksLevels.length > level
+ );
+ const links = nodes.reduce(
+ (acc, curNode) =>
+ acc.concat(
+ curNode.linksLevels[level].map((targetNodeInternalId) => ({
+ source: curNode.internalId,
+ target: targetNodeInternalId,
+ }))
+ ),
+ []
+ );
+ await forceLevel({ nodes, links, forceIterations });
+ level > 0 && scaleNodes({ nodes, M });
+ level > 0 && fixedCurLevel({ nodes });
+ overviewNodesLevels[level] = nodes;
+ overviewLinksLevels[level] = links;
+ }
+
+ const { layerPosLevels: overviewLayerPosLevels, transformFunc } =
+ transformHandler(overviewNodes, {
+ levelCount: overviewLevelCount,
+ width,
+ height,
+ padding,
+ });
+
+ overviewNodes.forEach((node) => {
+ node.overviewPosLevels = node.linksLevels.map((_, level) =>
+ transformFunc(node.x, node.y, level)
+ );
+ node.r = node.linksLevels.length * 0.8 + 1;
+ });
+
+ resolve({
+ overviewLayerPosLevels,
+ overviewNodesLevels,
+ overviewLinksLevels,
+ });
+ });
+};
+
+export default overviewLayoutHandler;
+
+export const forceLevel = ({ nodes, links, forceIterations }) => {
+ return new Promise((resolve) => {
+ const simulation = d3
+ .forceSimulation(nodes)
+ .alphaDecay(1 - Math.pow(0.001, (1 / forceIterations) * 2))
+ .force(
+ 'link',
+ d3
+ .forceLink(links)
+ .id((d) => d.internalId)
+ .strength(1)
+ )
+ .force('center', d3.forceCenter(0, 0))
+ .force('charge', d3.forceManyBody().strength(-500))
+ .on('end', () => {
+ resolve();
+ });
+ });
+};
+
+export const scaleNodes = ({ nodes, M }) => {
+ const xRange = d3.extent(nodes, (node) => node.x);
+ const yRange = d3.extent(nodes, (node) => node.y);
+
+ const isXLonger = xRange[1] - xRange[0] > yRange[1] - yRange[0];
+ if (!isXLonger) {
+ nodes.forEach((node) => ([node.x, node.y] = [node.y, node.x]));
+ }
+
+ const t = Math.sqrt(M) * 0.85;
+ nodes.forEach((node) => {
+ node.x = node.x * t;
+ node.y = node.y * t;
+ });
+};
+
+export const fixedCurLevel = ({ nodes }) => {
+ nodes.forEach((node) => {
+ node.fx = node.x;
+ node.fy = node.y;
+ });
+};
diff --git a/federjs_old/FederView/HnswView/layout/overviewShortestPath.js b/federjs_old/FederView/HnswView/layout/overviewShortestPath.js
new file mode 100644
index 0000000..3ab2f53
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/overviewShortestPath.js
@@ -0,0 +1,65 @@
+export default function getOverviewShortestPathData(
+ keyNode,
+ keyLevel,
+ { overviewNodesLevels, internalId2overviewNode, overviewLevelCount }
+) {
+ let SPLinksLevels = overviewNodesLevels.map((_) => []);
+ let SPNodesLevels = overviewNodesLevels.map((_) => []);
+ let reachableNodes = [];
+ let reachableLinks = [];
+ let reachableLevel = null;
+ if (keyNode) {
+ const path = [...keyNode.path, keyNode.internalId];
+ if (path.length === 0) {
+ SPNodesLevels = [keyNode.overviewPosLevels[keyLevel]];
+ } else {
+ let preNodeId = path[0];
+ let preNode = internalId2overviewNode[preNodeId];
+ let preLevel = overviewLevelCount - 1;
+ SPNodesLevels[preLevel].push(preNode);
+ for (let i = 1; i < path.length; i++) {
+ let curNodeId = path[i];
+ let curNode = internalId2overviewNode[curNodeId];
+ while (curNode.overviewPosLevels.length <= preLevel) {
+ preLevel -= 1;
+ SPLinksLevels[preLevel].push({
+ source: preNode,
+ target: preNode,
+ });
+ SPNodesLevels[preLevel].push(preNode);
+ }
+ SPNodesLevels[preLevel].push(curNode);
+ SPLinksLevels[preLevel].push({
+ source: preNode,
+ target: curNode,
+ });
+ preNode = curNode;
+ }
+ while (preLevel > keyLevel) {
+ preLevel -= 1;
+ SPLinksLevels[preLevel].push({
+ source: preNode,
+ target: preNode,
+ });
+ SPNodesLevels[preLevel].push(preNode);
+ }
+ }
+ const preNodeInternalId =
+ keyNode.path.length > 0 ? keyNode.path[keyNode.path.length - 1] : null;
+ reachableLevel = keyLevel;
+ reachableNodes = keyNode.linksLevels[keyLevel]
+ .filter((internalId) => internalId != preNodeInternalId)
+ .map((internalId) => internalId2overviewNode[internalId]);
+ reachableLinks = reachableNodes.map((target) => ({
+ source: keyNode,
+ target,
+ }));
+ }
+ return {
+ SPLinksLevels,
+ SPNodesLevels,
+ reachableLevel,
+ reachableNodes,
+ reachableLinks,
+ };
+}
diff --git a/federjs_old/FederView/HnswView/layout/parseVisRecords.js b/federjs_old/FederView/HnswView/layout/parseVisRecords.js
new file mode 100644
index 0000000..1630d21
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/parseVisRecords.js
@@ -0,0 +1,105 @@
+import { HNSW_NODE_TYPE, HNSW_LINK_TYPE } from 'Types';
+import { getLinkId, parseLinkId } from 'Utils';
+
+export const parseVisRecords = ({ topkResults, vis_records }) => {
+ const visData = [];
+ const numLevels = vis_records.length;
+ let fineIds = topkResults.map((d) => d.id);
+ let entryId = -1;
+ for (let i = numLevels - 1; i >= 0; i--) {
+ const level = numLevels - 1 - i;
+ if (level > 0) {
+ fineIds = [entryId];
+ }
+
+ const visRecordsLevel = vis_records[i];
+
+ const id2nodeType = {};
+ const linkId2linkType = {};
+ const updateNodeType = (nodeId, type) => {
+ if (id2nodeType[nodeId]) {
+ id2nodeType[nodeId] = Math.max(id2nodeType[nodeId], type);
+ } else {
+ id2nodeType[nodeId] = type;
+ }
+ };
+ const updateLinkType = (sourceId, targetId, type) => {
+ const linkId = getLinkId(sourceId, targetId, type);
+ if (linkId2linkType[linkId]) {
+ linkId2linkType[linkId] = Math.max(linkId2linkType[linkId], type);
+ } else {
+ linkId2linkType[linkId] = type;
+ }
+ };
+ const id2dist = {};
+ const sourceMap = {};
+
+ visRecordsLevel.forEach((record) => {
+ const [sourceId, targetId, dist] = record;
+
+ if (sourceId === targetId) {
+ // entry
+ entryId = targetId;
+ id2dist[targetId] = dist;
+ } else {
+ updateNodeType(sourceId, HNSW_NODE_TYPE.Candidate);
+ updateNodeType(targetId, HNSW_NODE_TYPE.Coarse);
+
+ if (id2dist[targetId] >= 0) {
+ // visited
+ updateLinkType(sourceId, targetId, HNSW_LINK_TYPE.Visited);
+ } else {
+ // not visited
+ id2dist[targetId] = dist;
+ updateLinkType(sourceId, targetId, HNSW_LINK_TYPE.Extended);
+
+ sourceMap[targetId] = sourceId;
+
+ // only level-0 have "link_type - search"
+ if (level === 0) {
+ const preSourceId = sourceMap[sourceId];
+ if (preSourceId >= 0) {
+ updateLinkType(preSourceId, sourceId, HNSW_LINK_TYPE.Searched);
+ }
+ }
+ }
+ }
+ });
+
+ fineIds.forEach((fineId) => {
+ updateNodeType(fineId, HNSW_NODE_TYPE.Fine);
+
+ let t = fineId;
+ while (t in sourceMap) {
+ let s = sourceMap[t];
+ updateLinkType(s, t, HNSW_LINK_TYPE.Fine);
+ t = s;
+ }
+ });
+
+ const nodes = Object.keys(id2nodeType).map((id) => ({
+ id: `${id}`,
+ type: id2nodeType[id],
+ dist: id2dist[id],
+ }));
+ const links = Object.keys(linkId2linkType).map((linkId) => {
+ const [source, target] = parseLinkId(linkId);
+ return {
+ source: `${source}`,
+ target: `${target}`,
+ type: linkId2linkType[linkId],
+ };
+ });
+ const visDataLevel = {
+ entryIds: [`${entryId}`],
+ fineIds: fineIds.map((id) => `${id}`),
+ links,
+ nodes,
+ };
+ visData.push(visDataLevel);
+ }
+
+ return visData;
+};
+
+export default parseVisRecords;
diff --git a/federjs_old/FederView/HnswView/layout/searchViewLayout.js b/federjs_old/FederView/HnswView/layout/searchViewLayout.js
new file mode 100644
index 0000000..f00ca6b
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/searchViewLayout.js
@@ -0,0 +1,102 @@
+import * as d3 from 'd3';
+import parseVisRecords from './parseVisRecords';
+import forceSearchView from './forceSearchView';
+import transformHandler from './transformHandler';
+import computeSearchViewTransition from './computeSearchViewTransition';
+import { HNSW_LINK_TYPE, HNSW_NODE_TYPE } from 'Types';
+
+export default async function searchViewLayoutHandler(searchRes, federView) {
+ const {
+ targetR,
+ canvasScale,
+ targetOrigin,
+ searchViewNodeBasicR,
+ searchInterLevelTime,
+ searchIntraLevelTime,
+ forceIterations,
+ } = federView;
+
+ const visData = parseVisRecords(searchRes);
+
+ const id2forcePos = await forceSearchView(
+ visData,
+ targetOrigin,
+ forceIterations
+ );
+
+ const searchNodesLevels = visData.map((levelData) => levelData.nodes);
+ searchNodesLevels.forEach((levelData) =>
+ levelData.forEach((node) => {
+ node.forcePos = id2forcePos[node.id];
+ node.x = node.forcePos[0];
+ node.y = node.forcePos[1];
+ })
+ );
+ const { layerPosLevels, transformFunc } = transformHandler(
+ searchNodesLevels.reduce((acc, node) => acc.concat(node), []),
+ federView
+ );
+
+ const searchTarget = {
+ id: 'target',
+ r: targetR * canvasScale,
+ searchViewPosLevels: d3
+ .range(visData.length)
+ .map((i) => transformFunc(...targetOrigin, i)),
+ };
+
+ searchNodesLevels.forEach((nodes, level) => {
+ nodes.forEach((node) => {
+ node.searchViewPosLevels = d3
+ .range(level + 1)
+ .map((i) => transformFunc(...node.forcePos, i));
+ node.r = (searchViewNodeBasicR + node.type * 0.5) * canvasScale;
+ });
+ });
+
+ const id2searchNode = {};
+ searchNodesLevels.forEach((levelData) =>
+ levelData.forEach((node) => (id2searchNode[node.id] = node))
+ );
+
+ const searchLinksLevels = parseVisRecords(searchRes).map((levelData) =>
+ levelData.links.filter((link) => link.type !== HNSW_LINK_TYPE.None)
+ );
+ searchLinksLevels.forEach((levelData) =>
+ levelData.forEach((link) => {
+ const sourceId = link.source;
+ const targetId = link.target;
+ const sourceNode = id2searchNode[sourceId];
+ const targetNode = id2searchNode[targetId];
+ link.source = sourceNode;
+ link.target = targetNode;
+ })
+ );
+
+ const entryNodesLevels = visData.map((levelData) =>
+ levelData.entryIds.map((id) => id2searchNode[id])
+ );
+
+ const { targetShowTime, nodeShowTime, linkShowTime, duration } =
+ computeSearchViewTransition({
+ linksLevels: searchLinksLevels,
+ entryNodesLevels,
+ interLevelGap: searchInterLevelTime,
+ intraLevelGap: searchIntraLevelTime,
+ });
+
+ return {
+ visData,
+ id2forcePos,
+ searchTarget,
+ entryNodesLevels,
+ searchNodesLevels,
+ searchLinksLevels,
+ searchLayerPosLevels: layerPosLevels,
+ searchTargetShowTime: targetShowTime,
+ searchNodeShowTime: nodeShowTime,
+ searchLinkShowTime: linkShowTime,
+ searchTransitionDuration: duration,
+ searchParams: searchRes.searchParams,
+ };
+}
diff --git a/federjs_old/FederView/HnswView/layout/transformHandler.js b/federjs_old/FederView/HnswView/layout/transformHandler.js
new file mode 100644
index 0000000..dd10153
--- /dev/null
+++ b/federjs_old/FederView/HnswView/layout/transformHandler.js
@@ -0,0 +1,48 @@
+import * as d3 from 'd3';
+import { generateArray } from 'Utils';
+
+export const transformHandler = (
+ nodes,
+ { levelCount, width, height, padding, xBias = 0.65, yBias = 0.4, yOver = 0.1 }
+) => {
+ const layerWidth = width - padding[1] - padding[3];
+ const layerHeight =
+ (height - padding[0] - padding[2]) /
+ (levelCount - (levelCount - 1) * yOver);
+ const xRange = d3.extent(nodes, (node) => node.x);
+ const yRange = d3.extent(nodes, (node) => node.y);
+
+ const xOffset = padding[3] + layerWidth * xBias;
+ const transformFunc = (x, y, level) => {
+ const _x = (x - xRange[0]) / (xRange[1] - xRange[0]);
+ const _y = (y - yRange[0]) / (yRange[1] - yRange[0]);
+
+ const newX =
+ xOffset + _x * layerWidth * (1 - xBias) - _y * layerWidth * xBias;
+
+ const newY =
+ padding[0] +
+ layerHeight * (1 - yOver) * (levelCount - 1 - level) +
+ _x * layerHeight * (1 - yBias) +
+ _y * layerHeight * yBias;
+
+ return [newX, newY];
+ };
+ const layerPos = [
+ [layerWidth * xBias, 0],
+ [layerWidth, layerHeight * (1 - yBias)],
+ [layerWidth * (1 - xBias), layerHeight],
+ [0, layerHeight * yBias],
+ ];
+ const layerPosLevels = generateArray(levelCount).map((_, level) =>
+ layerPos.map((coord) => [
+ coord[0] + padding[3],
+ coord[1] +
+ padding[0] +
+ layerHeight * (1 - yOver) * (levelCount - 1 - level),
+ ])
+ );
+ return { layerPosLevels, transformFunc };
+};
+
+export default transformHandler;
diff --git a/federjs_old/FederView/HnswView/render/TimeControllerView.js b/federjs_old/FederView/HnswView/render/TimeControllerView.js
new file mode 100644
index 0000000..ececbfb
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/TimeControllerView.js
@@ -0,0 +1,166 @@
+import * as d3 from 'd3';
+
+const iconGap = 10;
+const rectW = 36;
+const sliderBackGroundWidth = 200;
+const sliderWidth = sliderBackGroundWidth * 0.8;
+const sliderHeight = rectW * 0.15;
+const sliderBarWidth = 10;
+const sliderBarHeight = rectW * 0.6;
+const resetW = 16;
+const resetIconD = `M12.3579 13.0447C11.1482 14.0929 9.60059 14.6689 7.99992 14.6667C4.31792 14.6667 1.33325 11.682 1.33325 8.00004C1.33325 4.31804 4.31792 1.33337 7.99992 1.33337C11.6819 1.33337 14.6666 4.31804 14.6666 8.00004C14.6666 9.42404 14.2199 10.744 13.4599 11.8267L11.3333 8.00004H13.3333C13.3332 6.77085 12.9085 5.57942 12.131 4.6273C11.3536 3.67519 10.2712 3.02084 9.06681 2.77495C7.86246 2.52906 6.61014 2.70672 5.5217 3.27788C4.43327 3.84905 3.57553 4.77865 3.0936 5.90943C2.61167 7.04021 2.53512 8.30275 2.87691 9.48347C3.2187 10.6642 3.95785 11.6906 4.96931 12.3891C5.98077 13.0876 7.20245 13.4152 8.42768 13.3166C9.65292 13.218 10.8065 12.6993 11.6933 11.848L12.3579 13.0447Z`;
+const pauseIconHeight = rectW * 0.4;
+const pauseIconWidth = rectW * 0.08;
+const pauseIconGap = rectW * 0.15;
+const pauseIconX = (rectW - pauseIconWidth * 2 - pauseIconGap) / 2;
+export class TimeControllerView {
+ constructor(domSelector) {
+ this.render(domSelector);
+ this.moveSilderBar = () => {};
+ }
+ play() {
+ this.renderPauseIcon();
+ }
+ pause() {
+ this.renderPlayIcon();
+ }
+ renderPlayIcon() {
+ const playPauseIconG = this.playPauseIconG;
+ playPauseIconG.selectAll('*').remove();
+ playPauseIconG
+ .append('path')
+ .attr(
+ 'd',
+ `M${rectW * 0.36},${rectW * 0.3}L${rectW * 0.64},${rectW * 0.5}L${
+ rectW * 0.36
+ },${rectW * 0.7}Z`
+ )
+ .attr('fill', '#000');
+ }
+ renderPauseIcon() {
+ const playPauseIconG = this.playPauseIconG;
+ playPauseIconG.selectAll('*').remove();
+ playPauseIconG
+ .selectAll('rect')
+ .data([, ,])
+ .join('rect')
+ .attr('x', (_, i) => pauseIconX + i * (pauseIconGap + pauseIconWidth))
+ .attr('y', (rectW - pauseIconHeight) / 2)
+ .attr('rx', pauseIconWidth / 2)
+ .attr('ry', pauseIconWidth / 2)
+ .attr('width', pauseIconWidth)
+ .attr('height', pauseIconHeight)
+ .attr('fill', '#000');
+ }
+ render(domSelector) {
+ const _dom = d3.select(domSelector);
+ _dom.selectAll('svg#feder-timer').remove();
+ const svg = _dom
+ .append('svg')
+ .attr('id', 'feder-timer')
+ .attr('width', 300)
+ .attr('height', rectW)
+ .style('position', 'absolute')
+ .style('left', '20px')
+ .style('bottom', '32px');
+ // .style('border', '1px solid red');
+
+ const playPauseG = svg.append('g');
+ playPauseG
+ .append('rect')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('width', rectW)
+ .attr('height', rectW)
+ .attr('fill', '#fff');
+ const playPauseIconG = playPauseG.append('g');
+ this.playPauseIconG = playPauseIconG;
+ this.renderPlayIcon();
+
+ const sliderG = svg
+ .append('g')
+ .attr('transform', `translate(${rectW + iconGap}, 0)`);
+ sliderG
+ .append('rect')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('width', sliderBackGroundWidth)
+ .attr('height', rectW)
+ .attr('fill', '#1D2939');
+ sliderG
+ .append('rect')
+ .attr('x', sliderBackGroundWidth / 2 - sliderWidth / 2)
+ .attr('y', rectW / 2 - sliderHeight / 2)
+ .attr('width', sliderWidth)
+ .attr('height', sliderHeight)
+ .attr('fill', '#fff');
+ const sliderBar = sliderG
+ .append('g')
+ .append('rect')
+ .datum({ x: 0, y: 0 })
+ .attr(
+ 'transform',
+ `translate(${
+ sliderBackGroundWidth / 2 - sliderWidth / 2 - sliderBarWidth / 2
+ },0)`
+ )
+ .attr('x', 0)
+ .attr('y', rectW / 2 - sliderBarHeight / 2)
+ .attr('width', sliderBarWidth)
+ .attr('height', sliderBarHeight)
+ .attr('fill', '#fff');
+
+ const resetG = svg
+ .append('g')
+ .attr(
+ 'transform',
+ `translate(${rectW + iconGap + sliderBackGroundWidth + iconGap}, 0)`
+ );
+ resetG
+ .append('rect')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('width', rectW)
+ .attr('height', rectW)
+ .attr('fill', '#fff');
+
+ resetG
+ .append('path')
+ .attr('d', resetIconD)
+ .attr('fill', '#000')
+ .attr(
+ 'transform',
+ `translate(${rectW / 2 - resetW / 2},${rectW / 2 - resetW / 2})`
+ );
+
+ this.playPauseG = playPauseG;
+ this.sliderBar = sliderBar;
+ this.resetG = resetG;
+ }
+
+ setTimer(timer) {
+ this.playPauseG.on('click', () => timer.playPause());
+ this.resetG.on('click', () => timer.restart());
+
+ const drag = d3
+ .drag()
+ .on('start', () => timer.stop())
+ .on('drag', (e, d) => {
+ const x = Math.max(0, Math.min(e.x, sliderWidth));
+ sliderBar.attr('x', (d.x = x));
+ timer.setTimeP(x / sliderWidth);
+ });
+ // .on('end', continue);
+
+ const sliderBar = this.sliderBar;
+ sliderBar.call(drag);
+
+ this.moveSilderBar = (p) => {
+ const x = p * sliderWidth;
+ sliderBar.datum().x = x;
+ sliderBar.attr('x', x);
+ };
+ }
+}
+
+export default TimeControllerView;
diff --git a/federjs_old/FederView/HnswView/render/TimerController.js b/federjs_old/FederView/HnswView/render/TimerController.js
new file mode 100644
index 0000000..f77c246
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/TimerController.js
@@ -0,0 +1,82 @@
+import * as d3 from 'd3';
+
+export default class TimerController {
+ constructor({ duration, speed = 1, callback, playCallback, pauseCallback }) {
+ this.callback = callback;
+ this.speed = speed;
+ this.duration = duration;
+ this.playCallback = playCallback;
+ this.pauseCallback = pauseCallback;
+
+ this.tAlready = 0;
+ this.t = 0;
+ this.timer = null;
+ this.isPlaying = false;
+ }
+ get currentT() {
+ return this.t;
+ }
+ start() {
+ const speed = this.speed;
+ this.isPlaying || this.playCallback();
+ this.isPlaying = true;
+ this.timer = d3.timer((elapsed) => {
+ const t = elapsed * speed + this.tAlready;
+ const p = t / this.duration;
+ this.t = t;
+ this.callback({
+ t,
+ p,
+ });
+ if (p >= 1) {
+ this.stop();
+ }
+ });
+ }
+ restart() {
+ this.timer.stop();
+ this.tAlready = 0;
+ this.start();
+ }
+ stop() {
+ this.timer.stop();
+ this.tAlready = this.t;
+
+ this.isPlaying && this.pauseCallback();
+ this.isPlaying = false;
+ }
+ playPause() {
+ if (this.isPlaying) this.stop();
+ else this.start();
+ }
+ continue() {
+ this.start();
+ }
+ setSpeed(speed) {
+ this.stop();
+ this.speed = speed;
+ this.continue();
+ }
+ setTimeT(t) {
+ this.stop();
+ const p = t / this.duration;
+ this.tAlready = t;
+ this.t = t;
+ this.callback({
+ t,
+ p,
+ });
+ // this.continue();
+ }
+ setTimeP(p) {
+ this.stop();
+ const t = this.duration * p;
+ this.tAlready = t;
+ this.t = t;
+ this.callback({
+ t,
+ p,
+ });
+ // this.continue();
+ }
+}
diff --git a/federjs_old/FederView/HnswView/render/renderBackground.js b/federjs_old/FederView/HnswView/render/renderBackground.js
new file mode 100644
index 0000000..efbb6d3
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderBackground.js
@@ -0,0 +1,11 @@
+import { drawRect, blackColor } from 'Utils/renderUtils';
+
+export default function renderBackground(ctx, { width, height }) {
+ drawRect({
+ ctx,
+ width,
+ height,
+ hasFill: true,
+ fillStyle: blackColor,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderHoverLine.js b/federjs_old/FederView/HnswView/render/renderHoverLine.js
new file mode 100644
index 0000000..ab6c100
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderHoverLine.js
@@ -0,0 +1,56 @@
+import { drawPath, hexWithOpacity, ZYellow } from 'Utils/renderUtils';
+
+const renderHoverLine = (
+ ctx,
+ { hoveredNode, hoveredLevel, clickedNode, clickedLevel },
+ {
+ width,
+ padding,
+ hoveredPanelLineWidth,
+ HoveredPanelLine_1_x,
+ HoveredPanelLine_1_y,
+ HoveredPanelLine_2_x,
+ canvasScale,
+ }
+) => {
+ let isLeft = true;
+ let endX = 0;
+ let endY = 0;
+ if (!!hoveredNode) {
+ const [x, y] = hoveredNode.overviewPosLevels[hoveredLevel];
+ const originX = (width - padding[1] - padding[3]) / 2 + padding[3];
+ isLeft = !clickedNode
+ ? originX > x
+ : clickedNode.overviewPosLevels[clickedLevel][0] > x;
+ const k = isLeft ? -1 : 1;
+ endX =
+ x +
+ HoveredPanelLine_1_x * canvasScale * k +
+ HoveredPanelLine_2_x * canvasScale * k;
+ endY = y + HoveredPanelLine_1_y * canvasScale * k;
+ const points = [
+ [x, y],
+ [
+ x + HoveredPanelLine_1_x * canvasScale * k,
+ y + HoveredPanelLine_1_y * canvasScale * k,
+ ],
+ [
+ x +
+ HoveredPanelLine_1_x * canvasScale * k +
+ HoveredPanelLine_2_x * canvasScale * k,
+ y + HoveredPanelLine_1_y * canvasScale * k,
+ ],
+ ];
+ drawPath({
+ ctx,
+ points,
+ withZ: false,
+ hasStroke: true,
+ strokeStyle: hexWithOpacity(ZYellow, 1),
+ lineWidth: hoveredPanelLineWidth * canvasScale,
+ });
+ }
+ return { isLeft, endX, endY };
+};
+
+export default renderHoverLine;
diff --git a/federjs_old/FederView/HnswView/render/renderHoveredPanelLine.js b/federjs_old/FederView/HnswView/render/renderHoveredPanelLine.js
new file mode 100644
index 0000000..3d3abca
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderHoveredPanelLine.js
@@ -0,0 +1,36 @@
+import { drawPath, hexWithOpacity, ZYellow } from 'Utils/renderUtils';
+
+export default function renderHoveredPanelLine(
+ ctx,
+ { x, y, isLeft },
+ {
+ hoveredPanelLineWidth,
+ HoveredPanelLine_1_x,
+ HoveredPanelLine_1_y,
+ HoveredPanelLine_2_x,
+ canvasScale,
+ }
+) {
+ const k = isLeft ? -1 : 1;
+ const points = [
+ [x, y],
+ [
+ x + HoveredPanelLine_1_x * canvasScale * k,
+ y + HoveredPanelLine_1_y * canvasScale * k,
+ ],
+ [
+ x +
+ HoveredPanelLine_1_x * canvasScale * k +
+ HoveredPanelLine_2_x * canvasScale * k,
+ y + HoveredPanelLine_1_y * canvasScale * k,
+ ],
+ ];
+ drawPath({
+ ctx,
+ points,
+ withZ: false,
+ hasStroke: true,
+ strokeStyle: hexWithOpacity(ZYellow, 1),
+ lineWidth: hoveredPanelLineWidth * canvasScale,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderLinks.js b/federjs_old/FederView/HnswView/render/renderLinks.js
new file mode 100644
index 0000000..024972f
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderLinks.js
@@ -0,0 +1,29 @@
+import {
+ drawLinesWithLinearGradient,
+ normalGradientStopColors,
+} from 'Utils/renderUtils';
+import { shortenLine } from 'Utils';
+
+export default function renderLinks(
+ ctx,
+ links,
+ level,
+ { shortenLineD, overviewLinkLineWidth, canvasScale }
+) {
+ const pointsList = links.map((link) =>
+ shortenLine(
+ link.source.overviewPosLevels[level],
+ link.target.overviewPosLevels[level],
+ shortenLineD * canvasScale
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: normalGradientStopColors,
+ lineWidth: overviewLinkLineWidth * canvasScale,
+ lineCap: 'round',
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderNodes.js b/federjs_old/FederView/HnswView/render/renderNodes.js
new file mode 100644
index 0000000..fa2c3f7
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderNodes.js
@@ -0,0 +1,21 @@
+import { drawEllipse, ZBlue, hexWithOpacity } from 'Utils/renderUtils';
+
+export default function renderNodes(
+ ctx,
+ nodes,
+ level,
+ { canvasScale, ellipseRation, shadowBlur }
+) {
+ drawEllipse({
+ ctx,
+ circles: nodes.map((node) => [
+ ...node.overviewPosLevels[level],
+ node.r * ellipseRation * canvasScale,
+ node.r * canvasScale,
+ ]),
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZBlue, 0.75),
+ shadowColor: ZBlue,
+ shadowBlur: shadowBlur * canvasScale,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderOverview.js b/federjs_old/FederView/HnswView/render/renderOverview.js
new file mode 100644
index 0000000..cbf2a13
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderOverview.js
@@ -0,0 +1,28 @@
+import renderBackground from './renderBackground';
+import renderlevelLayer from './renderlevelLayer';
+import renderLinks from './renderLinks';
+import renderNodes from './renderNodes';
+import renderReachable from './renderReachable';
+import renderShortestPath from './renderShortestPath';
+
+export default function renderOverview(ctx, federView, overviewHighlightData) {
+ const {
+ overviewLevelCount,
+ overviewNodesLevels,
+ overviewLinksLevels,
+ overviewLayerPosLevels,
+ } = federView;
+ renderBackground(ctx, federView);
+ for (let level = 0; level < overviewLevelCount; level++) {
+ renderlevelLayer(ctx, overviewLayerPosLevels[level], federView);
+ const nodes = overviewNodesLevels[level];
+ const links = overviewLinksLevels[level];
+ level > 0 && renderLinks(ctx, links, level, federView);
+ renderNodes(ctx, nodes, level, federView);
+
+ if (overviewHighlightData) {
+ renderReachable(ctx, level, overviewHighlightData, federView);
+ renderShortestPath(ctx, level, overviewHighlightData, federView);
+ }
+ }
+}
diff --git a/federjs_old/FederView/HnswView/render/renderReachable.js b/federjs_old/FederView/HnswView/render/renderReachable.js
new file mode 100644
index 0000000..13c718a
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderReachable.js
@@ -0,0 +1,51 @@
+import {
+ hexWithOpacity,
+ whiteColor,
+ drawLinesWithLinearGradient,
+ neighbourGradientStopColors,
+ drawEllipse,
+} from 'Utils/renderUtils';
+
+import { shortenLine } from 'Utils';
+
+export default function renderReachableData(
+ ctx,
+ level,
+ { reachableLevel, reachableNodes, reachableLinks },
+ {
+ shortenLineD,
+ canvasScale,
+ reachableLineWidth,
+ ellipseRation,
+ shadowBlur,
+ highlightRadiusExt,
+ posAttr = 'overviewPosLevels',
+ }
+) {
+ if (level != reachableLevel) return;
+ const pointsList = reachableLinks
+ .map((link) => [link.source[posAttr][level], link.target[posAttr][level]])
+ .map((points) => shortenLine(...points, shortenLineD * canvasScale));
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: neighbourGradientStopColors,
+ lineWidth: reachableLineWidth * canvasScale,
+ lineCap: 'round',
+ });
+
+ drawEllipse({
+ ctx,
+ circles: reachableNodes.map((node) => [
+ ...node[posAttr][level],
+ (node.r + highlightRadiusExt) * ellipseRation * canvasScale,
+ (node.r + highlightRadiusExt) * canvasScale,
+ ]),
+ hasFill: true,
+ fillStyle: hexWithOpacity(whiteColor, 1),
+ shadowColor: whiteColor,
+ shadowBlur: shadowBlur * canvasScale,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderSearchViewInterLevelLinks.js b/federjs_old/FederView/HnswView/render/renderSearchViewInterLevelLinks.js
new file mode 100644
index 0000000..7181e44
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderSearchViewInterLevelLinks.js
@@ -0,0 +1,92 @@
+import {
+ drawLinesWithLinearGradient,
+ highLightGradientStopColors,
+ targetLevelGradientStopColors,
+} from 'Utils/renderUtils';
+import { shortenLine, getInprocessPos } from 'Utils';
+
+export default function renderSearchViewInterLevelLinks(
+ ctx,
+ { entryNodes, inprocessEntryNodes, searchTarget, level },
+ { shortenLineD, canvasScale }
+) {
+ const pointsList = entryNodes.map((node) =>
+ shortenLine(
+ node.searchViewPosLevels[level + 1],
+ node.searchViewPosLevels[level],
+ shortenLineD * canvasScale
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: highLightGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+ const targetPointsList =
+ pointsList.length === 0
+ ? []
+ : [
+ shortenLine(
+ searchTarget.searchViewPosLevels[level + 1],
+ searchTarget.searchViewPosLevels[level],
+ shortenLineD
+ ),
+ ];
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList: targetPointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: targetLevelGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+
+ const inprocessPointsList = inprocessEntryNodes.map(({ node, t }) =>
+ shortenLine(
+ node.searchViewPosLevels[level + 1],
+ getInprocessPos(
+ node.searchViewPosLevels[level + 1],
+ node.searchViewPosLevels[level],
+ t
+ ),
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList: inprocessPointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: highLightGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+ const inprocessTargetPointsList =
+ inprocessPointsList.length === 0
+ ? []
+ : [
+ shortenLine(
+ searchTarget.searchViewPosLevels[level + 1],
+ getInprocessPos(
+ searchTarget.searchViewPosLevels[level + 1],
+ searchTarget.searchViewPosLevels[level],
+ inprocessEntryNodes[0].t
+ ),
+ shortenLineD
+ ),
+ ];
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList: inprocessTargetPointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: targetLevelGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderSearchViewLinks.js b/federjs_old/FederView/HnswView/render/renderSearchViewLinks.js
new file mode 100644
index 0000000..fc439ad
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderSearchViewLinks.js
@@ -0,0 +1,184 @@
+import {
+ drawLinesWithLinearGradient,
+ highLightGradientStopColors,
+ normalGradientStopColors,
+} from 'Utils/renderUtils';
+import { shortenLine, getInprocessPos } from 'Utils';
+import { HNSW_LINK_TYPE } from 'Types';
+
+export default function renderSearchViewLinks(
+ ctx,
+ { links, inProcessLinks, level },
+ { shortenLineD, canvasScale }
+) {
+ let pointsList = [];
+ let inprocessPointsList = [];
+
+ // Visited
+ pointsList = links
+ .filter((link) => link.type === HNSW_LINK_TYPE.Visited)
+ .map((link) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ shortenLineD * canvasScale
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: normalGradientStopColors,
+ lineWidth: 4,
+ lineCap: 'round',
+ });
+ inprocessPointsList = inProcessLinks
+ .filter(({ link }) => link.type === HNSW_LINK_TYPE.Visited)
+ .map(({ t, link }) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ getInprocessPos(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ t
+ ),
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList: inprocessPointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: normalGradientStopColors,
+ lineWidth: 4,
+ lineCap: 'round',
+ });
+
+ // Extended
+ pointsList = links
+ .filter((link) => link.type === HNSW_LINK_TYPE.Extended)
+ .map((link) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: normalGradientStopColors,
+ lineWidth: 4,
+ lineCap: 'round',
+ });
+ inprocessPointsList = inProcessLinks
+ .filter(({ link }) => link.type === HNSW_LINK_TYPE.Extended)
+ .map(({ t, link }) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ getInprocessPos(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ t
+ ),
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList: inprocessPointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: normalGradientStopColors,
+ lineWidth: 4,
+ lineCap: 'round',
+ });
+
+ // Searched
+ pointsList = links
+ .filter((link) => link.type === HNSW_LINK_TYPE.Searched)
+ .map((link) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: highLightGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+ inprocessPointsList = inProcessLinks
+ .filter(({ link }) => link.type === HNSW_LINK_TYPE.Searched)
+ .map(({ t, link }) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ getInprocessPos(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ t
+ ),
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList: inprocessPointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: highLightGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+
+ // Fine
+ pointsList = links
+ .filter((link) => link.type === HNSW_LINK_TYPE.Fine)
+ .map((link) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: highLightGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+ inprocessPointsList = inProcessLinks
+ .filter(({ link }) => link.type === HNSW_LINK_TYPE.Fine)
+ .map(({ t, link }) =>
+ shortenLine(
+ link.source.searchViewPosLevels[level],
+ getInprocessPos(
+ link.source.searchViewPosLevels[level],
+ link.target.searchViewPosLevels[level],
+ t
+ ),
+ shortenLineD
+ )
+ );
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList: inprocessPointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: highLightGradientStopColors,
+ lineWidth: 6,
+ lineCap: 'round',
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderSearchViewNodes.js b/federjs_old/FederView/HnswView/render/renderSearchViewNodes.js
new file mode 100644
index 0000000..3123e6e
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderSearchViewNodes.js
@@ -0,0 +1,66 @@
+import {
+ drawEllipse,
+ hexWithOpacity,
+ ZBlue,
+ ZYellow,
+ ZOrange,
+ colorScheme,
+} from 'Utils/renderUtils';
+
+import { HNSW_NODE_TYPE } from 'Types';
+
+export default function renderSearchViewNodes(
+ ctx,
+ { nodes, level },
+ { ellipseRation, shadowBlur }
+) {
+ let _nodes = [];
+
+ // coarse
+ _nodes = nodes.filter((node) => node.type === HNSW_NODE_TYPE.Coarse);
+ drawEllipse({
+ ctx,
+ circles: _nodes.map((node) => [
+ ...node.searchViewPosLevels[level],
+ node.r * ellipseRation,
+ node.r,
+ ]),
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZBlue, 0.7),
+ shadowColor: ZBlue,
+ shadowBlur,
+ });
+
+ // candidate
+ _nodes = nodes.filter((node) => node.type === HNSW_NODE_TYPE.Candidate);
+ drawEllipse({
+ ctx,
+ circles: _nodes.map((node) => [
+ ...node.searchViewPosLevels[level],
+ node.r * ellipseRation,
+ node.r,
+ ]),
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZYellow, 0.8),
+ shadowColor: ZYellow,
+ shadowBlur,
+ });
+
+ // fine
+ _nodes = nodes.filter((node) => node.type === HNSW_NODE_TYPE.Fine);
+ drawEllipse({
+ ctx,
+ circles: _nodes.map((node) => [
+ ...node.searchViewPosLevels[level],
+ node.r * ellipseRation,
+ node.r,
+ ]),
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[2], 1),
+ hasStroke: true,
+ lineWidth: 1,
+ strokeStyle: hexWithOpacity(ZOrange, 0.8),
+ shadowColor: ZOrange,
+ shadowBlur,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderSearchViewTarget.js b/federjs_old/FederView/HnswView/render/renderSearchViewTarget.js
new file mode 100644
index 0000000..ccb9c60
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderSearchViewTarget.js
@@ -0,0 +1,18 @@
+import { drawEllipse, hexWithOpacity, whiteColor } from 'Utils/renderUtils';
+
+export default function renderSearchViewTarget(
+ ctx,
+ { node, level },
+ { ellipseRation }
+) {
+ drawEllipse({
+ ctx,
+ circles: [
+ [...node.searchViewPosLevels[level], node.r * ellipseRation, node.r],
+ ],
+ hasFill: true,
+ fillStyle: hexWithOpacity(whiteColor, 1),
+ shadowColor: whiteColor,
+ shadowBlur: 6,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderSearchViewTransition.js b/federjs_old/FederView/HnswView/render/renderSearchViewTransition.js
new file mode 100644
index 0000000..8a116eb
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderSearchViewTransition.js
@@ -0,0 +1,146 @@
+import * as d3 from 'd3';
+import renderBackground from './renderBackground';
+import renderlevelLayer from './renderlevelLayer';
+import renderSearchViewLinks from './renderSearchViewLinks';
+import renderSearchViewInterLevelLinks from './renderSearchViewInterLevelLinks';
+import renderSearchViewNodes from './renderSearchViewNodes';
+import renderSearchViewTarget from './renderSearchViewTarget';
+import renderSelectedNode from './renderSelectedNode';
+import renderHoveredPanelLine from './renderHoveredPanelLine';
+import {
+ getNodeIdWithLevel,
+ getLinkIdWithLevel,
+ getEntryLinkIdWithLevel,
+} from 'Utils';
+
+export default function renderSearchViewTransition(
+ ctx,
+ {
+ searchNodesLevels,
+ searchLinksLevels,
+ searchLayerPosLevels,
+ searchNodeShowTime,
+ searchLinkShowTime,
+ searchTarget,
+ searchTargetShowTime,
+ entryNodesLevels,
+
+ clickedLevel,
+ clickedNode,
+ hoveredLevel,
+ hoveredNode,
+ },
+ federView,
+ { t, p }
+) {
+ const {
+ searchIntraLevelTime,
+ searchInterLevelTime,
+ width,
+ padding,
+ canvasScale,
+ } = federView;
+ renderBackground(ctx, federView);
+
+ for (let level = 0; level < searchNodesLevels.length; level++) {
+ renderlevelLayer(ctx, searchLayerPosLevels[level], federView);
+ const nodes = searchNodesLevels[level].filter(
+ (node) => searchNodeShowTime[getNodeIdWithLevel(node.id, level)] < t
+ );
+ const links = searchLinksLevels[level].filter(
+ (link) =>
+ searchLinkShowTime[
+ getLinkIdWithLevel(link.source.id, link.target.id, level)
+ ] +
+ searchIntraLevelTime <
+ t
+ );
+ const inProcessLinks = searchLinksLevels[level]
+ .filter(
+ (link) =>
+ searchLinkShowTime[
+ getLinkIdWithLevel(link.source.id, link.target.id, level)
+ ] < t &&
+ searchLinkShowTime[
+ getLinkIdWithLevel(link.source.id, link.target.id, level)
+ ] +
+ searchIntraLevelTime >=
+ t
+ )
+ .map((link) => ({
+ t:
+ (t -
+ searchLinkShowTime[
+ getLinkIdWithLevel(link.source.id, link.target.id, level)
+ ]) /
+ searchIntraLevelTime,
+ link,
+ }));
+ const entryNodes =
+ level === entryNodesLevels.length - 1
+ ? []
+ : entryNodesLevels[level].filter(
+ (entryNode) =>
+ searchLinkShowTime[getEntryLinkIdWithLevel(entryNode.id, level)] +
+ searchInterLevelTime <
+ t
+ );
+ const inprocessEntryNodes =
+ level === entryNodesLevels.length - 1
+ ? []
+ : entryNodesLevels[level]
+ .filter(
+ (entryNode) =>
+ searchLinkShowTime[
+ getEntryLinkIdWithLevel(entryNode.id, level)
+ ] < t &&
+ searchLinkShowTime[
+ getEntryLinkIdWithLevel(entryNode.id, level)
+ ] +
+ searchInterLevelTime >=
+ t
+ )
+ .map((node) => ({
+ node,
+ t:
+ (t -
+ searchLinkShowTime[getEntryLinkIdWithLevel(node.id, level)]) /
+ searchInterLevelTime,
+ }));
+ renderSearchViewLinks(ctx, { links, inProcessLinks, level }, federView);
+ // console.log(level, entryNodes);
+ renderSearchViewInterLevelLinks(
+ ctx,
+ {
+ entryNodes,
+ inprocessEntryNodes,
+ searchTarget,
+ level,
+ },
+ federView
+ );
+ renderSearchViewNodes(ctx, { nodes, level }, federView);
+
+ searchTargetShowTime[level] < t &&
+ renderSearchViewTarget(ctx, { node: searchTarget, level }, federView);
+
+ if (!!hoveredNode) {
+ const [x, y] = hoveredNode.searchViewPosLevels[hoveredLevel];
+ const originX = (width - padding[1] - padding[3]) / 2 + padding[3];
+ const isLeft = originX > x;
+
+ renderHoveredPanelLine(ctx, { x, y, isLeft }, federView);
+ }
+
+ if (!!clickedNode) {
+ renderSelectedNode(
+ ctx,
+ {
+ pos: clickedNode.searchViewPosLevels[clickedLevel],
+ r: clickedNode.r + 2 * canvasScale,
+ },
+ federView
+ );
+ }
+ }
+}
diff --git a/federjs_old/FederView/HnswView/render/renderSelectedNode.js b/federjs_old/FederView/HnswView/render/renderSelectedNode.js
new file mode 100644
index 0000000..9afcfde
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderSelectedNode.js
@@ -0,0 +1,11 @@
+import { drawEllipse, hexWithOpacity, ZYellow } from 'Utils/renderUtils';
+
+export default function renderSelectedNode(ctx, { pos, r }, { ellipseRation }) {
+ drawEllipse({
+ ctx,
+ circles: [[...pos, r * ellipseRation, r]],
+ hasStroke: true,
+ strokeStyle: hexWithOpacity(ZYellow, 0.8),
+ lineWidth: 4,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderShortestPath.js b/federjs_old/FederView/HnswView/render/renderShortestPath.js
new file mode 100644
index 0000000..cd4c292
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderShortestPath.js
@@ -0,0 +1,58 @@
+import { shortenLine } from 'Utils';
+import {
+ drawEllipse,
+ drawLinesWithLinearGradient,
+ highLightGradientStopColors,
+ highLightColor,
+ hexWithOpacity,
+} from 'Utils/renderUtils';
+
+export default function renderShortestPath(
+ ctx,
+ level,
+ { SPLinksLevels, SPNodesLevels },
+ {
+ shortenLineD,
+ canvasScale,
+ shortestPathLineWidth,
+ highlightRadiusExt,
+ shadowBlur,
+ ellipseRation,
+ posAttr = 'overviewPosLevels',
+ }
+) {
+ const pointsList = (SPLinksLevels ? SPLinksLevels[level] : [])
+ .map((link) =>
+ link.source === link.target
+ ? !!link.source[posAttr][level + 1]
+ ? [link.source[posAttr][level + 1], link.target[posAttr][level]]
+ : null
+ : [link.source[posAttr][level], link.target[posAttr][level]]
+ )
+ .filter((a) => a)
+ .map((points) => shortenLine(...points, shortenLineD * canvasScale));
+ drawLinesWithLinearGradient({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ isStrokeLinearGradient: true,
+ gradientStopColors: highLightGradientStopColors,
+ lineWidth: shortestPathLineWidth * canvasScale,
+ lineCap: 'round',
+ });
+
+ drawEllipse({
+ ctx,
+ circles: (SPNodesLevels ? SPNodesLevels[level] : [])
+ .filter((node) => !!node[posAttr][level])
+ .map((node) => [
+ ...node[posAttr][level],
+ (node.r + highlightRadiusExt) * ellipseRation * canvasScale,
+ (node.r + highlightRadiusExt) * canvasScale,
+ ]),
+ hasFill: true,
+ fillStyle: hexWithOpacity(highLightColor, 1),
+ shadowColor: highLightColor,
+ shadowBlur: shadowBlur * canvasScale,
+ });
+}
diff --git a/federjs_old/FederView/HnswView/render/renderlevelLayer.js b/federjs_old/FederView/HnswView/render/renderlevelLayer.js
new file mode 100644
index 0000000..34e7f49
--- /dev/null
+++ b/federjs_old/FederView/HnswView/render/renderlevelLayer.js
@@ -0,0 +1,63 @@
+import * as d3 from 'd3';
+import {
+ drawCircle,
+ drawPath,
+ hexWithOpacity,
+ ZLayerBorder,
+ whiteColor,
+ layerGradientStopColors,
+} from 'Utils/renderUtils';
+import { dist } from 'Utils';
+
+export default function renderlevelLayer(
+ ctx,
+ points,
+ { canvasScale, layerDotNum }
+) {
+ drawPath({
+ ctx,
+ points,
+ hasStroke: true,
+ isStrokeLinearGradient: false,
+ strokeStyle: hexWithOpacity(ZLayerBorder, 0.6),
+ lineWidth: 0.5 * canvasScale,
+ hasFill: true,
+ isFillLinearGradient: true,
+ gradientStopColors: layerGradientStopColors,
+ gradientPos: [points[1][0], points[0][1], points[3][0], points[2][1]],
+ });
+
+ const rightTopLength = dist(points[0], points[1]);
+ const leftTopLength = dist(points[0], points[3]);
+ const rightTopDotNum = layerDotNum;
+ // const leftTopDotNum = (layerDotNum / rightTopLength) * leftTopLength;
+ const leftTopDotNum = layerDotNum;
+ const rightTopVec = [
+ points[1][0] - points[0][0],
+ points[1][1] - points[0][1],
+ ];
+ const leftTopVec = [points[3][0] - points[0][0], points[3][1] - points[0][1]];
+ const dots = [];
+ for (let i = 0; i < layerDotNum; i++) {
+ const rightTopT = i / layerDotNum + 1 / (2 * layerDotNum);
+ for (let j = 0; j < layerDotNum; j++) {
+ const leftTopT = j / layerDotNum + 1 / (2 * layerDotNum);
+ dots.push([
+ points[0][0] + rightTopVec[0] * rightTopT + leftTopVec[0] * leftTopT,
+ points[0][1] + rightTopVec[1] * rightTopT + leftTopVec[1] * leftTopT,
+ 0.8 * canvasScale,
+ ]);
+ }
+ }
+ // d3.range(0.02, 0.98, 1 / rightTopDotNum).forEach((rightTopT) =>
+ // d3.range(0.02, 0.98, 1 / leftTopDotNum).forEach((leftTopT) => {
+
+ // })
+ // );
+ drawCircle({
+ ctx,
+ circles: dots,
+ hasFill: true,
+ fillStyle: hexWithOpacity(whiteColor, 0.4),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/InfoPanel/index.js b/federjs_old/FederView/IvfflatView/InfoPanel/index.js
new file mode 100644
index 0000000..a795e39
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/InfoPanel/index.js
@@ -0,0 +1,528 @@
+import * as d3 from 'd3';
+
+import {
+ ZYellow,
+ ZBlue,
+ whiteColor,
+ blackColor,
+ drawPath,
+ hexWithOpacity,
+} from 'Utils/renderUtils';
+
+import { showVectors, randomSelect } from 'Utils';
+
+export const overviewPanelId = 'feder-info-overview-panel';
+export const hoveredPanelId = 'feder-info-hovered-panel';
+
+const panelBackgroundColor = hexWithOpacity(blackColor, 0.6);
+
+export default class InfoPanel {
+ constructor({ dom, width, height }) {
+ this.dom = dom;
+ this.width = width;
+ this.height = height;
+
+ this.initOverviewPanel();
+ this.initHoveredPanel();
+ this.initStyle();
+ }
+ initOverviewPanel() {
+ const dom = this.dom;
+ const overviewPanel = document.createElement('div');
+ overviewPanel.setAttribute('id', overviewPanelId);
+ overviewPanel.className =
+ overviewPanel.className + ' panel-border panel hide';
+ const overviewPanelStyle = {
+ position: 'absolute',
+ // left: `${canvas.width - 10}px`,
+ left: '16px',
+ top: '10px',
+ width: '240px',
+ 'max-height': `${this.height - 40}px`,
+ overflow: 'auto',
+ borderColor: whiteColor,
+ backgroundColor: panelBackgroundColor,
+ // pointerEvents: 'none',
+ };
+ Object.assign(overviewPanel.style, overviewPanelStyle);
+ dom.appendChild(overviewPanel);
+ this.overviewPanel = overviewPanel;
+ }
+ setOverviewPanelPos(isPanelLeft) {
+ const overviewPanel = this.overviewPanel;
+ const overviewPanelStyle = isPanelLeft
+ ? {
+ left: '16px',
+ }
+ : {
+ left: null,
+ right: '16px',
+ };
+ Object.assign(overviewPanel.style, overviewPanelStyle);
+ }
+
+ updateOverviewOverviewInfo({ indexMeta }) {
+ const { ntotal, nlist, listSizes } = indexMeta;
+ const items = [
+ {
+ title: 'IVF_Flat',
+ },
+ {
+ text: `${ntotal} vectors, divided into ${nlist} clusters.`,
+ },
+ {
+ text: `The largest cluster has ${d3.max(
+ listSizes
+ )} vectors and the smallest cluster has only ${d3.min(
+ listSizes
+ )} vectors.`,
+ },
+ ];
+
+ this.renderOverviewPanel(items, whiteColor);
+ }
+
+ updateSearchViewCoarseOverviewInfo(searchViewLayoutData, federView) {
+ const {
+ nprobe,
+ clusters,
+ targetMediaUrl,
+ nprobeClusters,
+ switchSearchViewHandlers,
+ } = searchViewLayoutData;
+ const { indexMeta } = federView;
+ const { ntotal, nlist, listSizes } = indexMeta;
+ const { switchVoronoi, switchPolar, switchProject } =
+ switchSearchViewHandlers;
+ const items = [
+ {
+ title: 'IVF_Flat - Search',
+ },
+ {
+ isImg: true,
+ imgUrl: targetMediaUrl,
+ },
+ {
+ isOption: true,
+ isActive: true,
+ label: 'Coarse Search',
+ callback: switchVoronoi,
+ },
+ {
+ isOption: true,
+ isActive: false,
+ label: 'Fine Search (Distance)',
+ callback: switchPolar,
+ },
+ {
+ isOption: true,
+ isActive: false,
+ label: 'Fine Search (Project)',
+ callback: switchProject,
+ },
+ {
+ text: `${ntotal} vectors, divided into ${nlist} clusters.`,
+ },
+ {
+ title: `Find the ${nprobe} (nprobe=${nprobe}) closest clusters.`,
+ },
+ ...nprobeClusters.map(({ clusterId }) => ({
+ text: `cluster-${clusterId} (${
+ listSizes[clusterId]
+ } vectors) dist: ${clusters[clusterId].dis.toFixed(3)}.`,
+ })),
+ ];
+
+ this.renderOverviewPanel(items, whiteColor);
+ }
+
+ updateSearchViewFinePolarOverviewInfo(searchViewLayoutData, federView) {
+ const {
+ k,
+ nprobe,
+ nodes,
+ topKNodes,
+ switchSearchViewHandlers,
+ targetMediaUrl,
+ } = searchViewLayoutData;
+ const { switchVoronoi, switchPolar, switchProject } =
+ switchSearchViewHandlers;
+ const { mediaCallback } = federView;
+ const fineAllVectorsCount = nodes.length;
+ const showImages = topKNodes
+ .map(({ id }) => mediaCallback(id))
+ .filter((a) => a);
+ const items = [
+ {
+ title: 'IVF_Flat - Search',
+ },
+ {
+ isImg: true,
+ imgUrl: targetMediaUrl,
+ },
+ {
+ isOption: true,
+ isActive: false,
+ label: 'Coarse Search',
+ callback: switchVoronoi,
+ },
+ {
+ isOption: true,
+ isActive: true,
+ label: 'Fine Search (Distance)',
+ callback: switchPolar,
+ },
+ {
+ isOption: true,
+ isActive: false,
+ label: 'Fine Search (Project)',
+ callback: switchProject,
+ },
+ {
+ text: `Find the ${k} (k=${k}) vectors closest to the target from these ${nprobe} (nprobe=${nprobe}) clusters, ${fineAllVectorsCount} vectors in total.`,
+ },
+ {
+ images: showImages,
+ },
+ ];
+
+ this.renderOverviewPanel(items, whiteColor);
+ }
+
+ updateSearchViewFineProjectOverviewInfo(searchViewLayoutData, federView) {
+ const {
+ nprobe,
+ nodes,
+ topKNodes,
+ targetMediaUrl,
+ switchSearchViewHandlers,
+ } = searchViewLayoutData;
+ const { switchVoronoi, switchPolar, switchProject } =
+ switchSearchViewHandlers;
+ const { mediaCallback, viewParams } = federView;
+ const fineAllVectorsCount = nodes.length;
+ const showImages = topKNodes
+ .map(({ id }) => mediaCallback(id))
+ .filter((a) => a);
+ const items = [
+ {
+ title: 'IVF_Flat - Search',
+ },
+ {
+ isImg: true,
+ imgUrl: targetMediaUrl,
+ },
+ {
+ isOption: true,
+ isActive: false,
+ label: 'Coarse Search',
+ callback: switchVoronoi,
+ },
+ {
+ isOption: true,
+ isActive: false,
+ label: 'Fine Search (Distance)',
+ callback: switchPolar,
+ },
+ {
+ isOption: true,
+ isActive: true,
+ label: 'Fine Search (Project)',
+ callback: switchProject,
+ },
+ {
+ text: `Projection of all ${fineAllVectorsCount} vectors in the ${nprobe} (nprobe=${nprobe}) clusters using ${
+ viewParams.projectMethod || 'random'
+ }.`,
+ },
+ {
+ images: showImages,
+ },
+ ];
+
+ this.renderOverviewPanel(items, whiteColor);
+ }
+
+ initHoveredPanel() {
+ const dom = this.dom;
+ const hoveredPanel = document.createElement('div');
+ hoveredPanel.setAttribute('id', hoveredPanelId);
+ hoveredPanel.className = hoveredPanel.className + 'panel-border panel hide';
+ const hoveredPanelStyle = {
+ position: 'absolute',
+ left: 0,
+ // right: '30px',
+ top: 0,
+ width: '200px',
+ // display: 'flex',
+ pointerEvents: 'none',
+ backgroundColor: panelBackgroundColor,
+ };
+ Object.assign(hoveredPanel.style, hoveredPanelStyle);
+ dom.appendChild(hoveredPanel);
+ }
+
+ updateOverviewHoveredInfo({
+ hoveredCluster = null,
+ listIds = [],
+ images = [],
+ x = 0,
+ y = 0,
+ } = {}) {
+ const showImages = randomSelect(images, 9).filter((a) => a);
+ // console.log('showImages', showImages);
+ const items = hoveredCluster
+ ? [
+ {
+ title: `cluster-${hoveredCluster.clusterId}`,
+ },
+ {
+ text: `including ${listIds.length} vectors`,
+ },
+ {
+ images: showImages,
+ },
+ ]
+ : [];
+
+ this.renderHoveredPanel(items, ZYellow, x, y);
+ }
+
+ updateSearchViewHoveredInfo({
+ hoveredCluster = null,
+ listIds = [],
+ images = [],
+ x = 0,
+ y = 0,
+ } = {}) {
+ if (!hoveredCluster) {
+ this.renderHoveredPanel();
+ } else {
+ const showImages = randomSelect(
+ images.filter((a) => a),
+ 9
+ );
+ // console.log('showImages', showImages, images);
+ const items = hoveredCluster
+ ? [
+ {
+ title: `cluster-${hoveredCluster.clusterId}`,
+ },
+ {
+ text: `including ${listIds.length} vectors`,
+ },
+ {
+ text: `distance from center: ${hoveredCluster.dis.toFixed(3)}`,
+ },
+ {
+ images: showImages,
+ },
+ ]
+ : [];
+
+ this.renderHoveredPanel(items, ZYellow, x, y);
+ }
+ }
+
+ updateSearchViewHoveredNodeInfo({
+ hoveredNode = null,
+ img = '',
+ x = 0,
+ y = 0,
+ } = {}) {
+ if (!hoveredNode) {
+ this.renderHoveredPanel();
+ } else {
+ const items = hoveredNode
+ ? [
+ {
+ title: `Row No. ${hoveredNode.id}`,
+ },
+ {
+ text: `belong to cluster-${hoveredNode.listId}`,
+ },
+ {
+ text: `distance the target: ${hoveredNode.dis.toFixed(3)}`,
+ },
+ {
+ isImg: true,
+ imgUrl: img,
+ },
+ ]
+ : [];
+
+ this.renderHoveredPanel(items, ZYellow, x, y);
+ }
+ }
+
+ initStyle() {
+ const style = document.createElement('style');
+ style.type = 'text/css';
+ style.innerHTML = `
+ .panel-border {
+ border-style: dashed;
+ border-width: 1px;
+ }
+ .panel {
+ padding: 6px 8px;
+ font-size: 12px;
+ }
+ .hide {
+ opacity: 0;
+ }
+ .panel-item {
+ margin-bottom: 6px;
+ }
+ .panel-img {
+ width: 150px;
+ height: 100px;
+ background-size: cover;
+ margin-bottom: 12px;
+ border-radius: 4px;
+ border: 1px solid ${ZYellow};
+ }
+ .panel-item-display-flex {
+ display: flex;
+ }
+ .panel-item-title {
+ font-weight: 600;
+ margin-bottom: 3px;
+ }
+ .panel-item-text {
+ font-weight: 400;
+ font-size: 10px;
+ word-break: break-all;
+ }
+ .panel-item-text-flex {
+ margin-left: 8px;
+ }
+ .panel-item-text-margin {
+ margin: 0 6px;
+ }
+ .text-no-wrap {
+ white-space: nowrap;
+ }
+ .panel-img-gallery {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-row-gap: 8px;
+ grid-column-gap: 8px;
+ // flex-wrap: wrap;
+ }
+ .panel-img-gallery-item {
+ width: 100%;
+ height: 44px;
+ background-size: cover;
+ border-radius: 2px;
+ // border: 1px solid ${ZYellow};
+ }
+ .panel-item-option {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ // pointer-events: auto;
+ }
+ .panel-item-option-icon {
+ width: 6px;
+ height: 6px;
+ border-radius: 7px;
+ border: 2px solid ${ZYellow};
+ margin-left: 2px;
+ }
+ .panel-item-option-icon-active {
+ background-color: ${ZYellow};
+ }
+ .panel-item-option-label {
+ margin-left: 6px;
+ }
+ `;
+ document.getElementsByTagName('head').item(0).appendChild(style);
+ }
+ renderHoveredPanel(itemList = [], color = '#000', x = 0, y = 0) {
+ const panel = d3.select(this.dom).select(`#${hoveredPanelId}`);
+ if (itemList.length === 0) panel.classed('hide', true);
+ else {
+ panel.style('color', color);
+ // panel.style.left = x + 'px';
+ // panel.style.top = y + 'px';
+ const isLeft = x > this.width * 0.7;
+ if (isLeft) {
+ panel.style('left', null);
+ panel.style('right', this.width - x - 10 + 'px');
+ } else {
+ panel.style('left', x + 10 + 'px');
+ panel.style('right', null);
+ }
+
+ const isTop = y > this.height * 0.7;
+ if (isTop) {
+ panel.style('top', null);
+ panel.style('bottom', this.height - y - 6 + 'px');
+ } else {
+ panel.style('top', y + 6 + 'px');
+ panel.style('bottom', null);
+ }
+
+ this.renderPanel(panel, itemList);
+ }
+ }
+ renderOverviewPanel(itemList = [], color) {
+ const panel = d3.select(this.dom).select(`#${overviewPanelId}`);
+ panel.style('color', color);
+ if (itemList.length === 0) panel.classed('hide', true);
+ else {
+ this.renderPanel(panel, itemList);
+ }
+ }
+ renderPanel(panel, itemList) {
+ panel.classed('hide', false);
+ panel.selectAll('*').remove();
+
+ itemList.forEach((item) => {
+ const div = panel.append('div');
+ div.classed('panel-item', true);
+ item.isFlex && div.classed('panel-item-display-flex', true);
+ if (item.isImg && item.imgUrl) {
+ div.classed('panel-img', true);
+ div.style('background-image', `url(${item.imgUrl})`);
+ }
+ if (item.title) {
+ const title = div.append('div');
+ title.classed('panel-item-title', true);
+ title.text(item.title);
+ }
+ if (item.text) {
+ const title = div.append('div');
+ title.classed('panel-item-text', true);
+ item.isFlex && title.classed('panel-item-text-flex', true);
+ item.textWithMargin && title.classed('panel-item-text-margin', true);
+ item.noWrap && title.classed('text-no-wrap', true);
+ title.text(item.text);
+ }
+ if (item.images) {
+ const imagesDiv = div.append('div');
+ imagesDiv.classed('panel-img-gallery', true);
+ item.images.forEach((url) => {
+ const imgItem = imagesDiv.append('div');
+ imgItem.classed('panel-img-gallery-item', true);
+ imgItem.style('background-image', `url(${url})`);
+ });
+ }
+ if (item.isOption) {
+ const optionDiv = div.append('div');
+ optionDiv.classed('panel-item-option', true);
+ const optionIcon = optionDiv.append('div');
+ optionIcon.classed('panel-item-option-icon', true);
+ if (item.isActive)
+ optionIcon.classed('panel-item-option-icon-active', true);
+ else optionIcon.classed('panel-item-option-icon-active', false);
+
+ const optionLabel = optionDiv.append('div');
+ optionLabel.classed('panel-item-option-label', true);
+ optionLabel.text(item.label);
+
+ item.callback && optionDiv.on('click', item.callback);
+ }
+ });
+ }
+}
diff --git a/federjs_old/FederView/IvfflatView/index.js b/federjs_old/FederView/IvfflatView/index.js
new file mode 100644
index 0000000..09cd9a8
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/index.js
@@ -0,0 +1,378 @@
+import * as d3 from 'd3';
+import BaseView from '../BaseView.js';
+import overviewLayoutHandler from './layout/overviewLayout';
+import renderVoronoiView from './render/renderVoronoiView';
+import renderNodeView from './render/renderNodeView';
+import mouse2voronoi from './layout/mouse2voronoi';
+import mouse2node from './layout/mouse2node';
+import searchViewLayoutHandler from './layout/searchViewLayout';
+import { SEARCH_VIEW_TYPE, VIEW_TYPE } from 'Types.js';
+import animateCoarse2Fine from './render/animateCoarse2Fine';
+import animateFine2Coarse from './render/animateFine2Coarse';
+import animateFine2Fine from './render/animateFine2Fine';
+import InfoPanel from './InfoPanel';
+
+const defaultIvfflatViewParams = {
+ minVoronoiRadius: 4,
+ // voronoiForceTime: 3000,
+ // nodeCollisionForceTime: 1000,
+ backgroundColor: 'red',
+ voronoiStrokeWidth: 2,
+ targetNodeStrokeWidth: 5,
+ targetNodeR: 7,
+ topKNodeR: 5,
+ hoveredNodeR: 6,
+ hoveredNodeStrokeWidth: 1,
+ hoveredNodeOpacity: 1,
+ topKNodeOpacity: 0.7,
+ topKNodeStrokeWidth: 1,
+ nonTopKNodeR: 3,
+ nonTopKNodeOpacity: 0.4,
+ projectPadding: [20, 20, 20, 260],
+ axisTickCount: 5,
+ polarAxisStrokeWidth: 1,
+ polarAxisOpacity: 0.4,
+ polarOriginBias: 0.15,
+ ease: d3.easeCubic,
+ animateExitTime: 1500,
+ animateEnterTime: 1000,
+ fineSearchNodeTransTime: 1200,
+ forceIterations: 100,
+};
+
+export default class IvfflatView extends BaseView {
+ constructor({ indexMeta, viewParams, getVectorById }) {
+ super({
+ viewParams,
+ getVectorById,
+ });
+ for (let key in defaultIvfflatViewParams) {
+ this[key] =
+ key in viewParams ? viewParams[key] : defaultIvfflatViewParams[key];
+ }
+
+ this.projectPadding = this.projectPadding.map(
+ (num) => num * this.canvasScale
+ );
+ this.indexMeta = indexMeta;
+ this.overviewLayoutHandler();
+ }
+ initInfoPanel(dom) {
+ const infoPanel = new InfoPanel({
+ dom,
+ width: this.clientWidth,
+ height: this.clientHeight,
+ });
+ return infoPanel;
+ }
+ overviewLayoutHandler() {
+ this.overviewLayoutPromise = overviewLayoutHandler(this).then(
+ ({ clusters, voronoi }) => {
+ // this.clusters = clusters;
+ // this.OVVoronoi = voronoi;
+ this.overviewLayoutData = { clusters, OVVoronoi: voronoi };
+ }
+ );
+ }
+ renderOverview(ctx, infoPanel) {
+ infoPanel.updateOverviewOverviewInfo(this);
+ renderVoronoiView(ctx, VIEW_TYPE.overview, this.overviewLayoutData, this);
+ }
+ getOverviewEventHandler(ctx, infoPanel) {
+ let hoveredClusterId = null;
+
+ const mouseLeaveHandler = () => {
+ hoveredClusterId = null;
+ renderVoronoiView(ctx, VIEW_TYPE.overview, this.overviewLayoutData, this);
+ infoPanel.updateOverviewHoveredInfo();
+ };
+
+ const mouseMoveHandler = ({ x, y }) => {
+ const currentHoveredClusterId = mouse2voronoi({
+ voronoi: this.overviewLayoutData.OVVoronoi,
+ x,
+ y,
+ });
+
+ if (hoveredClusterId !== currentHoveredClusterId) {
+ hoveredClusterId = currentHoveredClusterId;
+ const hoveredCluster = this.overviewLayoutData.clusters.find(
+ (cluster) => cluster.clusterId == hoveredClusterId
+ );
+ if (!!hoveredCluster) {
+ renderVoronoiView(
+ ctx,
+ VIEW_TYPE.overview,
+ this.overviewLayoutData,
+ this,
+ hoveredCluster
+ );
+ infoPanel.updateOverviewHoveredInfo({
+ hoveredCluster,
+ listIds: this.indexMeta.listIds[hoveredClusterId],
+ images: this.mediaCallback
+ ? this.indexMeta.listIds[hoveredClusterId].map((listId) =>
+ this.mediaCallback(listId)
+ )
+ : [],
+ x: hoveredCluster.OVPolyCentroid[0] / this.canvasScale,
+ y: hoveredCluster.OVPolyCentroid[1] / this.canvasScale,
+ });
+ }
+ }
+ };
+
+ return { mouseLeaveHandler, mouseMoveHandler };
+ }
+
+ async searchViewHandler(searchRes) {
+ this.overviewLayoutPromise && (await this.overviewLayoutPromise);
+
+ const searchViewLayoutData = {
+ nprobe: searchRes.csResIds.length,
+ k: searchRes.fsResIds.length,
+ clusters: JSON.parse(JSON.stringify(this.overviewLayoutData.clusters)),
+ };
+ searchViewLayoutData.nprobeClusters = searchViewLayoutData.clusters.filter(
+ (cluster) => searchRes.csResIds.indexOf(cluster.clusterId) >= 0
+ );
+ searchViewLayoutData.nonNprobeClusters =
+ searchViewLayoutData.clusters.filter(
+ (cluster) => searchRes.csResIds.indexOf(cluster.clusterId) < 0
+ );
+ searchViewLayoutData.colorScheme = d3
+ .range(searchViewLayoutData.nprobe)
+ .map((i) =>
+ d3.hsl((360 * i) / searchViewLayoutData.nprobe, 1, 0.5).formatHex()
+ );
+ await searchViewLayoutHandler(searchRes, searchViewLayoutData, this);
+ return searchViewLayoutData;
+ }
+ renderSearchView(ctx, infoPanel, searchViewLayoutData, targetMediaUrl) {
+ searchViewLayoutData.targetMediaUrl = targetMediaUrl;
+ searchViewLayoutData.switchSearchViewHandlers = {
+ switchVoronoi: () =>
+ this.switchSearchView(
+ SEARCH_VIEW_TYPE.voronoi,
+ ctx,
+ infoPanel,
+ searchViewLayoutData
+ ),
+ switchPolar: () =>
+ this.switchSearchView(
+ SEARCH_VIEW_TYPE.polar,
+ ctx,
+ infoPanel,
+ searchViewLayoutData
+ ),
+ switchProject: () =>
+ this.switchSearchView(
+ SEARCH_VIEW_TYPE.project,
+ ctx,
+ infoPanel,
+ searchViewLayoutData
+ ),
+ };
+ searchViewLayoutData.searchViewType = SEARCH_VIEW_TYPE.voronoi;
+ this.updateCoarseSearchInfoPanel(infoPanel, searchViewLayoutData);
+ this.renderCoarseSearch(ctx, searchViewLayoutData);
+ }
+ renderCoarseSearch(ctx, searchViewLayoutData) {
+ renderVoronoiView(ctx, VIEW_TYPE.search, searchViewLayoutData, this);
+ }
+ updateCoarseSearchInfoPanel(infoPanel, searchViewLayoutData) {
+ infoPanel.setOverviewPanelPos(
+ !searchViewLayoutData.targetNode.isLeft_coarseLevel
+ );
+ infoPanel.updateSearchViewCoarseOverviewInfo(searchViewLayoutData, this);
+ }
+ renderFineSearch(
+ ctx,
+ searchViewLayoutData,
+ searchViewType = SEARCH_VIEW_TYPE.polar,
+ hoveredNode
+ ) {
+ renderNodeView(
+ ctx,
+ searchViewLayoutData,
+ this,
+ searchViewType,
+ hoveredNode
+ );
+ }
+ updateFineSearchInfoPanel(
+ infoPanel,
+ searchViewLayoutData,
+ searchViewType = SEARCH_VIEW_TYPE.polar
+ ) {
+ searchViewType === SEARCH_VIEW_TYPE.polar &&
+ infoPanel.updateSearchViewFinePolarOverviewInfo(
+ searchViewLayoutData,
+ this
+ );
+ searchViewType === SEARCH_VIEW_TYPE.project &&
+ infoPanel.updateSearchViewFineProjectOverviewInfo(
+ searchViewLayoutData,
+ this
+ );
+ }
+ switchSearchView(searchViewType, ctx, infoPanel, searchViewLayoutData) {
+ if (searchViewType == searchViewLayoutData.searchViewType) return;
+
+ const oldSearchViewType = searchViewLayoutData.searchViewType;
+ const newSearchViewType = searchViewType;
+
+ // coarse => fine
+
+ if (oldSearchViewType === SEARCH_VIEW_TYPE.voronoi) {
+ // console.log('coarse => fine [start]');
+ this.updateFineSearchInfoPanel(
+ infoPanel,
+ searchViewLayoutData,
+ newSearchViewType
+ );
+ const endCallback = () => {
+ searchViewLayoutData.searchViewType = newSearchViewType;
+ this.renderFineSearch(ctx, searchViewLayoutData, newSearchViewType);
+ };
+ animateCoarse2Fine(
+ oldSearchViewType,
+ newSearchViewType,
+ ctx,
+ searchViewLayoutData,
+ this,
+ endCallback
+ );
+ }
+
+ // fine => coarse
+ if (newSearchViewType === SEARCH_VIEW_TYPE.voronoi) {
+ // console.log('fine => coarse [start]');
+ this.updateCoarseSearchInfoPanel(infoPanel, searchViewLayoutData);
+ const endCallback = () => {
+ searchViewLayoutData.searchViewType = newSearchViewType;
+ this.renderCoarseSearch(ctx, searchViewLayoutData);
+ };
+ animateFine2Coarse(
+ oldSearchViewType,
+ newSearchViewType,
+ ctx,
+ searchViewLayoutData,
+ this,
+ endCallback
+ );
+ }
+
+ // fine - intra
+ if (
+ newSearchViewType !== SEARCH_VIEW_TYPE.voronoi &&
+ oldSearchViewType !== SEARCH_VIEW_TYPE.voronoi
+ ) {
+ this.updateFineSearchInfoPanel(
+ infoPanel,
+ searchViewLayoutData,
+ newSearchViewType
+ );
+ // console.log('fine - intra [start]');
+ const endCallback = () => {
+ searchViewLayoutData.searchViewType = newSearchViewType;
+ this.renderFineSearch(ctx, searchViewLayoutData, newSearchViewType);
+ };
+ animateFine2Fine(
+ oldSearchViewType,
+ newSearchViewType,
+ ctx,
+ searchViewLayoutData,
+ this,
+ endCallback
+ );
+ }
+ }
+ getSearchViewEventHandler(ctx, searchViewLayoutData, infoPanel) {
+ let hoveredClusterId = null;
+ let hoveredNode = null;
+ const mouseLeaveHandler = () => {
+ hoveredClusterId = null;
+ hoveredNode = null;
+ const { searchViewType } = searchViewLayoutData;
+ if (searchViewType === SEARCH_VIEW_TYPE.voronoi) {
+ renderVoronoiView(ctx, VIEW_TYPE.search, searchViewLayoutData, this);
+ infoPanel.updateSearchViewHoveredInfo();
+ } else {
+ infoPanel.updateSearchViewHoveredNodeInfo();
+ }
+ };
+ const mouseMoveHandler = ({ x, y }) => {
+ const { searchViewType, clusters, nodes } = searchViewLayoutData;
+ if (searchViewType === SEARCH_VIEW_TYPE.voronoi) {
+ const currentHoveredClusterId = mouse2voronoi({
+ voronoi: searchViewLayoutData.SVVoronoi,
+ x,
+ y,
+ });
+ if (hoveredClusterId !== currentHoveredClusterId) {
+ hoveredClusterId = currentHoveredClusterId;
+ const hoveredCluster = clusters.find(
+ (cluster) => cluster.clusterId == hoveredClusterId
+ );
+ renderVoronoiView(
+ ctx,
+ VIEW_TYPE.search,
+ searchViewLayoutData,
+ this,
+ hoveredCluster
+ );
+
+ if (!!hoveredCluster) {
+ infoPanel.updateSearchViewHoveredInfo({
+ hoveredCluster,
+ listIds: this.indexMeta.listIds[hoveredClusterId],
+ images: this.mediaCallback
+ ? this.indexMeta.listIds[hoveredClusterId].map((listId) =>
+ this.mediaCallback(listId)
+ )
+ : [],
+ x: hoveredCluster.SVPolyCentroid[0] / this.canvasScale,
+ y: hoveredCluster.SVPolyCentroid[1] / this.canvasScale,
+ });
+ }
+ }
+ } else {
+ if (!nodes) return;
+ const nodesPos =
+ searchViewType === SEARCH_VIEW_TYPE.polar
+ ? nodes.map((node) => node.polarPos)
+ : nodes.map((node) => node.projectPos);
+ const hoveredNodeIndex = mouse2node({
+ nodesPos,
+ x,
+ y,
+ bias: (this.hoveredNodeR + 2) * this.canvasScale,
+ });
+ const currentHoveredNode =
+ hoveredNodeIndex >= 0 ? nodes[hoveredNodeIndex] : null;
+ if (hoveredNode !== currentHoveredNode) {
+ hoveredNode = currentHoveredNode;
+ this.renderFineSearch(
+ ctx,
+ searchViewLayoutData,
+ searchViewType,
+ hoveredNode
+ );
+ }
+
+ const img =
+ hoveredNode && this.mediaCallback
+ ? this.mediaCallback(hoveredNode.id)
+ : '';
+ infoPanel.updateSearchViewHoveredNodeInfo({
+ hoveredNode,
+ img,
+ x: x / this.canvasScale,
+ y: y / this.canvasScale,
+ });
+ }
+ };
+ return { mouseLeaveHandler, mouseMoveHandler };
+ }
+}
diff --git a/federjs_old/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js b/federjs_old/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js
new file mode 100644
index 0000000..931673e
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js
@@ -0,0 +1,153 @@
+import * as d3 from 'd3';
+import getVoronoi from './getVoronoi';
+import { vecSort } from 'Utils';
+
+export default function SVCoarseVoronoiHandler(
+ searchRes,
+ searchViewLayoutData,
+ federView
+) {
+ return new Promise((resolve) => {
+ const { clusters, nprobeClusters, nprobe } = searchViewLayoutData;
+ const { width, height, forceIterations, polarOriginBias } = federView;
+ const fineClusterOrder = vecSort(
+ nprobeClusters,
+ 'OVPolyCentroid',
+ 'clusterId'
+ );
+ // console.log('fineClusterOrder', fineClusterOrder);
+
+ const targetClusterId = searchRes.coarse[0].id;
+ const targetCluster = clusters.find(
+ (cluster) => cluster.clusterId === targetClusterId
+ );
+ const otherFineClustersId = fineClusterOrder.filter(
+ (clusterId) => clusterId !== targetClusterId
+ );
+ const links = otherFineClustersId.map((clusterId) => ({
+ source: clusterId,
+ target: targetClusterId,
+ }));
+ clusters.forEach((cluster) => {
+ cluster.x = cluster.forceProjection[0];
+ cluster.y = cluster.forceProjection[1];
+ });
+ const targetClusterX =
+ nprobeClusters.reduce((acc, cluster) => acc + cluster.x, 0) / nprobe;
+ const targetClusterY =
+ nprobeClusters.reduce((acc, cluster) => acc + cluster.y, 0) / nprobe;
+ targetCluster.x = targetClusterX;
+ targetCluster.y = targetClusterY;
+
+ const otherFineCluster = otherFineClustersId.map((clusterId) =>
+ nprobeClusters.find((cluster) => cluster.clusterId === clusterId)
+ );
+ const angleStep = (2 * Math.PI) / (nprobe - 1);
+ const biasR = targetCluster.r * 0.5;
+ otherFineCluster.forEach((cluster, i) => {
+ cluster.x = targetClusterX + biasR * Math.sin(angleStep * i);
+ cluster.y = targetClusterY + biasR * Math.cos(angleStep * i);
+ });
+
+ const simulation = d3
+ .forceSimulation(clusters)
+ .alphaDecay(1 - Math.pow(0.001, 1 / forceIterations))
+ .force(
+ 'links',
+ d3
+ .forceLink(links)
+ .id((cluster) => cluster.clusterId)
+ .strength((_) => 0.25)
+ )
+ .force(
+ 'collision',
+ d3
+ .forceCollide()
+ .radius((cluster) => cluster.r)
+ .strength(0.1)
+ )
+ .force('center', d3.forceCenter(width / 2, height / 2))
+ .on('tick', () => {
+ // border
+ clusters.forEach((cluster) => {
+ cluster.x = Math.max(
+ cluster.r,
+ Math.min(width - cluster.r, cluster.x)
+ );
+ cluster.y = Math.max(
+ cluster.r,
+ Math.min(height - cluster.r, cluster.y)
+ );
+ });
+ })
+ .on('end', () => {
+ clusters.forEach((cluster) => {
+ cluster.SVPos = [cluster.x, cluster.y];
+ });
+ const voronoi = getVoronoi(clusters, width, height);
+ clusters.forEach((cluster, i) => {
+ const points = voronoi.cellPolygon(i);
+ points.pop();
+ cluster.SVPolyPoints = points;
+ cluster.SVPolyCentroid = d3.polygonCentroid(points);
+ });
+ searchViewLayoutData.SVVoronoi = voronoi;
+
+ const targetCluster = clusters.find(
+ (cluster) => cluster.clusterId === targetClusterId
+ );
+ const centoid_fineClusters_x =
+ nprobeClusters.reduce(
+ (acc, cluster) => acc + cluster.SVPolyCentroid[0],
+ 0
+ ) / nprobe;
+ const centroid_fineClusters_y =
+ nprobeClusters.reduce(
+ (acc, cluster) => acc + cluster.SVPolyCentroid[1],
+ 0
+ ) / nprobe;
+ const _x = centoid_fineClusters_x - targetCluster.SVPos[0];
+ const _y = centroid_fineClusters_y - targetCluster.SVPos[1];
+ const biasR = Math.sqrt(_x * _x + _y * _y);
+ const targetNode = {
+ SVPos: [
+ targetCluster.SVPos[0] + targetCluster.r * 0.4 * (_x / biasR),
+ targetCluster.SVPos[1] + targetCluster.r * 0.4 * (_y / biasR),
+ ],
+ };
+ targetNode.isLeft_coarseLevel = targetNode.SVPos[0] < width / 2;
+ searchViewLayoutData.targetNode = targetNode;
+
+ const polarOrigin = [
+ width / 2 +
+ (targetNode.isLeft_coarseLevel ? -1 : 1) * polarOriginBias * width,
+ height / 2,
+ ];
+ // const polarOrigin = [width / 2, height / 2];
+ searchViewLayoutData.polarOrigin = polarOrigin;
+ targetNode.polarPos = polarOrigin;
+ const polarMaxR = Math.min(width, height) * 0.5 - 5;
+ searchViewLayoutData.polarMaxR = polarMaxR;
+ const angleStep = (Math.PI * 2) / fineClusterOrder.length;
+ nprobeClusters.forEach((cluster) => {
+ const order = fineClusterOrder.indexOf(cluster.clusterId);
+ cluster.polarOrder = order;
+ cluster.SVNextLevelPos = [
+ polarOrigin[0] + (polarMaxR / 2) * Math.sin(angleStep * order),
+ polarOrigin[1] + (polarMaxR / 2) * Math.cos(angleStep * order),
+ ];
+ cluster.SVNextLevelTran = [
+ cluster.SVNextLevelPos[0] - cluster.SVPolyCentroid[0],
+ cluster.SVNextLevelPos[1] - cluster.SVPolyCentroid[1],
+ ];
+ });
+ const clusterId2cluster = {};
+ nprobeClusters.forEach((cluster) => {
+ clusterId2cluster[cluster.clusterId] = cluster;
+ });
+ searchViewLayoutData.clusterId2cluster = clusterId2cluster;
+
+ resolve();
+ });
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/layout/SVFinePolarHandler.js b/federjs_old/FederView/IvfflatView/layout/SVFinePolarHandler.js
new file mode 100644
index 0000000..154f9cd
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/SVFinePolarHandler.js
@@ -0,0 +1,67 @@
+import * as d3 from 'd3';
+
+export default function SVFinePolarHandler(
+ searchRes,
+ searchViewLayoutData,
+ federView
+) {
+ return new Promise((resolve) => {
+ const { polarMaxR, polarOrigin, clusterId2cluster } = searchViewLayoutData;
+ const { forceIterations, nonTopKNodeR, canvasScale } = federView;
+ const nodes = searchRes.fine;
+
+ const distances = nodes
+ .map((node) => node.dis)
+ .filter((a) => a > 0)
+ .sort();
+ const minDis = distances.length > 0 ? distances[0] : 0;
+ const maxDis =
+ distances.length > 0
+ ? distances[Math.round((distances.length - 1) * 0.98)]
+ : 0;
+ const r = d3
+ .scaleLinear()
+ .domain([minDis, maxDis])
+ .range([polarMaxR * 0.2, polarMaxR])
+ .clamp(true);
+
+ nodes.forEach((node) => {
+ const cluster = clusterId2cluster[node.listId];
+ const { polarOrder, SVNextLevelPos } = cluster;
+ node.polarOrder = polarOrder;
+ const randAngle = Math.random() * Math.PI * 2;
+ const randBias = [Math.sin, Math.cos].map(
+ (f) => cluster.r * Math.random() * 0.7 * f(randAngle)
+ );
+ node.voronoiPos = SVNextLevelPos.map((d, i) => d + randBias[i]);
+ node.x = node.voronoiPos[0];
+ node.y = node.voronoiPos[1];
+ node.r = r(node.dis);
+ });
+
+ const simulation = d3
+ .forceSimulation(nodes)
+ .alphaDecay(1 - Math.pow(0.001, (1 / forceIterations) * 2))
+ .force(
+ 'collide',
+ d3
+ .forceCollide()
+ .radius((_) => nonTopKNodeR * canvasScale)
+ .strength(0.4)
+ )
+ .force('r', d3.forceRadial((node) => node.r, ...polarOrigin).strength(1))
+ .on('end', () => {
+ nodes.forEach((node) => {
+ node.polarPos = [node.x, node.y];
+ });
+ searchViewLayoutData.nodes = nodes;
+ searchViewLayoutData.topKNodes = nodes.filter((node) =>
+ searchRes.fsResIds.find((id) => id == node.id)
+ );
+ searchViewLayoutData.nonTopKNodes = nodes.filter(
+ (node) => !searchRes.fsResIds.find((id) => id == node.id)
+ );
+ resolve();
+ });
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/layout/SVFineProjectHandler.js b/federjs_old/FederView/IvfflatView/layout/SVFineProjectHandler.js
new file mode 100644
index 0000000..fc1b200
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/SVFineProjectHandler.js
@@ -0,0 +1,30 @@
+import * as d3 from 'd3';
+
+export default function SVFineProjectHandler(
+ searchRes,
+ searchViewLayoutData,
+ federView
+) {
+ const { nodes, targetNode } = searchViewLayoutData;
+ const { projectPadding, width, height } = federView;
+ if (!nodes[0].projection) {
+ console.log('No Projection Data. Should use "fineWithProjection".');
+ nodes.forEach((node) => {
+ node.projection = [Math.random(), Math.random()];
+ });
+ }
+ const xRange = targetNode.isLeft_coarseLevel
+ ? [projectPadding[1], width - projectPadding[3]]
+ : [projectPadding[3], width - projectPadding[1]];
+ const x = d3
+ .scaleLinear()
+ .domain(d3.extent(nodes, (node) => node.projection[0]))
+ .range(xRange);
+ const y = d3
+ .scaleLinear()
+ .domain(d3.extent(nodes, (node) => node.projection[1]))
+ .range([projectPadding[0], height - projectPadding[2]]);
+ nodes.forEach((node) => {
+ node.projectPos = [x(node.projection[0]), y(node.projection[1])];
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/layout/getVoronoi.js b/federjs_old/FederView/IvfflatView/layout/getVoronoi.js
new file mode 100644
index 0000000..c9e0ba4
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/getVoronoi.js
@@ -0,0 +1,9 @@
+import * as d3 from 'd3';
+
+export default function getVoronoi(clusters, width, height) {
+ const delaunay = d3.Delaunay.from(
+ clusters.map((cluster) => [cluster.x, cluster.y])
+ );
+ const voronoi = delaunay.voronoi([0, 0, width, height]);
+ return voronoi;
+}
diff --git a/federjs_old/FederView/IvfflatView/layout/mouse2node.js b/federjs_old/FederView/IvfflatView/layout/mouse2node.js
new file mode 100644
index 0000000..19ce021
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/mouse2node.js
@@ -0,0 +1,8 @@
+import { dist2 } from 'Utils';
+import * as d3 from 'd3';
+
+export default function mouse2node({ nodesPos, x, y, bias }) {
+ const minIndex = d3.minIndex(nodesPos, (nodePos) => dist2(nodePos, [x, y]));
+
+ return dist2(nodesPos[minIndex], [x, y]) > Math.pow(bias, 2) ? -1 : minIndex;
+}
diff --git a/federjs_old/FederView/IvfflatView/layout/mouse2voronoi.js b/federjs_old/FederView/IvfflatView/layout/mouse2voronoi.js
new file mode 100644
index 0000000..ba6636b
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/mouse2voronoi.js
@@ -0,0 +1,3 @@
+export default function mouse2node({ voronoi, x, y }) {
+ return voronoi.delaunay.find(x, y);
+}
\ No newline at end of file
diff --git a/federjs_old/FederView/IvfflatView/layout/overviewLayout.js b/federjs_old/FederView/IvfflatView/layout/overviewLayout.js
new file mode 100644
index 0000000..6862e16
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/overviewLayout.js
@@ -0,0 +1,78 @@
+import * as d3 from 'd3';
+import getVoronoi from './getVoronoi';
+
+export default function overviewLayoutHandler({
+ indexMeta,
+ width,
+ height,
+ canvasScale,
+ minVoronoiRadius,
+ forceIterations,
+}) {
+ return new Promise((resolve) => {
+ const allArea = width * height;
+ const { ntotal, listCentroidProjections = null, listSizes } = indexMeta;
+ const clusters = listSizes.map((listSize, i) => ({
+ clusterId: i,
+ oriProjection: listCentroidProjections
+ ? listCentroidProjections[i]
+ : [Math.random(), Math.random()],
+ count: listSize,
+ countP: listSize / ntotal,
+ countArea: allArea * (listSize / ntotal),
+ }));
+
+ const x = d3
+ .scaleLinear()
+ .domain(d3.extent(clusters, (cluster) => cluster.oriProjection[0]))
+ .range([0, width]);
+ const y = d3
+ .scaleLinear()
+ .domain(d3.extent(clusters, (cluster) => cluster.oriProjection[1]))
+ .range([0, height]);
+
+ clusters.forEach((cluster) => {
+ cluster.x = x(cluster.oriProjection[0]);
+ cluster.y = y(cluster.oriProjection[1]);
+ cluster.r = Math.max(
+ minVoronoiRadius * canvasScale,
+ Math.sqrt(cluster.countArea / Math.PI)
+ );
+ });
+
+ const simulation = d3
+ .forceSimulation(clusters)
+ .alphaDecay(1 - Math.pow(0.001, 1 / forceIterations))
+ .force(
+ 'collision',
+ d3.forceCollide().radius((cluster) => cluster.r)
+ )
+ .force('center', d3.forceCenter(width / 2, height / 2))
+ .on('tick', () => {
+ // border
+ clusters.forEach((cluster) => {
+ cluster.x = Math.max(
+ cluster.r,
+ Math.min(width - cluster.r, cluster.x)
+ );
+ cluster.y = Math.max(
+ cluster.r,
+ Math.min(height - cluster.r, cluster.y)
+ );
+ });
+ })
+ .on('end', () => {
+ clusters.forEach((cluster) => {
+ cluster.forceProjection = [cluster.x, cluster.y];
+ });
+ const voronoi = getVoronoi(clusters, width, height);
+ clusters.forEach((cluster, i) => {
+ const points = voronoi.cellPolygon(i);
+ points.pop();
+ cluster.OVPolyPoints = points;
+ cluster.OVPolyCentroid = d3.polygonCentroid(points);
+ });
+ resolve({ clusters, voronoi });
+ });
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/layout/searchViewLayout.js b/federjs_old/FederView/IvfflatView/layout/searchViewLayout.js
new file mode 100644
index 0000000..e0e072d
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/layout/searchViewLayout.js
@@ -0,0 +1,20 @@
+import SVCoarseVoronoiHandler from './SVCoarseVoronoiHandler';
+import SVFinePolarHandler from './SVFinePolarHandler';
+import SVFineProjectHandler from './SVFineProjectHandler';
+
+export default function searchViewLayoutHandler(
+ searchRes,
+ searchViewLayoutData,
+ federView
+) {
+ return new Promise(async (resolve) => {
+ searchRes.coarse.forEach(
+ ({ id, dis }) => (searchViewLayoutData.clusters[id].dis = dis)
+ );
+ await SVCoarseVoronoiHandler(searchRes, searchViewLayoutData, federView);
+ await SVFinePolarHandler(searchRes, searchViewLayoutData, federView);
+ SVFineProjectHandler(searchRes, searchViewLayoutData, federView);
+ // console.log('searchViewLayoutData', searchViewLayoutData);
+ resolve();
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateCoarse2Fine.js b/federjs_old/FederView/IvfflatView/render/animateCoarse2Fine.js
new file mode 100644
index 0000000..1a540e5
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateCoarse2Fine.js
@@ -0,0 +1,82 @@
+import * as d3 from 'd3';
+import renderBackground from './renderBackground';
+import animateNonNprobeClusters from './animateNonNprobeClusters';
+import animateNprobeClustersTrans from './animateNprobeClustersTrans';
+import animateTargetNode from './animateTargetNode';
+import animateNprobeClustersOpacity from './animateNprobeClustersOpacity';
+import animateNodesOpacityAndTrans from './animateNodesOpacityAndTrans';
+import { ANIMATION_TYPE } from 'Types';
+
+export default function animateCoarse2Fine(
+ oldSearchViewType,
+ newSearchViewType,
+ ctx,
+ searchViewLayoutData,
+ federView,
+ endCallback = () => {}
+) {
+ const { animateExitTime, animateEnterTime } = federView;
+ const stepAllTime = animateExitTime + animateEnterTime;
+ const timer = d3.timer((elapsed) => {
+ renderBackground(ctx, federView);
+
+ animateNonNprobeClusters({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: 0,
+ duration: animateExitTime,
+ animationType: ANIMATION_TYPE.exit,
+ });
+
+ animateNprobeClustersTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: 0,
+ duration: animateExitTime,
+ animationType: ANIMATION_TYPE.exit,
+ });
+
+ animateTargetNode({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: 0,
+ duration: animateExitTime,
+ animationType: ANIMATION_TYPE.exit,
+ newSearchViewType,
+ });
+
+ animateNprobeClustersOpacity({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: animateExitTime,
+ duration: animateEnterTime,
+ animationType: ANIMATION_TYPE.exit,
+ });
+
+ animateNodesOpacityAndTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: animateExitTime,
+ duration: animateEnterTime,
+ animationType: ANIMATION_TYPE.enter,
+ oldSearchViewType,
+ newSearchViewType,
+ });
+
+ if (elapsed >= stepAllTime) {
+ console.log('Coarse => Fine [OK]');
+ timer.stop();
+ endCallback();
+ }
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateFine2Coarse.js b/federjs_old/FederView/IvfflatView/render/animateFine2Coarse.js
new file mode 100644
index 0000000..11fd978
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateFine2Coarse.js
@@ -0,0 +1,83 @@
+import * as d3 from 'd3';
+import renderBackground from './renderBackground';
+import animateNonNprobeClusters from './animateNonNprobeClusters';
+import animateNprobeClustersTrans from './animateNprobeClustersTrans';
+import animateTargetNode from './animateTargetNode';
+import animateNprobeClustersOpacity from './animateNprobeClustersOpacity';
+import animateNodesOpacityAndTrans from './animateNodesOpacityAndTrans';
+import { ANIMATION_TYPE } from 'Types';
+
+export default function animateFine2Coarse(
+ oldSearchViewType,
+ newSearchViewType,
+ ctx,
+ searchViewLayoutData,
+ federView,
+ endCallback = () => {}
+) {
+ const { animateExitTime, animateEnterTime } = federView;
+
+ const stepAllTime = animateExitTime + animateEnterTime;
+ const timer = d3.timer((elapsed) => {
+ renderBackground(ctx, federView);
+
+ animateNodesOpacityAndTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: 0,
+ duration: animateExitTime,
+ animationType: ANIMATION_TYPE.exit,
+ oldSearchViewType,
+ newSearchViewType,
+ });
+
+ animateNprobeClustersOpacity({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: 0,
+ duration: animateExitTime,
+ animationType: ANIMATION_TYPE.enter,
+ });
+
+ animateNonNprobeClusters({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: animateExitTime,
+ duration: animateEnterTime,
+ animationType: ANIMATION_TYPE.enter,
+ });
+
+ animateNprobeClustersTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: animateExitTime,
+ duration: animateEnterTime,
+ animationType: ANIMATION_TYPE.enter,
+ });
+
+ animateTargetNode({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ delay: animateExitTime,
+ duration: animateEnterTime,
+ animationType: ANIMATION_TYPE.enter,
+ newSearchViewType,
+ });
+
+ if (elapsed >= stepAllTime) {
+ console.log('Fine => Coarse [OK]');
+ timer.stop();
+ endCallback();
+ }
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateFine2Fine.js b/federjs_old/FederView/IvfflatView/render/animateFine2Fine.js
new file mode 100644
index 0000000..347d7a4
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateFine2Fine.js
@@ -0,0 +1,32 @@
+import renderBackground from './renderBackground';
+import animateNodesTrans from './animateNodesTrans';
+import * as d3 from 'd3';
+
+export default function animateFine2Fine(
+ oldSearchViewType,
+ newSearchViewType,
+ ctx,
+ searchViewLayoutData,
+ federView,
+ endCallback
+) {
+ const { fineSearchNodeTransTime } = federView;
+ const timer = d3.timer((elapsed) => {
+ renderBackground(ctx, federView);
+ animateNodesTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ duration: fineSearchNodeTransTime,
+ delay: 0,
+ newSearchViewType,
+ });
+
+ if (elapsed >= fineSearchNodeTransTime) {
+ console.log(`${oldSearchViewType} To ${newSearchViewType} OK!`);
+ timer.stop();
+ endCallback();
+ }
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateNodesOpacityAndTrans.js b/federjs_old/FederView/IvfflatView/render/animateNodesOpacityAndTrans.js
new file mode 100644
index 0000000..58461f2
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateNodesOpacityAndTrans.js
@@ -0,0 +1,85 @@
+import { drawCircle, hexWithOpacity, whiteColor } from 'Utils/renderUtils';
+import { ANIMATION_TYPE, SEARCH_VIEW_TYPE } from 'Types';
+
+export default function animateNodesOpacityAndTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ duration,
+ delay,
+ animationType,
+ newSearchViewType,
+ oldSearchViewType,
+}) {
+ const { colorScheme, nprobe, topKNodes, nonTopKNodes } = searchViewLayoutData;
+ const {
+ ease,
+ topKNodeR,
+ topKNodeOpacity,
+ topKNodeStrokeWidth,
+ nonTopKNodeR,
+ nonTopKNodeOpacity,
+ canvasScale,
+ } = federView;
+ let t = ease((elapsed - delay) / duration);
+ if (t > 1 || t < 0) return;
+ t = animationType === ANIMATION_TYPE.enter ? 1 - t : t;
+
+ const nonTopKCircles =
+ (newSearchViewType === SEARCH_VIEW_TYPE.polar &&
+ animationType === ANIMATION_TYPE.enter) ||
+ (oldSearchViewType === SEARCH_VIEW_TYPE.polar &&
+ animationType === ANIMATION_TYPE.exit)
+ ? nonTopKNodes.map((node) => [
+ t * node.voronoiPos[0] + (1 - t) * node.polarPos[0],
+ t * node.voronoiPos[1] + (1 - t) * node.polarPos[1],
+ nonTopKNodeR * canvasScale,
+ node.polarOrder,
+ ])
+ : nonTopKNodes.map((node) => [
+ t * node.voronoiPos[0] + (1 - t) * node.projectPos[0],
+ t * node.voronoiPos[1] + (1 - t) * node.projectPos[1],
+ nonTopKNodeR * canvasScale,
+ node.polarOrder,
+ ]);
+ const topKCircles =
+ (newSearchViewType === SEARCH_VIEW_TYPE.polar &&
+ animationType === ANIMATION_TYPE.enter) ||
+ (oldSearchViewType === SEARCH_VIEW_TYPE.polar &&
+ animationType === ANIMATION_TYPE.exit)
+ ? topKNodes.map((node) => [
+ t * node.voronoiPos[0] + (1 - t) * node.polarPos[0],
+ t * node.voronoiPos[1] + (1 - t) * node.polarPos[1],
+ topKNodeR * canvasScale,
+ node.polarOrder,
+ ])
+ : topKNodes.map((node) => [
+ t * node.voronoiPos[0] + (1 - t) * node.projectPos[0],
+ t * node.voronoiPos[1] + (1 - t) * node.projectPos[1],
+ topKNodeR * canvasScale,
+ node.polarOrder,
+ ]);
+ for (let i = 0; i < nprobe; i++) {
+ let circles = nonTopKCircles.filter((circle) => circle[3] == i);
+ drawCircle({
+ ctx,
+ circles,
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[i], nonTopKNodeOpacity),
+ });
+ }
+ const opacity = t * 0.5 + (1 - t) * topKNodeOpacity;
+ for (let i = 0; i < nprobe; i++) {
+ let circles = topKCircles.filter((circle) => circle[3] == i);
+ drawCircle({
+ ctx,
+ circles,
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[i], opacity),
+ hasStroke: true,
+ strokeStyle: hexWithOpacity(whiteColor, opacity),
+ lineWidth: topKNodeStrokeWidth * canvasScale,
+ });
+ }
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateNodesTrans.js b/federjs_old/FederView/IvfflatView/render/animateNodesTrans.js
new file mode 100644
index 0000000..a5e98b0
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateNodesTrans.js
@@ -0,0 +1,61 @@
+import { SEARCH_VIEW_TYPE } from 'Types';
+import { hexWithOpacity, drawCircle, whiteColor } from 'Utils/renderUtils';
+
+export default function animateNodesTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ duration,
+ delay,
+ newSearchViewType,
+}) {
+ const { colorScheme, nonTopKNodes, topKNodes, nprobe } = searchViewLayoutData;
+ const {
+ ease,
+ nonTopKNodeR,
+ canvasScale,
+ topKNodeR,
+ topKNodeStrokeWidth,
+ nonTopKNodeOpacity,
+ topKNodeOpacity,
+ } = federView;
+ let t = ease((elapsed - delay) / duration);
+ if (t > 1 || t < 0) return;
+
+ t = newSearchViewType === SEARCH_VIEW_TYPE.polar ? 1 - t : t;
+
+ const nonTopKCircles = nonTopKNodes.map((node) => [
+ t * node.projectPos[0] + (1 - t) * node.polarPos[0],
+ t * node.projectPos[1] + (1 - t) * node.polarPos[1],
+ nonTopKNodeR * canvasScale,
+ node.polarOrder,
+ ]);
+ const topKCircles = topKNodes.map((node) => [
+ t * node.projectPos[0] + (1 - t) * node.polarPos[0],
+ t * node.projectPos[1] + (1 - t) * node.polarPos[1],
+ topKNodeR * canvasScale,
+ node.polarOrder,
+ ]);
+ for (let i = 0; i < nprobe; i++) {
+ let circles = nonTopKCircles.filter((circle) => circle[3] == i);
+ drawCircle({
+ ctx,
+ circles,
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[i], nonTopKNodeOpacity),
+ });
+ }
+ for (let i = 0; i < nprobe; i++) {
+ let circles = topKCircles.filter((circle) => circle[3] == i);
+ drawCircle({
+ ctx,
+ circles,
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[i], topKNodeOpacity),
+ hasStroke: true,
+ strokeStyle: hexWithOpacity(whiteColor, topKNodeOpacity),
+ lineWidth: topKNodeStrokeWidth * canvasScale,
+ });
+ }
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateNonNprobeClusters.js b/federjs_old/FederView/IvfflatView/render/animateNonNprobeClusters.js
new file mode 100644
index 0000000..7a56657
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateNonNprobeClusters.js
@@ -0,0 +1,33 @@
+import {
+ drawVoronoi,
+ hexWithOpacity,
+ blackColor,
+ ZBlue,
+} from 'Utils/renderUtils';
+import { ANIMATION_TYPE } from 'Types';
+
+export default function animateNonNprobeClusters({
+ ctx,
+ elapsed,
+ duration,
+ delay,
+ animationType,
+ searchViewLayoutData,
+ federView,
+}) {
+ const { ease, voronoiStrokeWidth, canvasScale } = federView;
+ const { nonNprobeClusters } = searchViewLayoutData;
+ let t = ease((elapsed - delay) / duration);
+ if (t > 1 || t < 0) return;
+ const opacity = animationType === ANIMATION_TYPE.enter ? t : 1 - t;
+ const pointsList = nonNprobeClusters.map((cluster) => cluster.SVPolyPoints);
+ drawVoronoi({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ strokeStyle: blackColor,
+ lineWidth: voronoiStrokeWidth * canvasScale,
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZBlue, opacity),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateNprobeClustersOpacity.js b/federjs_old/FederView/IvfflatView/render/animateNprobeClustersOpacity.js
new file mode 100644
index 0000000..ab4ded6
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateNprobeClustersOpacity.js
@@ -0,0 +1,39 @@
+import {
+ drawVoronoi,
+ hexWithOpacity,
+ blackColor,
+ ZLightBlue,
+} from 'Utils/renderUtils';
+import { ANIMATION_TYPE } from 'Types';
+
+export default function animateNonNprobeClusters({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ duration,
+ delay,
+ animationType,
+}) {
+ const { nprobeClusters } = searchViewLayoutData;
+ const { ease, voronoiStrokeWidth, canvasScale } = federView;
+ let t = ease((elapsed - delay) / duration);
+ if (t > 1 || t < 0) return;
+ t = animationType === ANIMATION_TYPE.enter ? t : 1 - t;
+ const opacity = t;
+ const pointsList = nprobeClusters.map((cluster) =>
+ cluster.SVPolyPoints.map((point) => [
+ point[0] + cluster.SVNextLevelTran[0],
+ point[1] + cluster.SVNextLevelTran[1],
+ ])
+ );
+ drawVoronoi({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ strokeStyle: blackColor,
+ lineWidth: voronoiStrokeWidth * canvasScale,
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZLightBlue, opacity),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateNprobeClustersTrans.js b/federjs_old/FederView/IvfflatView/render/animateNprobeClustersTrans.js
new file mode 100644
index 0000000..c0adcb6
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateNprobeClustersTrans.js
@@ -0,0 +1,40 @@
+import {
+ drawVoronoi,
+ hexWithOpacity,
+ whiteColor,
+ blackColor,
+ ZLightBlue,
+} from 'Utils/renderUtils';
+import { ANIMATION_TYPE } from 'Types';
+
+export default function animateNprobeClustersTrans({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ duration,
+ delay,
+ animationType,
+}) {
+ const { nprobeClusters } = searchViewLayoutData;
+ const { ease, voronoiStrokeWidth, canvasScale } = federView;
+ let t = ease((elapsed - delay) / duration);
+ if (t > 1 || t < 0) return;
+ t = animationType === ANIMATION_TYPE.enter ? 1 - t : t;
+
+ const pointsList = nprobeClusters.map((cluster) =>
+ cluster.SVPolyPoints.map((point) => [
+ point[0] + t * cluster.SVNextLevelTran[0],
+ point[1] + t * cluster.SVNextLevelTran[1],
+ ])
+ );
+ drawVoronoi({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ strokeStyle: blackColor,
+ lineWidth: voronoiStrokeWidth * canvasScale,
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZLightBlue, 1),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/animateTargetNode.js b/federjs_old/FederView/IvfflatView/render/animateTargetNode.js
new file mode 100644
index 0000000..3ba0e20
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/animateTargetNode.js
@@ -0,0 +1,33 @@
+import { whiteColor, drawCircle } from 'Utils/renderUtils';
+import { ANIMATION_TYPE, SEARCH_VIEW_TYPE } from 'Types';
+
+export default function animateTargetNode({
+ ctx,
+ searchViewLayoutData,
+ federView,
+ elapsed,
+ duration,
+ delay,
+ animationType,
+ newSearchViewType,
+}) {
+ const { targetNode } = searchViewLayoutData;
+ const { ease, targetNodeR, canvasScale, targetNodeStrokeWidth } = federView;
+ let t = ease((elapsed - delay) / duration);
+ if (newSearchViewType === SEARCH_VIEW_TYPE.project) {
+ if (t < 0 || t > 1) return;
+ }
+ if (t > 1) t = 1;
+ if (t < 0) t = 0;
+ t = animationType === ANIMATION_TYPE.enter ? t : 1 - t;
+ const x = targetNode.SVPos[0] * t + targetNode.polarPos[0] * (1 - t);
+ const y = targetNode.SVPos[1] * t + targetNode.polarPos[1] * (1 - t);
+
+ drawCircle({
+ ctx,
+ circles: [[x, y, targetNodeR * canvasScale]],
+ hasStroke: true,
+ strokeStyle: whiteColor,
+ lineWidth: targetNodeStrokeWidth * canvasScale,
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderBackground.js b/federjs_old/FederView/IvfflatView/render/renderBackground.js
new file mode 100644
index 0000000..e1cd225
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderBackground.js
@@ -0,0 +1,14 @@
+import { drawRect, blackColor } from 'Utils/renderUtils';
+
+export default function renderBackground(ctx, {
+ width,
+ height,
+}) {
+ drawRect({
+ ctx,
+ width,
+ height,
+ hasFill: true,
+ fillStyle: blackColor,
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderHighLightNodes.js b/federjs_old/FederView/IvfflatView/render/renderHighLightNodes.js
new file mode 100644
index 0000000..216e270
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderHighLightNodes.js
@@ -0,0 +1,34 @@
+import { SEARCH_VIEW_TYPE } from 'Types';
+import { hexWithOpacity, drawCircle, whiteColor } from 'Utils/renderUtils';
+
+export default function renderHighLightNodes(
+ ctx,
+ { colorScheme, topKNodes, nprobe },
+ { topKNodeR, canvasScale, topKNodeOpacity, topKNodeStrokeWidth },
+ searchViewType
+) {
+ const allCircles =
+ searchViewType === SEARCH_VIEW_TYPE.polar
+ ? topKNodes.map((node) => [
+ ...node.polarPos,
+ topKNodeR * canvasScale,
+ node.polarOrder,
+ ])
+ : topKNodes.map((node) => [
+ ...node.projectPos,
+ topKNodeR * canvasScale,
+ node.polarOrder,
+ ]);
+ for (let i = 0; i < nprobe; i++) {
+ let circles = allCircles.filter((circle) => circle[3] == i);
+ drawCircle({
+ ctx,
+ circles,
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[i], topKNodeOpacity),
+ hasStroke: true,
+ strokeStyle: hexWithOpacity(whiteColor, topKNodeOpacity),
+ lineWidth: topKNodeStrokeWidth * canvasScale,
+ });
+ }
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderHighlightVoronoi.js b/federjs_old/FederView/IvfflatView/render/renderHighlightVoronoi.js
new file mode 100644
index 0000000..0d9330c
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderHighlightVoronoi.js
@@ -0,0 +1,25 @@
+import { VIEW_TYPE } from 'Types';
+import {
+ drawVoronoi,
+ ZYellow,
+ ZLightBlue,
+ hexWithOpacity,
+ blackColor,
+} from 'Utils/renderUtils';
+
+export default function renderHighlightVoronoi(
+ ctx,
+ { nprobeClusters },
+ { voronoiStrokeWidth, canvasScale }
+) {
+ const pointsList = nprobeClusters.map((cluster) => cluster.SVPolyPoints);
+ drawVoronoi({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ strokeStyle: blackColor,
+ lineWidth: voronoiStrokeWidth * canvasScale,
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZLightBlue, 1),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderNodeView.js b/federjs_old/FederView/IvfflatView/render/renderNodeView.js
new file mode 100644
index 0000000..89123e9
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderNodeView.js
@@ -0,0 +1,41 @@
+import renderBackground from './renderBackground';
+import renderPolarAxis from './renderPolarAxis';
+import renderNormalNodes from './renderNormalNodes';
+import renderHighLightNodes from './renderHighLightNodes';
+import renderSelectedNode from './renderSelectedNode';
+import renderTarget from './renderTarget';
+import { SEARCH_VIEW_TYPE } from 'Types';
+
+export default function renderNodeView(
+ ctx,
+ searchViewLayoutData,
+ federView,
+ searchViewType = SEARCH_VIEW_TYPE.polar,
+ hoveredNode = null
+) {
+ // background
+ renderBackground(ctx, federView);
+
+ // axis polar
+ searchViewType === SEARCH_VIEW_TYPE.polar &&
+ renderPolarAxis(ctx, searchViewLayoutData, federView);
+
+ // normal-nodes
+ renderNormalNodes(ctx, searchViewLayoutData, federView, searchViewType);
+
+ // topk-nodes
+ renderHighLightNodes(ctx, searchViewLayoutData, federView, searchViewType);
+
+ // hovered node
+ !!hoveredNode &&
+ renderSelectedNode(
+ ctx,
+ searchViewLayoutData,
+ federView,
+ searchViewType,
+ hoveredNode
+ );
+
+ // target
+ renderTarget(ctx, searchViewType, searchViewLayoutData, federView);
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderNormalNodes.js b/federjs_old/FederView/IvfflatView/render/renderNormalNodes.js
new file mode 100644
index 0000000..d136c49
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderNormalNodes.js
@@ -0,0 +1,31 @@
+import { SEARCH_VIEW_TYPE } from 'Types';
+import { hexWithOpacity, drawCircle } from 'Utils/renderUtils';
+
+export default function renderNormalNodes(
+ ctx,
+ { colorScheme, nonTopKNodes, nprobe },
+ { nonTopKNodeR, canvasScale, nonTopKNodeOpacity },
+ searchViewType
+) {
+ const allCircles =
+ searchViewType === SEARCH_VIEW_TYPE.polar
+ ? nonTopKNodes.map((node) => [
+ ...node.polarPos,
+ nonTopKNodeR * canvasScale,
+ node.polarOrder,
+ ])
+ : nonTopKNodes.map((node) => [
+ ...node.projectPos,
+ nonTopKNodeR * canvasScale,
+ node.polarOrder,
+ ]);
+ for (let i = 0; i < nprobe; i++) {
+ let circles = allCircles.filter((circle) => circle[3] == i);
+ drawCircle({
+ ctx,
+ circles,
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[i], nonTopKNodeOpacity),
+ });
+ }
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderNormalVoronoi.js b/federjs_old/FederView/IvfflatView/render/renderNormalVoronoi.js
new file mode 100644
index 0000000..296d88c
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderNormalVoronoi.js
@@ -0,0 +1,28 @@
+import { VIEW_TYPE } from 'Types';
+import {
+ drawVoronoi,
+ ZBlue,
+ hexWithOpacity,
+ blackColor,
+} from 'Utils/renderUtils';
+
+export default function renderNormalVoronoi(
+ ctx,
+ viewType,
+ { clusters, nonNprobeClusters },
+ { voronoiStrokeWidth, canvasScale }
+) {
+ const pointsList =
+ viewType === VIEW_TYPE.overview
+ ? clusters.map((cluster) => cluster.OVPolyPoints)
+ : nonNprobeClusters.map((cluster) => cluster.SVPolyPoints);
+ drawVoronoi({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ strokeStyle: blackColor,
+ lineWidth: voronoiStrokeWidth * canvasScale,
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZBlue, 1),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderPolarAxis.js b/federjs_old/FederView/IvfflatView/render/renderPolarAxis.js
new file mode 100644
index 0000000..bcc4135
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderPolarAxis.js
@@ -0,0 +1,19 @@
+import { hexWithOpacity, drawCircle, ZBlue } from 'Utils/renderUtils';
+import * as d3 from 'd3';
+
+export default function renderPolarAxis(
+ ctx,
+ { polarOrigin, polarMaxR },
+ { axisTickCount, polarAxisStrokeWidth, canvasScale, polarAxisOpacity }
+) {
+ const circles = d3
+ .range(axisTickCount)
+ .map((i) => [...polarOrigin, ((i + 0.7) / axisTickCount) * polarMaxR]);
+ drawCircle({
+ ctx,
+ circles,
+ hasStroke: true,
+ lineWidth: polarAxisStrokeWidth * canvasScale,
+ strokeStyle: hexWithOpacity(ZBlue, polarAxisOpacity),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderSelectedNode.js b/federjs_old/FederView/IvfflatView/render/renderSelectedNode.js
new file mode 100644
index 0000000..f774f0f
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderSelectedNode.js
@@ -0,0 +1,32 @@
+import { SEARCH_VIEW_TYPE } from 'Types';
+import { hexWithOpacity, drawCircle, whiteColor } from 'Utils/renderUtils';
+
+export default function renderSelectedNode(
+ ctx,
+ { colorScheme },
+ { hoveredNodeR, canvasScale, hoveredNodeOpacity, hoveredNodeStrokeWidth },
+ searchViewType,
+ hoveredNode
+) {
+ const circle =
+ searchViewType === SEARCH_VIEW_TYPE.polar
+ ? [
+ ...hoveredNode.polarPos,
+ hoveredNodeR * canvasScale,
+ hoveredNode.polarOrder,
+ ]
+ : [
+ ...hoveredNode.projectPos,
+ hoveredNodeR * canvasScale,
+ hoveredNode.polarOrder,
+ ];
+ drawCircle({
+ ctx,
+ circles: [circle],
+ hasFill: true,
+ fillStyle: hexWithOpacity(colorScheme[circle[3]], hoveredNodeOpacity),
+ hasStroke: true,
+ lineWidth: hoveredNodeStrokeWidth * canvasScale,
+ strokeStyle: hexWithOpacity(whiteColor, 1),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderSelectedVoronoi.js b/federjs_old/FederView/IvfflatView/render/renderSelectedVoronoi.js
new file mode 100644
index 0000000..71165e6
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderSelectedVoronoi.js
@@ -0,0 +1,27 @@
+import { VIEW_TYPE } from 'Types';
+import {
+ drawVoronoi,
+ ZLightBlue,
+ ZYellow,
+ hexWithOpacity,
+ blackColor,
+} from 'Utils/renderUtils';
+
+export default function renderNormalVoronoi(ctx, viewType, hoveredCluster, {
+ voronoiStrokeWidth,
+ canvasScale,
+}) {
+ const pointsList =
+ viewType === VIEW_TYPE.overview
+ ? [hoveredCluster.OVPolyPoints]
+ : [hoveredCluster.SVPolyPoints];
+ drawVoronoi({
+ ctx,
+ pointsList,
+ hasStroke: true,
+ strokeStyle: blackColor,
+ lineWidth: voronoiStrokeWidth * canvasScale,
+ hasFill: true,
+ fillStyle: hexWithOpacity(ZYellow, 0.8),
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderTarget.js b/federjs_old/FederView/IvfflatView/render/renderTarget.js
new file mode 100644
index 0000000..6309889
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderTarget.js
@@ -0,0 +1,22 @@
+import { SEARCH_VIEW_TYPE } from 'Types';
+import { whiteColor, drawCircle } from 'Utils/renderUtils';
+
+export default function renderTarget(
+ ctx,
+ searchViewType,
+ { targetNode },
+ { targetNodeR, canvasScale, targetNodeStrokeWidth }
+) {
+ if (searchViewType === SEARCH_VIEW_TYPE.project) return;
+ const circle =
+ searchViewType === SEARCH_VIEW_TYPE.voronoi
+ ? [...targetNode.SVPos, targetNodeR * canvasScale]
+ : [...targetNode.polarPos, targetNodeR * canvasScale];
+ drawCircle({
+ ctx,
+ circles: [circle],
+ hasStroke: true,
+ strokeStyle: whiteColor,
+ lineWidth: targetNodeStrokeWidth * canvasScale,
+ });
+}
diff --git a/federjs_old/FederView/IvfflatView/render/renderVoronoiView.js b/federjs_old/FederView/IvfflatView/render/renderVoronoiView.js
new file mode 100644
index 0000000..d62ee2f
--- /dev/null
+++ b/federjs_old/FederView/IvfflatView/render/renderVoronoiView.js
@@ -0,0 +1,31 @@
+import renderBackground from './renderBackground';
+import renderNormalVoronoi from './renderNormalVoronoi';
+import renderHighlightVoronoi from './renderHighlightVoronoi';
+import renderSelectedVoronoi from './renderSelectedVoronoi';
+import renderTarget from './renderTarget';
+import { VIEW_TYPE, SEARCH_VIEW_TYPE } from 'Types';
+
+export default function renderVoronoiView(
+ ctx,
+ viewType,
+ layoutData,
+ federView,
+ hoveredCluster = null,
+) {
+ // background
+ renderBackground(ctx, federView);
+
+ // normal-cluster
+ renderNormalVoronoi(ctx, viewType, layoutData, federView);
+
+ // nprobe-cluster search
+ viewType === VIEW_TYPE.search &&
+ renderHighlightVoronoi(ctx, layoutData, federView);
+
+ // hoverCluster
+ !!hoveredCluster &&
+ renderSelectedVoronoi(ctx, viewType, hoveredCluster, federView);
+
+ // target search
+ viewType === VIEW_TYPE.search && renderTarget(ctx, SEARCH_VIEW_TYPE.voronoi, layoutData, federView);
+}
diff --git a/federjs_old/FederView/index.js b/federjs_old/FederView/index.js
new file mode 100644
index 0000000..67941d7
--- /dev/null
+++ b/federjs_old/FederView/index.js
@@ -0,0 +1,97 @@
+import { INDEX_TYPE } from 'Types';
+import { initLoadingStyle, renderLoading } from './loading';
+import HnswView from './HnswView';
+import IvfflatView from './IvfflatView';
+
+const viewHandlerMap = {
+ [INDEX_TYPE.hnsw]: HnswView,
+ [INDEX_TYPE.ivf_flat]: IvfflatView,
+};
+
+const defaultViewParams = {
+ width: 1000,
+ height: 600,
+ canvasScale: 3,
+};
+
+export default class FederView {
+ constructor({ domSelector, viewParams }) {
+ this.domSelector = domSelector;
+ this.viewParams = Object.assign({}, defaultViewParams, viewParams);
+
+ this.viewHandler = null;
+
+ // this.initDom();
+ initLoadingStyle();
+ }
+ initDom() {
+ const dom = document.createElement('div');
+ dom.id = `feder-dom-${Math.floor(Math.random() * 100000)}`;
+ const { width, height } = this.viewParams;
+ const domStyle = {
+ position: 'relative',
+ width: `${width}px`,
+ height: `${height}px`,
+ // boxShadow: '0 0 5px #ccc',
+ // borderRadius: '10px',
+ };
+ Object.assign(dom.style, domStyle);
+ renderLoading(dom, width, height);
+
+ if (this.domSelector) {
+ const domContainer = document.querySelector(this.domSelector);
+ domContainer.innerHTML = '';
+ domContainer.appendChild(dom);
+ }
+
+ return dom;
+ }
+ initView({ indexType, indexMeta, getVectorById }) {
+ if (indexType in viewHandlerMap) {
+ this.view = new viewHandlerMap[indexType]({
+ indexMeta,
+ viewParams: this.viewParams,
+ getVectorById,
+ });
+ } else throw `No view handler for ${indexType}`;
+ }
+ overview(initCoreAndViewPromise) {
+ const dom = this.initDom();
+ initCoreAndViewPromise.then(() => {
+ this.view.overview(dom);
+ });
+
+ return dom;
+ }
+ search({
+ searchRes = null,
+ targetMediaUrl = null,
+ searchResPromise = null,
+ } = {}) {
+ const dom = this.initDom();
+
+ if (searchResPromise) {
+ searchResPromise.then(({ searchRes, targetMediaUrl }) => {
+ this.view.search(dom, {
+ searchRes,
+ targetMediaUrl,
+ });
+ });
+ } else {
+ this.view.search(dom, {
+ searchRes,
+ targetMediaUrl,
+ });
+ }
+
+ return dom;
+ }
+
+ switchSearchView(searchViewType) {
+ try {
+ this.view.switchSearchView(searchViewType);
+ } catch (e) {
+ console.log('Not Support', e);
+ }
+ }
+}
diff --git a/federjs_old/FederView/loading.js b/federjs_old/FederView/loading.js
new file mode 100644
index 0000000..3926aa7
--- /dev/null
+++ b/federjs_old/FederView/loading.js
@@ -0,0 +1,89 @@
+import * as d3 from 'd3';
+
+const loadingSvgId = 'feder-loading';
+const loadingWidth = 30;
+const loadingStrokeWidth = 6;
+
+export const initLoadingStyle = () => {
+ const style = document.createElement('style');
+ style.type = 'text/css';
+ style.innerHTML = `
+ @keyframes rotation {
+ from {
+ transform: translate(${loadingWidth / 2}px,${
+ loadingWidth / 2
+ }px) rotate(0deg);
+ }
+ to {
+ transform: translate(${loadingWidth / 2}px,${
+ loadingWidth / 2
+ }px) rotate(359deg);
+ }
+ }
+ .rotate {
+ animation: rotation 2s infinite linear;
+ }
+ `;
+ document.getElementsByTagName('head').item(0).appendChild(style);
+};
+
+export const renderLoading = (domNode, width, height) => {
+ const dom = d3.select(domNode);
+ // const { width, height } = dom.node().getBoundingClientRect();
+ if (!dom.select(`#${loadingSvgId}`).empty()) return;
+ const svg = dom
+ .append('svg')
+ .attr('id', loadingSvgId)
+ .attr('width', loadingWidth)
+ .attr('height', loadingWidth)
+ .style('position', 'absolute')
+ .style('left', width / 2 - loadingWidth / 2)
+ .style('bottom', height / 2 - loadingWidth / 2)
+ .style('overflow', 'visible');
+
+ const defsG = svg.append('defs');
+ const linearGradientId = `feder-loading-gradient`;
+ const linearGradient = defsG
+ .append('linearGradient')
+ .attr('id', linearGradientId)
+ .attr('x1', 0)
+ .attr('y1', 0)
+ .attr('x2', 0)
+ .attr('y2', 1);
+ linearGradient
+ .append('stop')
+ .attr('offset', '0%')
+ .style('stop-color', '#1E64FF');
+ linearGradient
+ .append('stop')
+ .attr('offset', '100%')
+ .style('stop-color', '#061982');
+
+ const loadingCircle = svg
+ .append('circle')
+ .attr('cx', loadingWidth / 2)
+ .attr('cy', loadingWidth / 2)
+ .attr('fill', 'none')
+ .attr('r', loadingWidth / 2)
+ .attr('stroke', '#1E64FF')
+ .attr('stroke-width', loadingStrokeWidth);
+
+ const semiCircle = svg
+ .append('path')
+ .attr(
+ 'd',
+ `M0,${-loadingWidth / 2} a ${loadingWidth / 2} ${
+ loadingWidth / 2
+ } 0 1 1 ${0} ${loadingWidth}`
+ )
+ .attr('fill', 'none')
+ // .style('transform', ``)
+ .attr('stroke', `url(#${linearGradientId})`)
+ .attr('stroke-width', loadingStrokeWidth)
+ .classed('rotate', true);
+};
+
+export const finishLoading = (domNode) => {
+ const dom = d3.select(domNode);
+ dom.selectAll(`#${loadingSvgId}`).remove();
+};
diff --git a/federjs_old/Types.js b/federjs_old/Types.js
new file mode 100644
index 0000000..810251a
--- /dev/null
+++ b/federjs_old/Types.js
@@ -0,0 +1,82 @@
+export const SOURCE_TYPE = {
+ hnswlib: 'hnswlib',
+ faiss: 'faiss',
+};
+
+export const INDEX_TYPE = {
+ hnsw: 'hnsw',
+ ivf_flat: 'ivf_flat',
+ flat: 'flat',
+};
+
+export const PROJECT_METHOD = {
+ umap: 'umap',
+ tsne: 'tsne',
+}
+
+// faiss config
+export const MetricType = {
+ METRIC_INNER_PRODUCT: 0, ///< maximum inner product search
+ METRIC_L2: 1, ///< squared L2 search
+ METRIC_L1: 2, ///< L1 (aka cityblock)
+ METRIC_Linf: 3, ///< infinity distance
+ METRIC_Lp: 4, ///< L_p distance, p is given by a faiss::Index
+ /// metric_arg
+
+ /// some additional metrics defined in scipy.spatial.distance
+ METRIC_Canberra: 20,
+ METRIC_BrayCurtis: 21,
+ METRIC_JensenShannon: 22,
+};
+
+export const DirectMapType = {
+ NoMap: 0, // default
+ Array: 1, // sequential ids (only for add, no add_with_ids)
+ Hashtable: 2, // arbitrary ids
+};
+
+export const IndexHeader = {
+ IVFFlat: 'IwFl',
+ FlatL2: 'IxF2',
+ FlatIR: 'IxFI',
+};
+
+export const HNSW_NODE_TYPE = {
+ Coarse: 1,
+ Candidate: 2,
+ Fine: 3,
+ Target: 4,
+};
+
+export const HNSW_LINK_TYPE = {
+ None: 0,
+ Visited: 1,
+ Extended: 2,
+ Searched: 3,
+ Fine: 4,
+};
+
+export const VIEW_TYPE = {
+ overview: 'overview',
+ search: 'search',
+}
+
+export const SEARCH_VIEW_TYPE = {
+ voronoi: 'voronoi',
+ polar: 'polar',
+ project: 'project',
+}
+
+export const ANIMATION_TYPE = {
+ exit: 'exit',
+ enter: 'enter',
+}
+
+export const FEDER_CORE_REQUEST = {
+ get_index_type: "get_index_type",
+ get_index_meta: "get_index_meta",
+ get_test_id_and_vector: "get_test_id_and_vector",
+ get_vector_by_id: "get_vector_by_id",
+ search: "search",
+ set_search_params: "set_search_params",
+};
diff --git a/federjs_old/Utils/PriorityQueue.js b/federjs_old/Utils/PriorityQueue.js
new file mode 100644
index 0000000..b95c76a
--- /dev/null
+++ b/federjs_old/Utils/PriorityQueue.js
@@ -0,0 +1,78 @@
+//Minimum Heap
+class PriorityQueue {
+ constructor(arr = [], key = null) {
+ if (typeof key == 'string') {
+ this._key = (item) => item[key];
+ } else this._key = key;
+ this._tree = [];
+ arr.forEach((d) => this.add(d));
+ }
+ add(item) {
+ this._tree.push(item);
+ let id = this._tree.length - 1;
+ while (id) {
+ const fatherId = Math.floor((id - 1) / 2);
+ if (this._getValue(id) >= this._getValue(fatherId)) break;
+ else {
+ this._swap(fatherId, id);
+ id = fatherId;
+ }
+ }
+ }
+ get top() {
+ return this._tree[0];
+ }
+ pop() {
+ if (this.isEmpty) {
+ return 'empty';
+ }
+ const item = this.top;
+ if (this._tree.length > 1) {
+ const lastItem = this._tree.pop();
+ let id = 0;
+ this._tree[id] = lastItem;
+ while (!this._isLeaf(id)) {
+ const curValue = this._getValue(id);
+ const leftId = id * 2 + 1;
+ const leftValue = this._getValue(leftId);
+ const rightId = leftId >= this._tree.length - 1 ? leftId : id * 2 + 2;
+ const rightValue = this._getValue(rightId);
+ const minValue = Math.min(leftValue, rightValue);
+ if (curValue <= minValue) break;
+ else {
+ const minId = leftValue < rightValue ? leftId : rightId;
+ this._swap(minId, id);
+ id = minId;
+ }
+ }
+ } else {
+ this._tree = [];
+ }
+ return item;
+ }
+ get isEmpty() {
+ return this._tree.length === 0;
+ }
+ get size() {
+ return this._tree.length;
+ }
+ get _firstLeaf() {
+ return Math.floor(this._tree.length / 2);
+ }
+ _isLeaf(id) {
+ return id >= this._firstLeaf;
+ }
+ _getValue(id) {
+ if (this._key) {
+ return this._key(this._tree[id]);
+ } else {
+ return this._tree[id];
+ }
+ }
+ _swap(id0, id1) {
+ const tree = this._tree;
+ [tree[id0], tree[id1]] = [tree[id1], tree[id0]];
+ }
+}
+
+export default PriorityQueue;
diff --git a/federjs_old/Utils/index.js b/federjs_old/Utils/index.js
new file mode 100644
index 0000000..6b8718c
--- /dev/null
+++ b/federjs_old/Utils/index.js
@@ -0,0 +1,145 @@
+import * as d3 from 'd3';
+export const colorScheme = d3.schemeTableau10;
+
+import { MetricType } from 'Types';
+
+export const getDisL2 = (vec1, vec2) => {
+ return Math.sqrt(
+ vec1
+ .map((num, i) => num - vec2[i])
+ .map((num) => num * num)
+ .reduce((a, c) => a + c, 0)
+ );
+};
+
+export const getDisIR = (vec1, vec2) => {
+ return vec1.map((num, i) => num * vec2[i]).reduce((acc, cur) => acc + cur, 0);
+};
+
+export const getDisFunc = (metricType) => {
+ if (metricType === MetricType.METRIC_L2) {
+ return getDisL2;
+ } else if (metricType === MetricType.METRIC_INNER_PRODUCT) {
+ return getDisIR;
+ }
+ console.warn('[getDisFunc] wrong metric_type, use L2 (default).', metricType);
+ return getDisL2;
+};
+
+export default getDisFunc;
+
+export const getIvfListId = (listId) => `list-${listId}`;
+export const uint8toChars = (data) => {
+ return String.fromCharCode(...data);
+};
+
+export const generateArray = (num) => {
+ return Array.from(new Array(Math.floor(num)).keys());
+};
+
+export const polyPoints2path = (points, withZ = true) => {
+ return `M${points.join('L')}${withZ ? 'Z' : ''}`;
+};
+
+export const calAngle = (x, y) => {
+ let angle = (Math.atan(x / y) / Math.PI) * 180;
+ if (angle < 0) {
+ if (x < 0) {
+ angle += 360;
+ } else {
+ angle += 180;
+ }
+ } else {
+ if (x < 0) {
+ angle += 180;
+ }
+ }
+ return angle;
+};
+
+export const vecSort = (vecs, layoutKey, returnKey) => {
+ const center = {
+ x: vecs.reduce((acc, c) => acc + c[layoutKey][0], 0) / vecs.length,
+ y: vecs.reduce((acc, c) => acc + c[layoutKey][1], 0) / vecs.length,
+ };
+ const angles = vecs.map((vec) => ({
+ _vecSortAngle: calAngle(
+ vec[layoutKey][0] - center.x,
+ vec[layoutKey][1] - center.y
+ ),
+ _key: vec[returnKey],
+ }));
+ angles.sort((a, b) => a._vecSortAngle - b._vecSortAngle);
+ const res = angles.map((vec) => vec._key);
+ return res;
+};
+
+export const dist2 = (vec1, vec2) =>
+ vec1.map((num, i) => num - vec2[i]).reduce((acc, cur) => acc + cur * cur, 0);
+
+export const dist = (vec1, vec2) => Math.sqrt(dist2(vec1, vec2));
+
+export const inCircle = (x, y, x0, y0, r, bias = 0) =>
+ dist2([x, y], [x0, y0]) < Math.pow(r + bias, 2);
+
+export const deDupLink = (links, source = 'source', target = 'target') => {
+ const linkStringSet = new Set();
+ return links.filter((link) => {
+ const linkString = `${link[source]}---${link[target]}`;
+ const linkStringReverse = `${link[target]}---${link[source]}`;
+ if (linkStringSet.has(linkString) || linkStringSet.has(linkStringReverse)) {
+ return false;
+ } else {
+ linkStringSet.add(linkString);
+ return true;
+ }
+ });
+};
+
+const connection = '---';
+export const getLinkId = (sourceId, targetId) =>
+ `${sourceId}${connection}${targetId}`;
+export const parseLinkId = (linkId) => linkId.split(connection).map((d) => +d);
+export const getLinkIdWithLevel = (sourceId, targetId, level) =>
+ `link-${level}-${sourceId}-${targetId}`;
+
+export const getNodeIdWithLevel = (nodeId, level) => `node-${level}-${nodeId}`;
+export const getEntryLinkIdWithLevel = (nodeId, level) =>
+ `inter-level-${level}-${nodeId}`;
+
+export const shortenLine = (point_0, point_1, d = 20) => {
+ const length = dist(point_0, point_1);
+ const t = Math.min(d / length, 0.4);
+ return [
+ getInprocessPos(point_0, point_1, t),
+ getInprocessPos(point_0, point_1, 1 - t),
+ ];
+};
+
+export const getInprocessPos = (point_0, point_1, t) => {
+ const x = point_0[0] * (1 - t) + point_1[0] * t;
+ const y = point_0[1] * (1 - t) + point_1[1] * t;
+ return [x, y];
+};
+
+export const showVectors = (vec, precision = 6, maxLength = 20) => {
+ return (
+ vec
+ .slice(0, maxLength)
+ .map((num) => num.toFixed(precision))
+ .join(', ') + ', ...'
+ );
+};
+
+export const randomSelect = (arr, k) => {
+ const res = new Set();
+ k = Math.min(arr.length, k);
+ while (k > 0) {
+ const itemIndex = Math.floor(Math.random() * arr.length);
+ if (!res.has(itemIndex)) {
+ res.add(itemIndex);
+ k -= 1;
+ }
+ }
+ return Array.from(res).map((i) => arr[i]);
+};
diff --git a/federjs_old/Utils/renderUtils.js b/federjs_old/Utils/renderUtils.js
new file mode 100644
index 0000000..3e0ccdf
--- /dev/null
+++ b/federjs_old/Utils/renderUtils.js
@@ -0,0 +1,293 @@
+import { polyPoints2path } from 'Utils';
+import * as d3 from 'd3';
+
+export const colorScheme = d3.schemeTableau10;
+
+export const ZBlue = '#175FFF';
+export const ZLightBlue = '#91FDFF';
+export const ZYellow = '#FFFC85';
+export const ZOrange = '#F36E4B';
+export const ZLayerBorder = '#D9EAFF';
+
+export const whiteColor = '#ffffff';
+export const blackColor = '#000000';
+export const backgroundColor = blackColor;
+export const highLightColor = ZYellow;
+export const voronoiHighlightColor = ZYellow;
+export const selectedColor = '#FFC671';
+export const voronoiHoverColor = selectedColor;
+export const hexWithOpacity = (color, opacity) => {
+ let opacityString = Math.round(opacity * 255).toString(16);
+ if (opacityString.length < 2) {
+ opacityString = '0' + opacityString;
+ }
+ return color + opacityString;
+};
+export const voronoiStrokeWidth = 4;
+
+export const highLightGradientStopColors = [
+ [0, hexWithOpacity(whiteColor, 0.2)],
+ [1, hexWithOpacity(ZYellow, 1)],
+];
+
+export const neighbourGradientStopColors = [
+ [0, hexWithOpacity(whiteColor, 0)],
+ [1, hexWithOpacity(whiteColor, 0.8)],
+];
+
+export const targetLevelGradientStopColors = neighbourGradientStopColors;
+
+export const normalGradientStopColors = [
+ [0, hexWithOpacity('#061982', 0.3)],
+ [1, hexWithOpacity('#1E64FF', 0.4)],
+];
+
+export const layerGradientStopColors = [
+ [0.1, hexWithOpacity('#1E64FF', 0.4)],
+ [0.9, hexWithOpacity('#00234D', 0)],
+];
+
+const draw = ({
+ ctx,
+ drawFunc = () => {},
+ fillStyle = '',
+ strokeStyle = '',
+ lineWidth = 0,
+ lineCap = 'butt',
+ shadowColor = '',
+ shadowBlur = 0,
+ shadowOffsetX = 0,
+ shadowOffsetY = 0,
+ isFillLinearGradient = false,
+ isStrokeLinearGradient = false,
+ gradientPos = [0, 0, 100, 100],
+ gradientStopColors = [],
+}) => {
+ ctx.save();
+
+ let gradient = null;
+ if (isFillLinearGradient || isStrokeLinearGradient) {
+ gradient = ctx.createLinearGradient(...gradientPos);
+ gradientStopColors.forEach((stopColor) =>
+ gradient.addColorStop(...stopColor)
+ );
+ }
+ ctx.fillStyle = isFillLinearGradient ? gradient : fillStyle;
+ ctx.strokeStyle = isStrokeLinearGradient ? gradient : strokeStyle;
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = lineCap;
+
+ ctx.shadowColor = shadowColor;
+ ctx.shadowBlur = shadowBlur;
+ ctx.shadowOffsetX = shadowOffsetX;
+ ctx.shadowOffsetY = shadowOffsetY;
+
+ drawFunc();
+
+ ctx.restore();
+};
+
+export const drawVoronoi = ({
+ ctx,
+ pointsList,
+ hasFill = false,
+ hasStroke = false,
+ ...styles
+}) => {
+ const drawFunc = () => {
+ pointsList.forEach((points) => {
+ const path = new Path2D(polyPoints2path(points));
+ hasFill && ctx.fill(path);
+ hasStroke && ctx.stroke(path);
+ });
+ };
+ draw({ ctx, drawFunc, ...styles });
+};
+
+export const extraExtent = ([x0, x1], p = 0.5) => {
+ const length = x1 - x0;
+ return [x0 - length * p, x1 + length * p];
+};
+
+export const drawVoronoiWithDots = ({
+ ctx,
+ points,
+ hasFill = false,
+ hasStroke = false,
+ dotColor = hexWithOpacity('red', 0.6),
+ dotR = 1.5,
+ dotAngle = Math.PI / 6,
+ dotGap = 4,
+ fillStyle = 'blue',
+ strokeStyle = 'green',
+ lineWidth = 2,
+}) => {
+ ctx.save();
+
+ ctx.fillStyle = fillStyle;
+ ctx.strokeStyle = strokeStyle;
+ ctx.lineWidth = lineWidth;
+
+ path = new Path2D(polyPoints2path(points));
+ hasFill && ctx.fill(path);
+ hasStroke && ctx.stroke(path);
+
+ ctx.clip(path);
+
+ const extentX = extraExtent(d3.extent(points, (point) => point[0]));
+ const extentY = extraExtent(d3.extent(points, (point) => point[1]));
+
+ ctx.fillStyle = dotColor;
+ const step = (dotR + dotGap) * 2;
+ d3.range(extentX[0], extentX[1], step).forEach((x) =>
+ d3.range(extentY[0], extentY[1], step).forEach((_y) => {
+ y = _y + (x - extentX[0]) * Math.tan(dotAngle);
+ ctx.beginPath();
+ ctx.arc(x, y, dotR, 0, 2 * Math.PI);
+ ctx.fill();
+ })
+ );
+
+ ctx.strokeStyle = strokeStyle;
+ hasStroke && ctx.stroke(path);
+
+ ctx.restore();
+};
+
+export const drawCircle = ({
+ ctx,
+ circles,
+ hasFill = false,
+ hasStroke = false,
+ ...styles
+}) => {
+ const drawFunc = () => {
+ circles.forEach(([x, y, r]) => {
+ ctx.beginPath();
+ ctx.arc(x, y, r, 0, 2 * Math.PI);
+ hasFill && ctx.fill();
+ hasStroke && ctx.stroke();
+ });
+ };
+ draw({ ctx, drawFunc, ...styles });
+};
+
+export const drawEllipse = ({
+ ctx,
+ circles,
+ hasFill = false,
+ hasStroke = false,
+ ...styles
+}) => {
+ const drawFunc = () => {
+ circles.forEach(([x, y, rx, ry]) => {
+ ctx.beginPath();
+ ctx.ellipse(x, y, rx, ry, 0, 0, 2 * Math.PI);
+ hasFill && ctx.fill();
+ hasStroke && ctx.stroke();
+ });
+ };
+ draw({ ctx, drawFunc, ...styles });
+};
+
+export const drawRect = ({
+ ctx,
+ x = 0,
+ y = 0,
+ width,
+ height,
+ hasFill = false,
+ hasStroke = false,
+ ...styles
+}) => {
+ const drawFunc = () => {
+ hasFill && ctx.fillRect(0, 0, width, height);
+ hasStroke && ctx.strokeRect(0, 0, width, height);
+ };
+ draw({ ctx, drawFunc, ...styles });
+};
+
+export const drawPath = ({
+ ctx,
+ points,
+ hasFill = false,
+ hasStroke = false,
+ withZ = true,
+ ...styles
+}) => {
+ const drawFunc = () => {
+ const path = new Path2D(polyPoints2path(points, withZ));
+ hasFill && ctx.fill(path);
+ hasStroke && ctx.stroke(path);
+ };
+ draw({ ctx, drawFunc, ...styles });
+};
+
+export const drawLine = ({
+ ctx,
+ points,
+ hasFill = false,
+ hasStroke = false,
+ ...styles
+}) => {
+ const drawFunc = () => {
+ const path = new Path2D(`M${points[0]}L${points[1]}`);
+ hasFill && ctx.fill(path);
+ hasStroke && ctx.stroke(path);
+ };
+ draw({ ctx, drawFunc, ...styles });
+};
+
+export const drawLines = ({
+ ctx,
+ pointsList,
+ hasFill = false,
+ hasStroke = false,
+ ...styles
+}) => {
+ const drawFunc = () => {
+ pointsList.forEach((points) => {
+ const path = new Path2D(`M${points[0]}L${points[1]}`);
+ hasFill && ctx.fill(path);
+ hasStroke && ctx.stroke(path);
+ });
+ };
+ draw({ ctx, drawFunc, ...styles });
+};
+
+export const drawLinesWithLinearGradient = ({
+ ctx,
+ pointsList,
+ hasFill = false,
+ hasStroke = false,
+ isStrokeLinearGradient = true,
+ ...styles
+}) => {
+ pointsList.forEach((points) => {
+ const path = new Path2D(`M${points[0]}L${points[1]}`);
+ const gradientPos = [...points[0], ...points[1]];
+ const drawFunc = () => {
+ hasFill && ctx.fill(path);
+ hasStroke && ctx.stroke(path);
+ };
+ draw({ ctx, drawFunc, isStrokeLinearGradient, gradientPos, ...styles });
+ });
+};
+
+export const renderLoading = ({ dom, width, height }) => {
+ const _dom = d3.select(`#${dom.id}`);
+ const svg = _dom
+ .append('svg')
+ .attr('id', loadingSvgId)
+ .attr('width', loadingWidth)
+ .attr('height', loadingWidth)
+ .style('position', 'absolute')
+ .style('left', width / 2 - loadingWidth / 2)
+ .style('bottom', height / 2 - loadingWidth / 2)
+ .style('border', '1px solid red');
+};
+
+export const finishLoading = ({ dom }) => {
+ const _dom = d3.select(`#${dom.id}`);
+ _dom.select(`#${loadingSvgId}`).remove();
+};
diff --git a/federjs_old/index.js b/federjs_old/index.js
new file mode 100644
index 0000000..2914b6a
--- /dev/null
+++ b/federjs_old/index.js
@@ -0,0 +1,6 @@
+import FederCore from './FederCore';
+import FederView from './FederView';
+import Feder from './Feder';
+import { FEDER_CORE_REQUEST } from 'Types';
+
+export { Feder, FederCore, FederView, FEDER_CORE_REQUEST };
diff --git a/federpy/LICENSE b/federpy/LICENSE
new file mode 100644
index 0000000..335ea9d
--- /dev/null
+++ b/federpy/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018 The Python Packaging Authority
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/federpy/README.md b/federpy/README.md
new file mode 100644
index 0000000..65bb62b
--- /dev/null
+++ b/federpy/README.md
@@ -0,0 +1,23 @@
+# Feder for Python
+
+## 0.6.0
+
+Support search-by-vector
+
+```
+searchByVec(vector [, imgUrl=None, isDisplayed=True])
+```
+
+## 0.7.0
+
+Each action (search or overview) command generates a new div with a random id.
+
+Different actions will no longer share an output cell.
+
+## 0.8.0
+
+Support chain functions.
+
+```
+federPy.setSearchParams(params).search()
+```
\ No newline at end of file
diff --git a/federpy/nodeServer/index.js b/federpy/nodeServer/index.js
new file mode 100644
index 0000000..4eaa0b8
--- /dev/null
+++ b/federpy/nodeServer/index.js
@@ -0,0 +1,52 @@
+import express from 'express';
+import cors from 'cors';
+import getFederCore from './initFederCore.js';
+
+import { coreNodePort } from '../test/config.js';
+
+const app = express();
+app.use(cors());
+
+const core = await getFederCore();
+
+const successData = (data) => ({ code: 200, data });
+
+app.get('/', (req, res) => {
+ console.log(req.query);
+ res.send(successData('get it~'));
+});
+
+app.get('/get_index_type', (_, res) => {
+ res.json(successData(core.indexType));
+});
+
+app.get('/get_index_meta', (_, res) => {
+ res.json(successData(core.indexMeta));
+});
+
+app.get('/get_test_id_and_vector', (_, res) => {
+ let [testId, testVec] = core.getTestIdAndVec();
+ while (isNaN(testId)) {
+ [testId, testVec] = core.getTestIdAndVec();
+ }
+ res.json(successData({ testId, testVec: Array.from(testVec) }));
+});
+
+app.get('/get_vector_by_id', (req, res) => {
+ const { id } = req.query;
+ res.json(successData(Array.from(core.id2vector[id] || [])));
+});
+
+app.get('/search', (req, res) => {
+ const { target } = req.query;
+ res.json(successData(core.search(target)));
+});
+
+app.get('/set_search_params', (req, res) => {
+ core.setSearchParams(req.query);
+ res.json(successData('ok'));
+});
+
+app.listen(coreNodePort, () => {
+ console.log('listening on port', coreNodePort);
+});
diff --git a/federpy/pyproject.toml b/federpy/pyproject.toml
new file mode 100644
index 0000000..a673a1f
--- /dev/null
+++ b/federpy/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools>=42", "ipython>=7"]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/federpy/setup.py b/federpy/setup.py
new file mode 100644
index 0000000..f6d133e
--- /dev/null
+++ b/federpy/setup.py
@@ -0,0 +1,23 @@
+import setuptools
+
+with open("README.md", "r", encoding="utf-8") as fh:
+ long_description = fh.read()
+
+setuptools.setup(
+ name="federpy",
+ version="0.8.0",
+ author="min.tian@zilliz",
+ author_email="min.tian@zilliz.com",
+ description="Feder for python",
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ url="https://github.com/zilliztech/feder",
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ ],
+ package_dir={"": "src"},
+ packages=setuptools.find_packages(where="src"),
+ python_requires=">=3.6",
+)
diff --git a/federpy/src/federpy/__init__.py b/federpy/src/federpy/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/federpy/src/federpy/federpy.py b/federpy/src/federpy/federpy.py
new file mode 100644
index 0000000..29ccdf7
--- /dev/null
+++ b/federpy/src/federpy/federpy.py
@@ -0,0 +1,106 @@
+from IPython.display import display, HTML
+import random
+
+
+class FederPy:
+ def __init__(self, indexFile, indexSource, mediaUrls=[], **viewParams):
+ self.indexFile = indexFile
+ self.indexSource = indexSource
+
+ self.federjs = "https://unpkg.com/@zilliz/feder"
+
+ self.actionJs = ""
+ self.searchParams = {}
+ self.mediaUrls = mediaUrls
+ self.viewParams = viewParams
+
+ def getDiv(self):
+ self.container = "feder-container-%s" % random.randint(0, 10000000)
+ return '' % self.container
+
+ def getInitJs(self):
+ return """
+import { Feder } from "%s"
+// console.log(Feder)
+
+const mediaUrls = [%s]
+const mediaCallback = (rowId) => rowId in mediaUrls ? mediaUrls[rowId] : null
+
+const feder = new Feder({
+ filePath: "%s",
+ source: "%s",
+ domSelector: "#%s",
+ viewParams: {
+ ...%s,
+ mediaCallback,
+ }
+})
+ """ % (self.federjs, ",".join(["'%s'" % url for url in self.mediaUrls]), self.indexFile, self.indexSource, self.container, self.viewParams)
+
+ def overview(self, isDisplay=True):
+ self.actionJs = "feder.overview()"
+ if isDisplay:
+ self.showHtml()
+ else:
+ return self.getHtml()
+
+ def searchById(self, targetId, isDisplay=True):
+ self.actionJs = "feder.setSearchParams(%s)\nfeder.searchById(%s)" % (
+ self.searchParams, targetId)
+ if isDisplay:
+ self.showHtml()
+ else:
+ return self.getHtml()
+
+ def searchByVec(self, targetVec, targetUrl=None, isDisplay=True):
+ targetVecString = "[" + ",".join([str(num) for num in targetVec]) + "]"
+ targetUrlString = "'%s'" % targetUrl if targetUrl else 'null'
+ self.actionJs = "feder.setSearchParams(%s)\nfeder.search(%s,%s)" % (
+ self.searchParams, targetVecString, targetUrlString)
+ if isDisplay:
+ self.showHtml()
+ else:
+ return self.getHtml()
+
+ def searchRandTestVec(self, isDisplay=True):
+ self.actionJs = "feder.setSearchParams(%s)\nfeder.searchRandTestVec()" % self.searchParams
+ if isDisplay:
+ self.showHtml()
+ else:
+ return self.getHtml()
+
+ def setSearchParams(self, searchParams):
+ self.searchParams = searchParams
+ return self
+
+ def getJs(self):
+ return """
+%s
+%s
+ """ % (self.getInitJs(), self.actionJs)
+
+ def getHtml(self):
+ return """
+
+
+
+
+
+
+
+ Feder
+
+
+
+ %s
+
+
+
+
+
+""" % (self.getDiv(), self.getJs())
+
+ def showHtml(self):
+ display(HTML(self.getHtml()))
diff --git a/federpy/test/test_searchById.ipynb b/federpy/test/test_searchById.ipynb
new file mode 100644
index 0000000..1ebca0e
--- /dev/null
+++ b/federpy/test/test_searchById.ipynb
@@ -0,0 +1,440 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "2e0a8dce",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from IPython.display import display, HTML\n",
+ "import random\n",
+ "\n",
+ "\n",
+ "class FederPy:\n",
+ " def __init__(self, indexFile, indexSource, mediaUrls=[], **viewParams):\n",
+ " self.indexFile = indexFile\n",
+ " self.indexSource = indexSource\n",
+ "\n",
+ " self.federjs = \"https://unpkg.com/@zilliz/feder\"\n",
+ "\n",
+ " self.actionJs = \"\"\n",
+ " self.searchParams = {}\n",
+ " self.mediaUrls = mediaUrls\n",
+ " self.viewParams = viewParams\n",
+ "\n",
+ " def getDiv(self):\n",
+ " self.container = \"feder-container-%s\" % random.randint(0, 10000000)\n",
+ " return '' % self.container\n",
+ "\n",
+ " def getInitJs(self):\n",
+ " return \"\"\"\n",
+ "import { Feder } from \"%s\"\n",
+ "// console.log(Feder)\n",
+ "\n",
+ "const mediaUrls = [%s]\n",
+ "const mediaCallback = (rowId) => rowId in mediaUrls ? mediaUrls[rowId] : null\n",
+ "\n",
+ "const feder = new Feder({\n",
+ " filePath: \"%s\",\n",
+ " source: \"%s\",\n",
+ " domSelector: \"#%s\",\n",
+ " viewParams: {\n",
+ " ...%s,\n",
+ " mediaCallback,\n",
+ " }\n",
+ "})\n",
+ " \"\"\" % (self.federjs, \",\".join([\"'%s'\" % url for url in self.mediaUrls]), self.indexFile, self.indexSource, self.container, self.viewParams)\n",
+ "\n",
+ " def overview(self, isDisplay=True):\n",
+ " self.actionJs = \"feder.overview()\"\n",
+ " if isDisplay:\n",
+ " self.showHtml()\n",
+ " else:\n",
+ " return self.getHtml()\n",
+ "\n",
+ " def searchById(self, targetId, isDisplay=True):\n",
+ " self.actionJs = \"feder.setSearchParams(%s)\\nfeder.searchById(%s)\" % (\n",
+ " self.searchParams, targetId)\n",
+ " if isDisplay:\n",
+ " self.showHtml()\n",
+ " else:\n",
+ " return self.getHtml()\n",
+ "\n",
+ " def searchByVec(self, targetVec, targetUrl=None, isDisplay=True):\n",
+ " targetVecString = \"[\" + \",\".join([str(num) for num in targetVec]) + \"]\"\n",
+ " targetUrlString = \"'%s'\" % targetUrl if targetUrl else 'null'\n",
+ " self.actionJs = \"feder.setSearchParams(%s)\\nfeder.search(%s,%s)\" % (\n",
+ " self.searchParams, targetVecString, targetUrlString)\n",
+ " if isDisplay:\n",
+ " self.showHtml()\n",
+ " else:\n",
+ " return self.getHtml()\n",
+ "\n",
+ " def searchRandTestVec(self, isDisplay=True):\n",
+ " self.actionJs = \"feder.setSearchParams(%s)\\nfeder.searchRandTestVec()\" % self.searchParams\n",
+ " if isDisplay:\n",
+ " self.showHtml()\n",
+ " else:\n",
+ " return self.getHtml()\n",
+ "\n",
+ " def setSearchParams(self, searchParams):\n",
+ " self.searchParams = searchParams\n",
+ "\n",
+ " def getJs(self):\n",
+ " return \"\"\"\n",
+ "%s\n",
+ "%s\n",
+ " \"\"\" % (self.getInitJs(), self.actionJs)\n",
+ "\n",
+ " def getHtml(self):\n",
+ " return \"\"\"\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " Feder\n",
+ "\n",
+ "\n",
+ "\n",
+ " %s\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "\"\"\" % (self.getDiv(), self.getJs())\n",
+ "\n",
+ " def showHtml(self):\n",
+ " display(HTML(self.getHtml()))\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "097125ac",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# IVFFlat - Search\n",
+ "import csv\n",
+ "import pandas as pd\n",
+ "# this csv includes 17,000+ items,each of which includes its filename.\n",
+ "namesFile = \"https://assets.zilliz.com/voc_names_4cee9440b1.csv\"\n",
+ "namesCsv = pd.read_csv(namesFile)\n",
+ "names = [row['name'] for index, row in namesCsv.iterrows()]\n",
+ "imageUrls = [\"https://assets.zilliz.com/voc2012/JPEGImages/%s\" % name for name in names]\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "1f95dcdf",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "indexFile = 'https://assets.zilliz.com/faiss_ivf_flat_voc_17k_ab112eec72.index'\n",
+ "indexSource = 'faiss'\n",
+ "\n",
+ "viewParams = {\n",
+ " \"width\": 800,\n",
+ " \"height\": 500,\n",
+ " \"mediaType\": \"img\",\n",
+ " \"mediaUrls\": imageUrls,\n",
+ " \"fineSearchWithProjection\": 1,\n",
+ " \"projectMethod\": \"umap\"\n",
+ "}\n",
+ "federPy = FederPy(indexFile, indexSource, **viewParams)\n",
+ "federPy.setSearchParams({\"k\": 15, \"nprobe\": 8})\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "88653ad0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " Feder\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "federPy.searchById(1156)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "a4232ea2",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " Feder\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "federPy.searchById(665)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "7aef9547",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " Feder\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "testId = 14486\n",
+ "testVec = [0.3180559277534485,1.8129287958145142,1.1755975484848022,0.7891319394111633,0.22350305318832397,2.516303300857544,1.1570743322372437,0.06357292830944061,0.943956196308136,0.6021895408630371,0.9676622748374939,0.6982965469360352,0.9805429577827454,0.5724471807479858,0.22905878722667694,0.09630092978477478,0.9583004713058472,1.0572623014450073,0.6430980563163757,1.3918004035949707,0.08578045666217804,1.5907948017120361,3.3321969509124756,1.3610285520553589,0.09104672074317932,0.10312048345804214,0.7651993632316589,1.3956501483917236,0.01969618909060955,0.6128015518188477,1.6002562046051025,0.43189698457717896,0.09782502800226212,0.44681864976882935,0.17383213341236115,1.540826439857483,1.3423514366149902,1.6673506498336792,0.5427879095077515,0.8117725849151611,1.1495232582092285,1.1069304943084717,0.1126856878399849,0.8899828791618347,2.41703724861145,0.8937768936157227,0.5728181004524231,0.5219349265098572,3.1016955375671387,1.0886644124984741,1.2785980701446533,1.5853283405303955,2.2631473541259766,0.9746670126914978,0.9295732378959656,0.2759018838405609,0.03575670346617699,1.1523011922836304,1.5381693840026855,2.675575017929077,0.12831033766269684,0.1481480449438095,0.971545934677124,0.5385479927062988,0.5174979567527771,0.2873912453651428,0.7357646226882935,0.4928179383277893,0.3909325897693634,1.1287131309509277,1.5709086656570435,1.692298412322998,0.9139777421951294,0,1.5500441789627075,0.7839444279670715,0.9178622364997864,0.2820394039154053,0.8132333755493164,3.5066189765930176,0.9958689212799072,1.1382688283920288,0.309495210647583,1.415186882019043,0.7401412725448608,0.8137246966362,1.7396281957626343,0.11588877439498901,1.7407218217849731,1.8080974817276,0.5819406509399414,1.4546641111373901,2.42268705368042,0.9622344970703125,1.2213270664215088,0.8828086256980896,0.6727229356765747,1.5722789764404297,1.4593628644943237,0.03921904042363167,1.090134859085083,2.0868043899536133,0.28828656673431396,0.13450616598129272,0.7206522226333618,0.8748136758804321,1.8188858032226562,0.26462316513061523,0.8555235862731934,1.7327840328216553,1.4438320398330688,0.5325329303741455,0.7508541941642761,1.339582920074463,1.70830500125885,1.0904120206832886,0.5070831775665283,0.5154015421867371,0.5790265798568726,1.773036003112793,0.5179463028907776,0.8528609871864319,1.0828299522399902,1.0559226274490356,1.0264568328857422,0.9711904525756836,0.2862222492694855,0.2566063702106476,0.0488421656191349,0.3808225095272064,0.6076082587242126,1.9010634422302246,1.3411893844604492,0.5852816700935364,1.391355276107788,2.265092134475708,0.08930734544992447,0.24246472120285034,0.3210437297821045,1.153865098953247,1.6873430013656616,0.13649778068065643,0.654911994934082,1.6923422813415527,0.5705053210258484,0.8343877792358398,0.6226603388786316,0.5277264714241028,1.5640935897827148,0.15445412695407867,0.5749205946922302,1.3574976921081543,1.0360305309295654,0.4930250644683838,0.20601044595241547,0.14740405976772308,0.458052396774292,0.9187849164009094,1.2116740942001343,0.23303964734077454,0.5805302262306213,0.5607320070266724,0.4117003083229065,2.465473175048828,1.6386477947235107,1.7031350135803223,0.7070876359939575,1.0024104118347168,0.8232477307319641,0.454339861869812,1.3948756456375122,0.3280218541622162,1.2151522636413574,0.003233200404793024,1.733725666999817,0.8428475260734558,0.22271889448165894,1.8416955471038818,0.7364838123321533,1.3723012208938599,0.5785627365112305,0.26418179273605347,0.9129582643508911,0.09611000120639801,1.287489891052246,0.11864835023880005,1.1257281303405762,0.2487281858921051,0.18080449104309082,1.7580831050872803,4.647215843200684,0.27495577931404114,0.9688863158226013,0.11473143100738525,0.5914319157600403,1.342428207397461,1.138437271118164,0.5523998737335205,0.7816208004951477,2.2148325443267822,1.0988376140594482,1.6005674600601196,1.1915910243988037,1.3269768953323364,0.378095418214798,0.4395700991153717,0.7779095768928528,0.830786943435669,0.054311707615852356,0.5461437702178955,0.5768404006958008,0.5111103653907776,0.909504771232605,0.601173460483551,0.1638777256011963,0.21263957023620605,1.3269394636154175,0.7092355489730835,0.23375484347343445,2.742825508117676,0.04263075068593025,1.0065308809280396,0.13075709342956543,0.5005173683166504,0.8138548135757446,0.06231844797730446,2.2059550285339355,0.06170327961444855,0.24481193721294403,0.9969230890274048,1.6500935554504395,0.55216383934021,1.2010104656219482,0.3328365385532379,0.6167313456535339,0.6265155076980591,0.1876257061958313,0.4984287619590759,0.6478190422058105,0.6275940537452698,1.99158775806427,0.817809522151947,0.6767558455467224,0.02413162961602211,0.8833544254302979,0.8482292294502258,0.3146904408931732,0.518939197063446,0.36384570598602295,0,0.9742975831031799,0.14988255500793457,0.43216198682785034,0.22757378220558167,0.4066866636276245,1.1298837661743164,0.9067808389663696,0.22455531358718872,0.05864056199789047,0.9304453730583191,0.9014017581939697,1.8137454986572266,0.0968548133969307,0.2409812957048416,0.6663099527359009,1.2130497694015503,0.24968497455120087,0.9161180853843689,0.353221595287323,0.8130157589912415,0.2987111806869507,0.6010563373565674,1.3867548704147339,2.9300880432128906,0.8085629343986511,0.40099242329597473,0.43244197964668274,0.5951328277587891,0.9391095638275146,0.26254960894584656,0.6959999203681946,0.1226467564702034,1.1226005554199219,0.4928096830844879,1.9354279041290283,0.3433109223842621,0.853999674320221,1.4863165616989136,1.118110179901123,1.1116055250167847,1.3586441278457642,1.268913745880127,0.3293173015117645,1.097061038017273,0.5706954002380371,1.133157730102539,0.08769454807043076,1.3840101957321167,3.1065492630004883,0.2939116060733795,0.2675859332084656,0.3103168308734894,0.4951390326023102,0.32175976037979126,0.6401526927947998,0.6554588079452515,0.6203683614730835,1.000016212463379,0.5004483461380005,1.860917329788208,1.376828908920288,0.7377114295959473,1.0946835279464722,0.7701189517974854,0.6361571550369263,0.6349006295204163,0.014083875343203545,0.12574534118175507,1.4780312776565552,0.684380054473877,0.26904618740081787,0.22538070380687714,0.00879308395087719,0.9298273921012878,1.9020938873291016,0.7884451150894165,0.07946275174617767,1.1165977716445923,0.30933812260627747,0.5403715372085571,1.1603479385375977,0.3008138835430145,1.3190054893493652,0.795007050037384,0.22366446256637573,0.7132651805877686,0.24945855140686035,1.3368955850601196,0.19791144132614136,0.3044207692146301,1.2946991920471191,2.172100067138672,0.0620955154299736,1.1856104135513306,1.050154209136963,0.22127188742160797,0.4226319491863251,0.6396470069885254,0.32624831795692444,0.6085342168807983,1.1651835441589355,0.16642792522907257,1.4138462543487549,1.297584056854248,1.6136043071746826,1.0782232284545898,0.6430231332778931,1.1396124362945557,0.3635297417640686,0.48967084288597107,0.46470028162002563,1.4600411653518677,0.4599548280239105,0.8528343439102173,0.14161142706871033,0.8258335590362549,0.582858145236969,0.9192168712615967,0.8009287118911743,0.6240752935409546,0.39849743247032166,0.7765495777130127,0.3709612488746643,2.652484893798828,0.8104533553123474,0.006185327190905809,0.35379570722579956,1.5678136348724365,0.6926767826080322,1.693477749824524,1.2875405550003052,0.6767603754997253,0.6120878458023071,2.0956668853759766,2.656432867050171,0.5108842253684998,2.3255038261413574,0.8390073180198669,2.5381956100463867,0.9664138555526733,0.6655454039573669,0.4604036509990692,0.4444624185562134,0.3776257634162903,2.2371013164520264,0.5808571577072144,0.31693392992019653,0.33319956064224243,0.8720987439155579,0.10681258141994476,0.6595206260681152,1.1067618131637573,1.2506458759307861,0.9226015210151672,0.8610519766807556,0.8589621782302856,0.4330337345600128,0.8203577995300293,0.2464151531457901,1.0172529220581055,0.3628329336643219,2.0844993591308594,0.6375234723091125,0.5927277207374573,1.312648892402649,0.37716561555862427,1.7252025604248047,0.45358338952064514,0.18682627379894257,0.0062152305617928505,0.6554352045059204,1.2754335403442383,0.8909009099006653,0.1972004622220993,1.9216731786727905,1.2920082807540894,0.13435210287570953,2.3624701499938965,0.22141511738300323,0.9661803245544434,1.1026030778884888,0.23505376279354095,0.030129481106996536,0.4950302243232727,0.4512035548686981,0.026624049991369247,1.4041651487350464,0.209356427192688,1.6079065799713135,2.164234161376953,1.5885684490203857,0.8960825204849243,1.886052131652832,0.2653025686740875,1.7950098514556885,0.6545081734657288,1.6967599391937256,0.03656003996729851,1.2098544836044312,3.3872408866882324,1.5322508811950684,0.19821853935718536,1.120884656906128,0.5186260938644409,0.7395733594894409,0.928455114364624,1.355623483657837,0.26370465755462646,1.2062020301818848,0.09249454736709595,0.5326693654060364,1.3141732215881348,0.2362804263830185,0.4778333008289337,0.22796191275119781,1.056803822517395,0.020549282431602478,0.33742931485176086,0.46185389161109924,2.4832324981689453,0.7287827134132385,0.36329227685928345,0.6522612571716309,0.3673896789550781,1.4581804275512695,0.6657092571258545,1.0051950216293335,1.2561205625534058,0.614002525806427,2.298558235168457,0.9794350266456604,0.48981261253356934,2.2125771045684814,0.2940179109573364,1.4562480449676514,0.9676485061645508,0.7187795639038086,0.6007862687110901,0.056436002254486084,1.2564529180526733,0.41692912578582764,0.7548884153366089,0.1753060221672058,1.8333464860916138,0.4114776849746704,0.42682477831840515,0.0785190537571907,1.204007863998413,0.16531716287136078,1.065880298614502,0.13906246423721313,0.4406368136405945,0.46826162934303284,1.468100666999817,1.7256641387939453,0.058478403836488724,0.3659725487232208,1.2905678749084473,1.2725436687469482,0.34850987792015076,0.22254829108715057,0.0393102802336216]\n",
+ "len(testVec)\n",
+ "federPy.searchByVec(testVec)\n",
+ "# federPy.searchByVec(testVec, imageUrls[testId])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "5c7f9bb0",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " Feder\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(federPy.searchByVec(testVec, imageUrls[testId], False))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8484f838",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ba0f7925",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/fig/hnsw_overview.png b/fig/hnsw_overview.png
new file mode 100644
index 0000000..5201dfb
Binary files /dev/null and b/fig/hnsw_overview.png differ
diff --git a/fig/hnsw_search.png b/fig/hnsw_search.png
new file mode 100644
index 0000000..e016d25
Binary files /dev/null and b/fig/hnsw_search.png differ
diff --git a/fig/ivfflat_coarse.png b/fig/ivfflat_coarse.png
new file mode 100644
index 0000000..231253e
Binary files /dev/null and b/fig/ivfflat_coarse.png differ
diff --git a/fig/ivfflat_fine_polar.png b/fig/ivfflat_fine_polar.png
new file mode 100644
index 0000000..d3ebe72
Binary files /dev/null and b/fig/ivfflat_fine_polar.png differ
diff --git a/fig/ivfflat_fine_project.png b/fig/ivfflat_fine_project.png
new file mode 100644
index 0000000..611275d
Binary files /dev/null and b/fig/ivfflat_fine_project.png differ
diff --git a/fig/ivfflat_overview.png b/fig/ivfflat_overview.png
new file mode 100644
index 0000000..d5204a1
Binary files /dev/null and b/fig/ivfflat_overview.png differ
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index c269617..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,1181 +0,0 @@
-{
- "name": "feder",
- "version": "0.1.0",
- "lockfileVersion": 2,
- "requires": true,
- "packages": {
- "": {
- "name": "feder",
- "version": "0.1.0",
- "license": "Apache-2.0",
- "devDependencies": {
- "ascjs": "^5.0.1",
- "c8": "^7.11.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.16.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz",
- "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==",
- "dev": true,
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true
- },
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
- "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
- "dev": true
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/ascjs": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ascjs/-/ascjs-5.0.1.tgz",
- "integrity": "sha512-1d/QdICzpywXiP53/Zz3fMdaC0/BB1ybLf+fK+QrqY8iyXNnWUHUrpmrowueXeswo+O+meJWm43TJSg2ClP3Sg==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.12.5"
- },
- "bin": {
- "ascjs": "bin.js"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/c8": {
- "version": "7.11.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-7.11.0.tgz",
- "integrity": "sha512-XqPyj1uvlHMr+Y1IeRndC2X5P7iJzJlEJwBpCdBbq2JocXOgJfr+JVfJkyNMGROke5LfKrhSFXGFXnwnRJAUJw==",
- "dev": true,
- "dependencies": {
- "@bcoe/v8-coverage": "^0.2.3",
- "@istanbuljs/schema": "^0.1.2",
- "find-up": "^5.0.0",
- "foreground-child": "^2.0.0",
- "istanbul-lib-coverage": "^3.0.1",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-reports": "^3.0.2",
- "rimraf": "^3.0.0",
- "test-exclude": "^6.0.0",
- "v8-to-istanbul": "^8.0.0",
- "yargs": "^16.2.0",
- "yargs-parser": "^20.2.7"
- },
- "bin": {
- "c8": "bin/c8.js"
- },
- "engines": {
- "node": ">=10.12.0"
- }
- },
- "node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "dev": true,
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "dev": true
- },
- "node_modules/convert-source-map": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
- "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
- "dev": true,
- "dependencies": {
- "safe-buffer": "~5.1.1"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "dev": true,
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
- },
- "node_modules/escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/foreground-child": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
- "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
- "dev": true,
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^3.0.2"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "dev": true
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true,
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/glob": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
- "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "dev": true,
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
- "dev": true
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
- "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
- "dev": true,
- "dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^3.0.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-reports": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz",
- "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==",
- "dev": true,
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
- "dependencies": {
- "semver": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "dev": true,
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
- "node_modules/semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
- "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==",
- "dev": true
- },
- "node_modules/source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
- "dev": true,
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/v8-to-istanbul": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz",
- "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==",
- "dev": true,
- "dependencies": {
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^1.6.0",
- "source-map": "^0.7.3"
- },
- "engines": {
- "node": ">=10.12.0"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "dev": true
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "dev": true,
- "dependencies": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- }
- },
- "dependencies": {
- "@babel/parser": {
- "version": "7.16.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz",
- "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==",
- "dev": true
- },
- "@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true
- },
- "@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
- "dev": true
- },
- "@types/istanbul-lib-coverage": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
- "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
- "dev": true
- },
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "ascjs": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ascjs/-/ascjs-5.0.1.tgz",
- "integrity": "sha512-1d/QdICzpywXiP53/Zz3fMdaC0/BB1ybLf+fK+QrqY8iyXNnWUHUrpmrowueXeswo+O+meJWm43TJSg2ClP3Sg==",
- "dev": true,
- "requires": {
- "@babel/parser": "^7.12.5"
- }
- },
- "balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "c8": {
- "version": "7.11.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-7.11.0.tgz",
- "integrity": "sha512-XqPyj1uvlHMr+Y1IeRndC2X5P7iJzJlEJwBpCdBbq2JocXOgJfr+JVfJkyNMGROke5LfKrhSFXGFXnwnRJAUJw==",
- "dev": true,
- "requires": {
- "@bcoe/v8-coverage": "^0.2.3",
- "@istanbuljs/schema": "^0.1.2",
- "find-up": "^5.0.0",
- "foreground-child": "^2.0.0",
- "istanbul-lib-coverage": "^3.0.1",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-reports": "^3.0.2",
- "rimraf": "^3.0.0",
- "test-exclude": "^6.0.0",
- "v8-to-istanbul": "^8.0.0",
- "yargs": "^16.2.0",
- "yargs-parser": "^20.2.7"
- }
- },
- "cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "dev": true,
- "requires": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "dev": true
- },
- "convert-source-map": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
- "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.1"
- }
- },
- "cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "dev": true,
- "requires": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- }
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
- },
- "escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true
- },
- "find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "requires": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "foreground-child": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
- "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
- "dev": true,
- "requires": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^3.0.2"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "dev": true
- },
- "get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true
- },
- "glob": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
- "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "dev": true,
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
- },
- "isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
- "dev": true
- },
- "istanbul-lib-coverage": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
- "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==",
- "dev": true
- },
- "istanbul-lib-report": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
- "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
- "dev": true,
- "requires": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^3.0.0",
- "supports-color": "^7.1.0"
- }
- },
- "istanbul-reports": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz",
- "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==",
- "dev": true,
- "requires": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- }
- },
- "locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "requires": {
- "p-locate": "^5.0.0"
- }
- },
- "make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
- "requires": {
- "semver": "^6.0.0"
- }
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "dev": true,
- "requires": {
- "wrappy": "1"
- }
- },
- "p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "requires": {
- "yocto-queue": "^0.1.0"
- }
- },
- "p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "requires": {
- "p-limit": "^3.0.2"
- }
- },
- "path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "dev": true
- },
- "path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true
- },
- "require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
- "dev": true
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
- },
- "shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "requires": {
- "shebang-regex": "^3.0.0"
- }
- },
- "shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true
- },
- "signal-exit": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
- "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==",
- "dev": true
- },
- "source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
- "dev": true
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- },
- "test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "requires": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- }
- },
- "v8-to-istanbul": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz",
- "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==",
- "dev": true,
- "requires": {
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^1.6.0",
- "source-map": "^0.7.3"
- }
- },
- "which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "requires": {
- "isexe": "^2.0.0"
- }
- },
- "wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "dev": true
- },
- "y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true
- },
- "yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "dev": true,
- "requires": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- }
- },
- "yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
- "dev": true
- },
- "yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true
- }
- }
-}
diff --git a/package.json b/package.json
index 2fc47a4..82f34e0 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,23 @@
{
- "name": "feder",
+ "name": "@zilliz/feder",
"author": "ued@zilliz.com",
- "version": "0.1.0",
+ "version": "0.2.4",
"description": "visualization packages for vector space",
- "main": "./cjs/index.js",
+ "main": "dist/index.js",
+ "files": [
+ "dist"
+ ],
"scripts": {
- "build": "npm run cjs && npm run test",
+ "build": "esbuild federjs/index.js --format=esm --bundle --outdir=dist",
+ "build_iife_global": "esbuild federjs/index.js --format=iife --global-name=Feder --bundle --outfile=test/feder_iife_global.js",
+ "build_esm": "esbuild federjs/index.js --format=esm --bundle --outfile=test/feder_esm.js --watch",
"cjs": "ascjs --no-default esm cjs",
"test": "c8 node test/index.js",
- "coverage": "mkdir -p ./coverage; c8 report --reporter=text-lcov > ./coverage/lcov.info"
+ "coverage": "mkdir -p ./coverage; c8 report --reporter=text-lcov > ./coverage/lcov.info",
+ "dev": "esbuild test/index.js --bundle --outfile=test/bundle.js --watch",
+ "dev_core_browser": "esbuild test/test_core_browser.js --bundle --outfile=test/bundle.js --watch",
+ "dev_core_node": "esbuild test/test_core_node.js --bundle --outfile=test/bundle.js --watch",
+ "publish_py": "cd federpy && rm -rf dist && python -m build && twine upload dist/*"
},
"repository": {
"type": "git",
@@ -21,17 +30,14 @@
"hnsw"
],
"devDependencies": {
+ "@zilliz/feder": "^0.2.4",
"ascjs": "^5.0.1",
- "c8": "^7.11.0"
+ "c8": "^7.11.2",
+ "esbuild": "^0.14.38"
},
"exports": {
".": {
- "import": "./esm/index.js",
- "default": "./cjs/index.js"
- },
- "./utils": {
- "import": "./esm/utils.js",
- "default": "./cjs/utils.js"
+ "import": "./dist/index.js"
},
"./package.json": "./package.json"
},
@@ -47,5 +53,20 @@
},
"module": "./esm/index.js",
"type": "module",
- "homepage": "https://github.com/zilliztech/feder#readme"
+ "homepage": "https://github.com/zilliztech/feder#readme",
+ "dependencies": {
+ "@types/animejs": "^3.1.5",
+ "@types/three": "^0.143.2",
+ "animejs": "^3.2.1",
+ "d3": "^7.4.4",
+ "d3-fetch": "^3.0.1",
+ "seedrandom": "^3.0.5",
+ "three": "^0.143.0",
+ "three.meshline": "^1.4.0",
+ "tsne-js": "^1.0.3",
+ "umap-js": "^1.3.3"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
}
diff --git a/test/bundle.js b/test/bundle.js
new file mode 100644
index 0000000..53cf06c
--- /dev/null
+++ b/test/bundle.js
@@ -0,0 +1,54262 @@
+(() => {
+ var __create = Object.create;
+ var __defProp = Object.defineProperty;
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+ var __getOwnPropNames = Object.getOwnPropertyNames;
+ var __getProtoOf = Object.getPrototypeOf;
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
+ var __require = /* @__PURE__ */ ((x2) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x2, {
+ get: (a2, b) => (typeof require !== "undefined" ? require : a2)[b]
+ }) : x2)(function(x2) {
+ if (typeof require !== "undefined")
+ return require.apply(this, arguments);
+ throw new Error('Dynamic require of "' + x2 + '" is not supported');
+ });
+ var __commonJS = (cb, mod) => function __require2() {
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
+ };
+ var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+ };
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
+ var __async = (__this, __arguments, generator) => {
+ return new Promise((resolve, reject) => {
+ var fulfilled = (value) => {
+ try {
+ step(generator.next(value));
+ } catch (e) {
+ reject(e);
+ }
+ };
+ var rejected = (value) => {
+ try {
+ step(generator.throw(value));
+ } catch (e) {
+ reject(e);
+ }
+ };
+ var step = (x2) => x2.done ? resolve(x2.value) : Promise.resolve(x2.value).then(fulfilled, rejected);
+ step((generator = generator.apply(__this, __arguments)).next());
+ });
+ };
+
+ // node_modules/three/build/three.cjs
+ var require_three = __commonJS({
+ "node_modules/three/build/three.cjs"(exports) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ var REVISION2 = "143";
+ var MOUSE2 = {
+ LEFT: 0,
+ MIDDLE: 1,
+ RIGHT: 2,
+ ROTATE: 0,
+ DOLLY: 1,
+ PAN: 2
+ };
+ var TOUCH2 = {
+ ROTATE: 0,
+ PAN: 1,
+ DOLLY_PAN: 2,
+ DOLLY_ROTATE: 3
+ };
+ var CullFaceNone2 = 0;
+ var CullFaceBack2 = 1;
+ var CullFaceFront2 = 2;
+ var CullFaceFrontBack = 3;
+ var BasicShadowMap = 0;
+ var PCFShadowMap2 = 1;
+ var PCFSoftShadowMap2 = 2;
+ var VSMShadowMap2 = 3;
+ var FrontSide2 = 0;
+ var BackSide2 = 1;
+ var DoubleSide2 = 2;
+ var FlatShading2 = 1;
+ var SmoothShading = 2;
+ var NoBlending2 = 0;
+ var NormalBlending2 = 1;
+ var AdditiveBlending2 = 2;
+ var SubtractiveBlending2 = 3;
+ var MultiplyBlending2 = 4;
+ var CustomBlending2 = 5;
+ var AddEquation2 = 100;
+ var SubtractEquation2 = 101;
+ var ReverseSubtractEquation2 = 102;
+ var MinEquation2 = 103;
+ var MaxEquation2 = 104;
+ var ZeroFactor2 = 200;
+ var OneFactor2 = 201;
+ var SrcColorFactor2 = 202;
+ var OneMinusSrcColorFactor2 = 203;
+ var SrcAlphaFactor2 = 204;
+ var OneMinusSrcAlphaFactor2 = 205;
+ var DstAlphaFactor2 = 206;
+ var OneMinusDstAlphaFactor2 = 207;
+ var DstColorFactor2 = 208;
+ var OneMinusDstColorFactor2 = 209;
+ var SrcAlphaSaturateFactor2 = 210;
+ var NeverDepth2 = 0;
+ var AlwaysDepth2 = 1;
+ var LessDepth2 = 2;
+ var LessEqualDepth2 = 3;
+ var EqualDepth2 = 4;
+ var GreaterEqualDepth2 = 5;
+ var GreaterDepth2 = 6;
+ var NotEqualDepth2 = 7;
+ var MultiplyOperation2 = 0;
+ var MixOperation2 = 1;
+ var AddOperation2 = 2;
+ var NoToneMapping2 = 0;
+ var LinearToneMapping2 = 1;
+ var ReinhardToneMapping2 = 2;
+ var CineonToneMapping2 = 3;
+ var ACESFilmicToneMapping2 = 4;
+ var CustomToneMapping2 = 5;
+ var UVMapping2 = 300;
+ var CubeReflectionMapping2 = 301;
+ var CubeRefractionMapping2 = 302;
+ var EquirectangularReflectionMapping2 = 303;
+ var EquirectangularRefractionMapping2 = 304;
+ var CubeUVReflectionMapping2 = 306;
+ var RepeatWrapping2 = 1e3;
+ var ClampToEdgeWrapping2 = 1001;
+ var MirroredRepeatWrapping2 = 1002;
+ var NearestFilter2 = 1003;
+ var NearestMipmapNearestFilter2 = 1004;
+ var NearestMipMapNearestFilter = 1004;
+ var NearestMipmapLinearFilter2 = 1005;
+ var NearestMipMapLinearFilter = 1005;
+ var LinearFilter2 = 1006;
+ var LinearMipmapNearestFilter2 = 1007;
+ var LinearMipMapNearestFilter = 1007;
+ var LinearMipmapLinearFilter2 = 1008;
+ var LinearMipMapLinearFilter = 1008;
+ var UnsignedByteType2 = 1009;
+ var ByteType2 = 1010;
+ var ShortType2 = 1011;
+ var UnsignedShortType2 = 1012;
+ var IntType2 = 1013;
+ var UnsignedIntType2 = 1014;
+ var FloatType2 = 1015;
+ var HalfFloatType2 = 1016;
+ var UnsignedShort4444Type2 = 1017;
+ var UnsignedShort5551Type2 = 1018;
+ var UnsignedInt248Type2 = 1020;
+ var AlphaFormat2 = 1021;
+ var RGBFormat2 = 1022;
+ var RGBAFormat2 = 1023;
+ var LuminanceFormat2 = 1024;
+ var LuminanceAlphaFormat2 = 1025;
+ var DepthFormat2 = 1026;
+ var DepthStencilFormat2 = 1027;
+ var RedFormat2 = 1028;
+ var RedIntegerFormat2 = 1029;
+ var RGFormat2 = 1030;
+ var RGIntegerFormat2 = 1031;
+ var RGBAIntegerFormat2 = 1033;
+ var RGB_S3TC_DXT1_Format2 = 33776;
+ var RGBA_S3TC_DXT1_Format2 = 33777;
+ var RGBA_S3TC_DXT3_Format2 = 33778;
+ var RGBA_S3TC_DXT5_Format2 = 33779;
+ var RGB_PVRTC_4BPPV1_Format2 = 35840;
+ var RGB_PVRTC_2BPPV1_Format2 = 35841;
+ var RGBA_PVRTC_4BPPV1_Format2 = 35842;
+ var RGBA_PVRTC_2BPPV1_Format2 = 35843;
+ var RGB_ETC1_Format2 = 36196;
+ var RGB_ETC2_Format2 = 37492;
+ var RGBA_ETC2_EAC_Format2 = 37496;
+ var RGBA_ASTC_4x4_Format2 = 37808;
+ var RGBA_ASTC_5x4_Format2 = 37809;
+ var RGBA_ASTC_5x5_Format2 = 37810;
+ var RGBA_ASTC_6x5_Format2 = 37811;
+ var RGBA_ASTC_6x6_Format2 = 37812;
+ var RGBA_ASTC_8x5_Format2 = 37813;
+ var RGBA_ASTC_8x6_Format2 = 37814;
+ var RGBA_ASTC_8x8_Format2 = 37815;
+ var RGBA_ASTC_10x5_Format2 = 37816;
+ var RGBA_ASTC_10x6_Format2 = 37817;
+ var RGBA_ASTC_10x8_Format2 = 37818;
+ var RGBA_ASTC_10x10_Format2 = 37819;
+ var RGBA_ASTC_12x10_Format2 = 37820;
+ var RGBA_ASTC_12x12_Format2 = 37821;
+ var RGBA_BPTC_Format2 = 36492;
+ var LoopOnce = 2200;
+ var LoopRepeat = 2201;
+ var LoopPingPong = 2202;
+ var InterpolateDiscrete2 = 2300;
+ var InterpolateLinear2 = 2301;
+ var InterpolateSmooth2 = 2302;
+ var ZeroCurvatureEnding2 = 2400;
+ var ZeroSlopeEnding2 = 2401;
+ var WrapAroundEnding2 = 2402;
+ var NormalAnimationBlendMode = 2500;
+ var AdditiveAnimationBlendMode = 2501;
+ var TrianglesDrawMode = 0;
+ var TriangleStripDrawMode = 1;
+ var TriangleFanDrawMode = 2;
+ var LinearEncoding2 = 3e3;
+ var sRGBEncoding2 = 3001;
+ var BasicDepthPacking2 = 3200;
+ var RGBADepthPacking2 = 3201;
+ var TangentSpaceNormalMap2 = 0;
+ var ObjectSpaceNormalMap2 = 1;
+ var NoColorSpace = "";
+ var SRGBColorSpace2 = "srgb";
+ var LinearSRGBColorSpace2 = "srgb-linear";
+ var ZeroStencilOp = 0;
+ var KeepStencilOp2 = 7680;
+ var ReplaceStencilOp = 7681;
+ var IncrementStencilOp = 7682;
+ var DecrementStencilOp = 7683;
+ var IncrementWrapStencilOp = 34055;
+ var DecrementWrapStencilOp = 34056;
+ var InvertStencilOp = 5386;
+ var NeverStencilFunc = 512;
+ var LessStencilFunc = 513;
+ var EqualStencilFunc = 514;
+ var LessEqualStencilFunc = 515;
+ var GreaterStencilFunc = 516;
+ var NotEqualStencilFunc = 517;
+ var GreaterEqualStencilFunc = 518;
+ var AlwaysStencilFunc2 = 519;
+ var StaticDrawUsage2 = 35044;
+ var DynamicDrawUsage = 35048;
+ var StreamDrawUsage = 35040;
+ var StaticReadUsage = 35045;
+ var DynamicReadUsage = 35049;
+ var StreamReadUsage = 35041;
+ var StaticCopyUsage = 35046;
+ var DynamicCopyUsage = 35050;
+ var StreamCopyUsage = 35042;
+ var GLSL1 = "100";
+ var GLSL32 = "300 es";
+ var _SRGBAFormat2 = 1035;
+ var EventDispatcher2 = class {
+ addEventListener(type2, listener) {
+ if (this._listeners === void 0)
+ this._listeners = {};
+ const listeners = this._listeners;
+ if (listeners[type2] === void 0) {
+ listeners[type2] = [];
+ }
+ if (listeners[type2].indexOf(listener) === -1) {
+ listeners[type2].push(listener);
+ }
+ }
+ hasEventListener(type2, listener) {
+ if (this._listeners === void 0)
+ return false;
+ const listeners = this._listeners;
+ return listeners[type2] !== void 0 && listeners[type2].indexOf(listener) !== -1;
+ }
+ removeEventListener(type2, listener) {
+ if (this._listeners === void 0)
+ return;
+ const listeners = this._listeners;
+ const listenerArray = listeners[type2];
+ if (listenerArray !== void 0) {
+ const index2 = listenerArray.indexOf(listener);
+ if (index2 !== -1) {
+ listenerArray.splice(index2, 1);
+ }
+ }
+ }
+ dispatchEvent(event) {
+ if (this._listeners === void 0)
+ return;
+ const listeners = this._listeners;
+ const listenerArray = listeners[event.type];
+ if (listenerArray !== void 0) {
+ event.target = this;
+ const array2 = listenerArray.slice(0);
+ for (let i = 0, l = array2.length; i < l; i++) {
+ array2[i].call(this, event);
+ }
+ event.target = null;
+ }
+ }
+ };
+ var _lut2 = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff"];
+ var _seed = 1234567;
+ var DEG2RAD2 = Math.PI / 180;
+ var RAD2DEG2 = 180 / Math.PI;
+ function generateUUID2() {
+ const d0 = Math.random() * 4294967295 | 0;
+ const d1 = Math.random() * 4294967295 | 0;
+ const d2 = Math.random() * 4294967295 | 0;
+ const d3 = Math.random() * 4294967295 | 0;
+ const uuid = _lut2[d0 & 255] + _lut2[d0 >> 8 & 255] + _lut2[d0 >> 16 & 255] + _lut2[d0 >> 24 & 255] + "-" + _lut2[d1 & 255] + _lut2[d1 >> 8 & 255] + "-" + _lut2[d1 >> 16 & 15 | 64] + _lut2[d1 >> 24 & 255] + "-" + _lut2[d2 & 63 | 128] + _lut2[d2 >> 8 & 255] + "-" + _lut2[d2 >> 16 & 255] + _lut2[d2 >> 24 & 255] + _lut2[d3 & 255] + _lut2[d3 >> 8 & 255] + _lut2[d3 >> 16 & 255] + _lut2[d3 >> 24 & 255];
+ return uuid.toLowerCase();
+ }
+ function clamp2(value, min2, max2) {
+ return Math.max(min2, Math.min(max2, value));
+ }
+ function euclideanModulo2(n, m2) {
+ return (n % m2 + m2) % m2;
+ }
+ function mapLinear(x2, a1, a2, b1, b2) {
+ return b1 + (x2 - a1) * (b2 - b1) / (a2 - a1);
+ }
+ function inverseLerp(x2, y2, value) {
+ if (x2 !== y2) {
+ return (value - x2) / (y2 - x2);
+ } else {
+ return 0;
+ }
+ }
+ function lerp2(x2, y2, t) {
+ return (1 - t) * x2 + t * y2;
+ }
+ function damp(x2, y2, lambda, dt) {
+ return lerp2(x2, y2, 1 - Math.exp(-lambda * dt));
+ }
+ function pingpong(x2, length = 1) {
+ return length - Math.abs(euclideanModulo2(x2, length * 2) - length);
+ }
+ function smoothstep(x2, min2, max2) {
+ if (x2 <= min2)
+ return 0;
+ if (x2 >= max2)
+ return 1;
+ x2 = (x2 - min2) / (max2 - min2);
+ return x2 * x2 * (3 - 2 * x2);
+ }
+ function smootherstep(x2, min2, max2) {
+ if (x2 <= min2)
+ return 0;
+ if (x2 >= max2)
+ return 1;
+ x2 = (x2 - min2) / (max2 - min2);
+ return x2 * x2 * x2 * (x2 * (x2 * 6 - 15) + 10);
+ }
+ function randInt(low, high) {
+ return low + Math.floor(Math.random() * (high - low + 1));
+ }
+ function randFloat(low, high) {
+ return low + Math.random() * (high - low);
+ }
+ function randFloatSpread(range2) {
+ return range2 * (0.5 - Math.random());
+ }
+ function seededRandom(s) {
+ if (s !== void 0)
+ _seed = s;
+ let t = _seed += 1831565813;
+ t = Math.imul(t ^ t >>> 15, t | 1);
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
+ }
+ function degToRad(degrees2) {
+ return degrees2 * DEG2RAD2;
+ }
+ function radToDeg(radians) {
+ return radians * RAD2DEG2;
+ }
+ function isPowerOfTwo2(value) {
+ return (value & value - 1) === 0 && value !== 0;
+ }
+ function ceilPowerOfTwo(value) {
+ return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2));
+ }
+ function floorPowerOfTwo2(value) {
+ return Math.pow(2, Math.floor(Math.log(value) / Math.LN2));
+ }
+ function setQuaternionFromProperEuler(q, a2, b, c2, order) {
+ const cos = Math.cos;
+ const sin = Math.sin;
+ const c22 = cos(b / 2);
+ const s2 = sin(b / 2);
+ const c13 = cos((a2 + c2) / 2);
+ const s13 = sin((a2 + c2) / 2);
+ const c1_3 = cos((a2 - c2) / 2);
+ const s1_3 = sin((a2 - c2) / 2);
+ const c3_1 = cos((c2 - a2) / 2);
+ const s3_1 = sin((c2 - a2) / 2);
+ switch (order) {
+ case "XYX":
+ q.set(c22 * s13, s2 * c1_3, s2 * s1_3, c22 * c13);
+ break;
+ case "YZY":
+ q.set(s2 * s1_3, c22 * s13, s2 * c1_3, c22 * c13);
+ break;
+ case "ZXZ":
+ q.set(s2 * c1_3, s2 * s1_3, c22 * s13, c22 * c13);
+ break;
+ case "XZX":
+ q.set(c22 * s13, s2 * s3_1, s2 * c3_1, c22 * c13);
+ break;
+ case "YXY":
+ q.set(s2 * c3_1, c22 * s13, s2 * s3_1, c22 * c13);
+ break;
+ case "ZYZ":
+ q.set(s2 * s3_1, s2 * c3_1, c22 * s13, c22 * c13);
+ break;
+ default:
+ console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: " + order);
+ }
+ }
+ function denormalize$1(value, array2) {
+ switch (array2.constructor) {
+ case Float32Array:
+ return value;
+ case Uint16Array:
+ return value / 65535;
+ case Uint8Array:
+ return value / 255;
+ case Int16Array:
+ return Math.max(value / 32767, -1);
+ case Int8Array:
+ return Math.max(value / 127, -1);
+ default:
+ throw new Error("Invalid component type.");
+ }
+ }
+ function normalize2(value, array2) {
+ switch (array2.constructor) {
+ case Float32Array:
+ return value;
+ case Uint16Array:
+ return Math.round(value * 65535);
+ case Uint8Array:
+ return Math.round(value * 255);
+ case Int16Array:
+ return Math.round(value * 32767);
+ case Int8Array:
+ return Math.round(value * 127);
+ default:
+ throw new Error("Invalid component type.");
+ }
+ }
+ var MathUtils = /* @__PURE__ */ Object.freeze({
+ __proto__: null,
+ DEG2RAD: DEG2RAD2,
+ RAD2DEG: RAD2DEG2,
+ generateUUID: generateUUID2,
+ clamp: clamp2,
+ euclideanModulo: euclideanModulo2,
+ mapLinear,
+ inverseLerp,
+ lerp: lerp2,
+ damp,
+ pingpong,
+ smoothstep,
+ smootherstep,
+ randInt,
+ randFloat,
+ randFloatSpread,
+ seededRandom,
+ degToRad,
+ radToDeg,
+ isPowerOfTwo: isPowerOfTwo2,
+ ceilPowerOfTwo,
+ floorPowerOfTwo: floorPowerOfTwo2,
+ setQuaternionFromProperEuler,
+ normalize: normalize2,
+ denormalize: denormalize$1
+ });
+ var Vector22 = class {
+ constructor(x2 = 0, y2 = 0) {
+ Vector22.prototype.isVector2 = true;
+ this.x = x2;
+ this.y = y2;
+ }
+ get width() {
+ return this.x;
+ }
+ set width(value) {
+ this.x = value;
+ }
+ get height() {
+ return this.y;
+ }
+ set height(value) {
+ this.y = value;
+ }
+ set(x2, y2) {
+ this.x = x2;
+ this.y = y2;
+ return this;
+ }
+ setScalar(scalar) {
+ this.x = scalar;
+ this.y = scalar;
+ return this;
+ }
+ setX(x2) {
+ this.x = x2;
+ return this;
+ }
+ setY(y2) {
+ this.y = y2;
+ return this;
+ }
+ setComponent(index2, value) {
+ switch (index2) {
+ case 0:
+ this.x = value;
+ break;
+ case 1:
+ this.y = value;
+ break;
+ default:
+ throw new Error("index is out of range: " + index2);
+ }
+ return this;
+ }
+ getComponent(index2) {
+ switch (index2) {
+ case 0:
+ return this.x;
+ case 1:
+ return this.y;
+ default:
+ throw new Error("index is out of range: " + index2);
+ }
+ }
+ clone() {
+ return new this.constructor(this.x, this.y);
+ }
+ copy(v) {
+ this.x = v.x;
+ this.y = v.y;
+ return this;
+ }
+ add(v) {
+ this.x += v.x;
+ this.y += v.y;
+ return this;
+ }
+ addScalar(s) {
+ this.x += s;
+ this.y += s;
+ return this;
+ }
+ addVectors(a2, b) {
+ this.x = a2.x + b.x;
+ this.y = a2.y + b.y;
+ return this;
+ }
+ addScaledVector(v, s) {
+ this.x += v.x * s;
+ this.y += v.y * s;
+ return this;
+ }
+ sub(v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ return this;
+ }
+ subScalar(s) {
+ this.x -= s;
+ this.y -= s;
+ return this;
+ }
+ subVectors(a2, b) {
+ this.x = a2.x - b.x;
+ this.y = a2.y - b.y;
+ return this;
+ }
+ multiply(v) {
+ this.x *= v.x;
+ this.y *= v.y;
+ return this;
+ }
+ multiplyScalar(scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ return this;
+ }
+ divide(v) {
+ this.x /= v.x;
+ this.y /= v.y;
+ return this;
+ }
+ divideScalar(scalar) {
+ return this.multiplyScalar(1 / scalar);
+ }
+ applyMatrix3(m2) {
+ const x2 = this.x, y2 = this.y;
+ const e = m2.elements;
+ this.x = e[0] * x2 + e[3] * y2 + e[6];
+ this.y = e[1] * x2 + e[4] * y2 + e[7];
+ return this;
+ }
+ min(v) {
+ this.x = Math.min(this.x, v.x);
+ this.y = Math.min(this.y, v.y);
+ return this;
+ }
+ max(v) {
+ this.x = Math.max(this.x, v.x);
+ this.y = Math.max(this.y, v.y);
+ return this;
+ }
+ clamp(min2, max2) {
+ this.x = Math.max(min2.x, Math.min(max2.x, this.x));
+ this.y = Math.max(min2.y, Math.min(max2.y, this.y));
+ return this;
+ }
+ clampScalar(minVal, maxVal) {
+ this.x = Math.max(minVal, Math.min(maxVal, this.x));
+ this.y = Math.max(minVal, Math.min(maxVal, this.y));
+ return this;
+ }
+ clampLength(min2, max2) {
+ const length = this.length();
+ return this.divideScalar(length || 1).multiplyScalar(Math.max(min2, Math.min(max2, length)));
+ }
+ floor() {
+ this.x = Math.floor(this.x);
+ this.y = Math.floor(this.y);
+ return this;
+ }
+ ceil() {
+ this.x = Math.ceil(this.x);
+ this.y = Math.ceil(this.y);
+ return this;
+ }
+ round() {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ return this;
+ }
+ roundToZero() {
+ this.x = this.x < 0 ? Math.ceil(this.x) : Math.floor(this.x);
+ this.y = this.y < 0 ? Math.ceil(this.y) : Math.floor(this.y);
+ return this;
+ }
+ negate() {
+ this.x = -this.x;
+ this.y = -this.y;
+ return this;
+ }
+ dot(v) {
+ return this.x * v.x + this.y * v.y;
+ }
+ cross(v) {
+ return this.x * v.y - this.y * v.x;
+ }
+ lengthSq() {
+ return this.x * this.x + this.y * this.y;
+ }
+ length() {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ manhattanLength() {
+ return Math.abs(this.x) + Math.abs(this.y);
+ }
+ normalize() {
+ return this.divideScalar(this.length() || 1);
+ }
+ angle() {
+ const angle = Math.atan2(-this.y, -this.x) + Math.PI;
+ return angle;
+ }
+ distanceTo(v) {
+ return Math.sqrt(this.distanceToSquared(v));
+ }
+ distanceToSquared(v) {
+ const dx = this.x - v.x, dy = this.y - v.y;
+ return dx * dx + dy * dy;
+ }
+ manhattanDistanceTo(v) {
+ return Math.abs(this.x - v.x) + Math.abs(this.y - v.y);
+ }
+ setLength(length) {
+ return this.normalize().multiplyScalar(length);
+ }
+ lerp(v, alpha) {
+ this.x += (v.x - this.x) * alpha;
+ this.y += (v.y - this.y) * alpha;
+ return this;
+ }
+ lerpVectors(v1, v2, alpha) {
+ this.x = v1.x + (v2.x - v1.x) * alpha;
+ this.y = v1.y + (v2.y - v1.y) * alpha;
+ return this;
+ }
+ equals(v) {
+ return v.x === this.x && v.y === this.y;
+ }
+ fromArray(array2, offset = 0) {
+ this.x = array2[offset];
+ this.y = array2[offset + 1];
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ array2[offset] = this.x;
+ array2[offset + 1] = this.y;
+ return array2;
+ }
+ fromBufferAttribute(attribute, index2) {
+ this.x = attribute.getX(index2);
+ this.y = attribute.getY(index2);
+ return this;
+ }
+ rotateAround(center, angle) {
+ const c2 = Math.cos(angle), s = Math.sin(angle);
+ const x2 = this.x - center.x;
+ const y2 = this.y - center.y;
+ this.x = x2 * c2 - y2 * s + center.x;
+ this.y = x2 * s + y2 * c2 + center.y;
+ return this;
+ }
+ random() {
+ this.x = Math.random();
+ this.y = Math.random();
+ return this;
+ }
+ *[Symbol.iterator]() {
+ yield this.x;
+ yield this.y;
+ }
+ };
+ var Matrix32 = class {
+ constructor() {
+ Matrix32.prototype.isMatrix3 = true;
+ this.elements = [1, 0, 0, 0, 1, 0, 0, 0, 1];
+ }
+ set(n11, n12, n13, n21, n22, n23, n31, n32, n33) {
+ const te = this.elements;
+ te[0] = n11;
+ te[1] = n21;
+ te[2] = n31;
+ te[3] = n12;
+ te[4] = n22;
+ te[5] = n32;
+ te[6] = n13;
+ te[7] = n23;
+ te[8] = n33;
+ return this;
+ }
+ identity() {
+ this.set(1, 0, 0, 0, 1, 0, 0, 0, 1);
+ return this;
+ }
+ copy(m2) {
+ const te = this.elements;
+ const me = m2.elements;
+ te[0] = me[0];
+ te[1] = me[1];
+ te[2] = me[2];
+ te[3] = me[3];
+ te[4] = me[4];
+ te[5] = me[5];
+ te[6] = me[6];
+ te[7] = me[7];
+ te[8] = me[8];
+ return this;
+ }
+ extractBasis(xAxis, yAxis, zAxis) {
+ xAxis.setFromMatrix3Column(this, 0);
+ yAxis.setFromMatrix3Column(this, 1);
+ zAxis.setFromMatrix3Column(this, 2);
+ return this;
+ }
+ setFromMatrix4(m2) {
+ const me = m2.elements;
+ this.set(me[0], me[4], me[8], me[1], me[5], me[9], me[2], me[6], me[10]);
+ return this;
+ }
+ multiply(m2) {
+ return this.multiplyMatrices(this, m2);
+ }
+ premultiply(m2) {
+ return this.multiplyMatrices(m2, this);
+ }
+ multiplyMatrices(a2, b) {
+ const ae = a2.elements;
+ const be = b.elements;
+ const te = this.elements;
+ const a11 = ae[0], a12 = ae[3], a13 = ae[6];
+ const a21 = ae[1], a22 = ae[4], a23 = ae[7];
+ const a31 = ae[2], a32 = ae[5], a33 = ae[8];
+ const b11 = be[0], b12 = be[3], b13 = be[6];
+ const b21 = be[1], b22 = be[4], b23 = be[7];
+ const b31 = be[2], b32 = be[5], b33 = be[8];
+ te[0] = a11 * b11 + a12 * b21 + a13 * b31;
+ te[3] = a11 * b12 + a12 * b22 + a13 * b32;
+ te[6] = a11 * b13 + a12 * b23 + a13 * b33;
+ te[1] = a21 * b11 + a22 * b21 + a23 * b31;
+ te[4] = a21 * b12 + a22 * b22 + a23 * b32;
+ te[7] = a21 * b13 + a22 * b23 + a23 * b33;
+ te[2] = a31 * b11 + a32 * b21 + a33 * b31;
+ te[5] = a31 * b12 + a32 * b22 + a33 * b32;
+ te[8] = a31 * b13 + a32 * b23 + a33 * b33;
+ return this;
+ }
+ multiplyScalar(s) {
+ const te = this.elements;
+ te[0] *= s;
+ te[3] *= s;
+ te[6] *= s;
+ te[1] *= s;
+ te[4] *= s;
+ te[7] *= s;
+ te[2] *= s;
+ te[5] *= s;
+ te[8] *= s;
+ return this;
+ }
+ determinant() {
+ const te = this.elements;
+ const a2 = te[0], b = te[1], c2 = te[2], d = te[3], e = te[4], f = te[5], g = te[6], h = te[7], i = te[8];
+ return a2 * e * i - a2 * f * h - b * d * i + b * f * g + c2 * d * h - c2 * e * g;
+ }
+ invert() {
+ const te = this.elements, n11 = te[0], n21 = te[1], n31 = te[2], n12 = te[3], n22 = te[4], n32 = te[5], n13 = te[6], n23 = te[7], n33 = te[8], t11 = n33 * n22 - n32 * n23, t12 = n32 * n13 - n33 * n12, t13 = n23 * n12 - n22 * n13, det = n11 * t11 + n21 * t12 + n31 * t13;
+ if (det === 0)
+ return this.set(0, 0, 0, 0, 0, 0, 0, 0, 0);
+ const detInv = 1 / det;
+ te[0] = t11 * detInv;
+ te[1] = (n31 * n23 - n33 * n21) * detInv;
+ te[2] = (n32 * n21 - n31 * n22) * detInv;
+ te[3] = t12 * detInv;
+ te[4] = (n33 * n11 - n31 * n13) * detInv;
+ te[5] = (n31 * n12 - n32 * n11) * detInv;
+ te[6] = t13 * detInv;
+ te[7] = (n21 * n13 - n23 * n11) * detInv;
+ te[8] = (n22 * n11 - n21 * n12) * detInv;
+ return this;
+ }
+ transpose() {
+ let tmp2;
+ const m2 = this.elements;
+ tmp2 = m2[1];
+ m2[1] = m2[3];
+ m2[3] = tmp2;
+ tmp2 = m2[2];
+ m2[2] = m2[6];
+ m2[6] = tmp2;
+ tmp2 = m2[5];
+ m2[5] = m2[7];
+ m2[7] = tmp2;
+ return this;
+ }
+ getNormalMatrix(matrix4) {
+ return this.setFromMatrix4(matrix4).invert().transpose();
+ }
+ transposeIntoArray(r) {
+ const m2 = this.elements;
+ r[0] = m2[0];
+ r[1] = m2[3];
+ r[2] = m2[6];
+ r[3] = m2[1];
+ r[4] = m2[4];
+ r[5] = m2[7];
+ r[6] = m2[2];
+ r[7] = m2[5];
+ r[8] = m2[8];
+ return this;
+ }
+ setUvTransform(tx, ty, sx, sy, rotation, cx, cy) {
+ const c2 = Math.cos(rotation);
+ const s = Math.sin(rotation);
+ this.set(sx * c2, sx * s, -sx * (c2 * cx + s * cy) + cx + tx, -sy * s, sy * c2, -sy * (-s * cx + c2 * cy) + cy + ty, 0, 0, 1);
+ return this;
+ }
+ scale(sx, sy) {
+ const te = this.elements;
+ te[0] *= sx;
+ te[3] *= sx;
+ te[6] *= sx;
+ te[1] *= sy;
+ te[4] *= sy;
+ te[7] *= sy;
+ return this;
+ }
+ rotate(theta) {
+ const c2 = Math.cos(theta);
+ const s = Math.sin(theta);
+ const te = this.elements;
+ const a11 = te[0], a12 = te[3], a13 = te[6];
+ const a21 = te[1], a22 = te[4], a23 = te[7];
+ te[0] = c2 * a11 + s * a21;
+ te[3] = c2 * a12 + s * a22;
+ te[6] = c2 * a13 + s * a23;
+ te[1] = -s * a11 + c2 * a21;
+ te[4] = -s * a12 + c2 * a22;
+ te[7] = -s * a13 + c2 * a23;
+ return this;
+ }
+ translate(tx, ty) {
+ const te = this.elements;
+ te[0] += tx * te[2];
+ te[3] += tx * te[5];
+ te[6] += tx * te[8];
+ te[1] += ty * te[2];
+ te[4] += ty * te[5];
+ te[7] += ty * te[8];
+ return this;
+ }
+ equals(matrix) {
+ const te = this.elements;
+ const me = matrix.elements;
+ for (let i = 0; i < 9; i++) {
+ if (te[i] !== me[i])
+ return false;
+ }
+ return true;
+ }
+ fromArray(array2, offset = 0) {
+ for (let i = 0; i < 9; i++) {
+ this.elements[i] = array2[i + offset];
+ }
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ const te = this.elements;
+ array2[offset] = te[0];
+ array2[offset + 1] = te[1];
+ array2[offset + 2] = te[2];
+ array2[offset + 3] = te[3];
+ array2[offset + 4] = te[4];
+ array2[offset + 5] = te[5];
+ array2[offset + 6] = te[6];
+ array2[offset + 7] = te[7];
+ array2[offset + 8] = te[8];
+ return array2;
+ }
+ clone() {
+ return new this.constructor().fromArray(this.elements);
+ }
+ };
+ function arrayNeedsUint322(array2) {
+ for (let i = array2.length - 1; i >= 0; --i) {
+ if (array2[i] > 65535)
+ return true;
+ }
+ return false;
+ }
+ var TYPED_ARRAYS = {
+ Int8Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array
+ };
+ function getTypedArray(type2, buffer) {
+ return new TYPED_ARRAYS[type2](buffer);
+ }
+ function createElementNS2(name) {
+ return document.createElementNS("http://www.w3.org/1999/xhtml", name);
+ }
+ function SRGBToLinear2(c2) {
+ return c2 < 0.04045 ? c2 * 0.0773993808 : Math.pow(c2 * 0.9478672986 + 0.0521327014, 2.4);
+ }
+ function LinearToSRGB2(c2) {
+ return c2 < 31308e-7 ? c2 * 12.92 : 1.055 * Math.pow(c2, 0.41666) - 0.055;
+ }
+ var FN2 = {
+ [SRGBColorSpace2]: {
+ [LinearSRGBColorSpace2]: SRGBToLinear2
+ },
+ [LinearSRGBColorSpace2]: {
+ [SRGBColorSpace2]: LinearToSRGB2
+ }
+ };
+ var ColorManagement2 = {
+ legacyMode: true,
+ get workingColorSpace() {
+ return LinearSRGBColorSpace2;
+ },
+ set workingColorSpace(colorSpace) {
+ console.warn("THREE.ColorManagement: .workingColorSpace is readonly.");
+ },
+ convert: function(color2, sourceColorSpace, targetColorSpace) {
+ if (this.legacyMode || sourceColorSpace === targetColorSpace || !sourceColorSpace || !targetColorSpace) {
+ return color2;
+ }
+ if (FN2[sourceColorSpace] && FN2[sourceColorSpace][targetColorSpace] !== void 0) {
+ const fn = FN2[sourceColorSpace][targetColorSpace];
+ color2.r = fn(color2.r);
+ color2.g = fn(color2.g);
+ color2.b = fn(color2.b);
+ return color2;
+ }
+ throw new Error("Unsupported color space conversion.");
+ },
+ fromWorkingColorSpace: function(color2, targetColorSpace) {
+ return this.convert(color2, this.workingColorSpace, targetColorSpace);
+ },
+ toWorkingColorSpace: function(color2, sourceColorSpace) {
+ return this.convert(color2, sourceColorSpace, this.workingColorSpace);
+ }
+ };
+ var _colorKeywords2 = {
+ "aliceblue": 15792383,
+ "antiquewhite": 16444375,
+ "aqua": 65535,
+ "aquamarine": 8388564,
+ "azure": 15794175,
+ "beige": 16119260,
+ "bisque": 16770244,
+ "black": 0,
+ "blanchedalmond": 16772045,
+ "blue": 255,
+ "blueviolet": 9055202,
+ "brown": 10824234,
+ "burlywood": 14596231,
+ "cadetblue": 6266528,
+ "chartreuse": 8388352,
+ "chocolate": 13789470,
+ "coral": 16744272,
+ "cornflowerblue": 6591981,
+ "cornsilk": 16775388,
+ "crimson": 14423100,
+ "cyan": 65535,
+ "darkblue": 139,
+ "darkcyan": 35723,
+ "darkgoldenrod": 12092939,
+ "darkgray": 11119017,
+ "darkgreen": 25600,
+ "darkgrey": 11119017,
+ "darkkhaki": 12433259,
+ "darkmagenta": 9109643,
+ "darkolivegreen": 5597999,
+ "darkorange": 16747520,
+ "darkorchid": 10040012,
+ "darkred": 9109504,
+ "darksalmon": 15308410,
+ "darkseagreen": 9419919,
+ "darkslateblue": 4734347,
+ "darkslategray": 3100495,
+ "darkslategrey": 3100495,
+ "darkturquoise": 52945,
+ "darkviolet": 9699539,
+ "deeppink": 16716947,
+ "deepskyblue": 49151,
+ "dimgray": 6908265,
+ "dimgrey": 6908265,
+ "dodgerblue": 2003199,
+ "firebrick": 11674146,
+ "floralwhite": 16775920,
+ "forestgreen": 2263842,
+ "fuchsia": 16711935,
+ "gainsboro": 14474460,
+ "ghostwhite": 16316671,
+ "gold": 16766720,
+ "goldenrod": 14329120,
+ "gray": 8421504,
+ "green": 32768,
+ "greenyellow": 11403055,
+ "grey": 8421504,
+ "honeydew": 15794160,
+ "hotpink": 16738740,
+ "indianred": 13458524,
+ "indigo": 4915330,
+ "ivory": 16777200,
+ "khaki": 15787660,
+ "lavender": 15132410,
+ "lavenderblush": 16773365,
+ "lawngreen": 8190976,
+ "lemonchiffon": 16775885,
+ "lightblue": 11393254,
+ "lightcoral": 15761536,
+ "lightcyan": 14745599,
+ "lightgoldenrodyellow": 16448210,
+ "lightgray": 13882323,
+ "lightgreen": 9498256,
+ "lightgrey": 13882323,
+ "lightpink": 16758465,
+ "lightsalmon": 16752762,
+ "lightseagreen": 2142890,
+ "lightskyblue": 8900346,
+ "lightslategray": 7833753,
+ "lightslategrey": 7833753,
+ "lightsteelblue": 11584734,
+ "lightyellow": 16777184,
+ "lime": 65280,
+ "limegreen": 3329330,
+ "linen": 16445670,
+ "magenta": 16711935,
+ "maroon": 8388608,
+ "mediumaquamarine": 6737322,
+ "mediumblue": 205,
+ "mediumorchid": 12211667,
+ "mediumpurple": 9662683,
+ "mediumseagreen": 3978097,
+ "mediumslateblue": 8087790,
+ "mediumspringgreen": 64154,
+ "mediumturquoise": 4772300,
+ "mediumvioletred": 13047173,
+ "midnightblue": 1644912,
+ "mintcream": 16121850,
+ "mistyrose": 16770273,
+ "moccasin": 16770229,
+ "navajowhite": 16768685,
+ "navy": 128,
+ "oldlace": 16643558,
+ "olive": 8421376,
+ "olivedrab": 7048739,
+ "orange": 16753920,
+ "orangered": 16729344,
+ "orchid": 14315734,
+ "palegoldenrod": 15657130,
+ "palegreen": 10025880,
+ "paleturquoise": 11529966,
+ "palevioletred": 14381203,
+ "papayawhip": 16773077,
+ "peachpuff": 16767673,
+ "peru": 13468991,
+ "pink": 16761035,
+ "plum": 14524637,
+ "powderblue": 11591910,
+ "purple": 8388736,
+ "rebeccapurple": 6697881,
+ "red": 16711680,
+ "rosybrown": 12357519,
+ "royalblue": 4286945,
+ "saddlebrown": 9127187,
+ "salmon": 16416882,
+ "sandybrown": 16032864,
+ "seagreen": 3050327,
+ "seashell": 16774638,
+ "sienna": 10506797,
+ "silver": 12632256,
+ "skyblue": 8900331,
+ "slateblue": 6970061,
+ "slategray": 7372944,
+ "slategrey": 7372944,
+ "snow": 16775930,
+ "springgreen": 65407,
+ "steelblue": 4620980,
+ "tan": 13808780,
+ "teal": 32896,
+ "thistle": 14204888,
+ "tomato": 16737095,
+ "turquoise": 4251856,
+ "violet": 15631086,
+ "wheat": 16113331,
+ "white": 16777215,
+ "whitesmoke": 16119285,
+ "yellow": 16776960,
+ "yellowgreen": 10145074
+ };
+ var _rgb2 = {
+ r: 0,
+ g: 0,
+ b: 0
+ };
+ var _hslA2 = {
+ h: 0,
+ s: 0,
+ l: 0
+ };
+ var _hslB2 = {
+ h: 0,
+ s: 0,
+ l: 0
+ };
+ function hue2rgb2(p, q, t) {
+ if (t < 0)
+ t += 1;
+ if (t > 1)
+ t -= 1;
+ if (t < 1 / 6)
+ return p + (q - p) * 6 * t;
+ if (t < 1 / 2)
+ return q;
+ if (t < 2 / 3)
+ return p + (q - p) * 6 * (2 / 3 - t);
+ return p;
+ }
+ function toComponents2(source, target) {
+ target.r = source.r;
+ target.g = source.g;
+ target.b = source.b;
+ return target;
+ }
+ var Color3 = class {
+ constructor(r, g, b) {
+ this.isColor = true;
+ this.r = 1;
+ this.g = 1;
+ this.b = 1;
+ if (g === void 0 && b === void 0) {
+ return this.set(r);
+ }
+ return this.setRGB(r, g, b);
+ }
+ set(value) {
+ if (value && value.isColor) {
+ this.copy(value);
+ } else if (typeof value === "number") {
+ this.setHex(value);
+ } else if (typeof value === "string") {
+ this.setStyle(value);
+ }
+ return this;
+ }
+ setScalar(scalar) {
+ this.r = scalar;
+ this.g = scalar;
+ this.b = scalar;
+ return this;
+ }
+ setHex(hex2, colorSpace = SRGBColorSpace2) {
+ hex2 = Math.floor(hex2);
+ this.r = (hex2 >> 16 & 255) / 255;
+ this.g = (hex2 >> 8 & 255) / 255;
+ this.b = (hex2 & 255) / 255;
+ ColorManagement2.toWorkingColorSpace(this, colorSpace);
+ return this;
+ }
+ setRGB(r, g, b, colorSpace = LinearSRGBColorSpace2) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ ColorManagement2.toWorkingColorSpace(this, colorSpace);
+ return this;
+ }
+ setHSL(h, s, l, colorSpace = LinearSRGBColorSpace2) {
+ h = euclideanModulo2(h, 1);
+ s = clamp2(s, 0, 1);
+ l = clamp2(l, 0, 1);
+ if (s === 0) {
+ this.r = this.g = this.b = l;
+ } else {
+ const p = l <= 0.5 ? l * (1 + s) : l + s - l * s;
+ const q = 2 * l - p;
+ this.r = hue2rgb2(q, p, h + 1 / 3);
+ this.g = hue2rgb2(q, p, h);
+ this.b = hue2rgb2(q, p, h - 1 / 3);
+ }
+ ColorManagement2.toWorkingColorSpace(this, colorSpace);
+ return this;
+ }
+ setStyle(style, colorSpace = SRGBColorSpace2) {
+ function handleAlpha(string) {
+ if (string === void 0)
+ return;
+ if (parseFloat(string) < 1) {
+ console.warn("THREE.Color: Alpha component of " + style + " will be ignored.");
+ }
+ }
+ let m2;
+ if (m2 = /^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec(style)) {
+ let color2;
+ const name = m2[1];
+ const components = m2[2];
+ switch (name) {
+ case "rgb":
+ case "rgba":
+ if (color2 = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(components)) {
+ this.r = Math.min(255, parseInt(color2[1], 10)) / 255;
+ this.g = Math.min(255, parseInt(color2[2], 10)) / 255;
+ this.b = Math.min(255, parseInt(color2[3], 10)) / 255;
+ ColorManagement2.toWorkingColorSpace(this, colorSpace);
+ handleAlpha(color2[4]);
+ return this;
+ }
+ if (color2 = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(components)) {
+ this.r = Math.min(100, parseInt(color2[1], 10)) / 100;
+ this.g = Math.min(100, parseInt(color2[2], 10)) / 100;
+ this.b = Math.min(100, parseInt(color2[3], 10)) / 100;
+ ColorManagement2.toWorkingColorSpace(this, colorSpace);
+ handleAlpha(color2[4]);
+ return this;
+ }
+ break;
+ case "hsl":
+ case "hsla":
+ if (color2 = /^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(components)) {
+ const h = parseFloat(color2[1]) / 360;
+ const s = parseInt(color2[2], 10) / 100;
+ const l = parseInt(color2[3], 10) / 100;
+ handleAlpha(color2[4]);
+ return this.setHSL(h, s, l, colorSpace);
+ }
+ break;
+ }
+ } else if (m2 = /^\#([A-Fa-f\d]+)$/.exec(style)) {
+ const hex2 = m2[1];
+ const size = hex2.length;
+ if (size === 3) {
+ this.r = parseInt(hex2.charAt(0) + hex2.charAt(0), 16) / 255;
+ this.g = parseInt(hex2.charAt(1) + hex2.charAt(1), 16) / 255;
+ this.b = parseInt(hex2.charAt(2) + hex2.charAt(2), 16) / 255;
+ ColorManagement2.toWorkingColorSpace(this, colorSpace);
+ return this;
+ } else if (size === 6) {
+ this.r = parseInt(hex2.charAt(0) + hex2.charAt(1), 16) / 255;
+ this.g = parseInt(hex2.charAt(2) + hex2.charAt(3), 16) / 255;
+ this.b = parseInt(hex2.charAt(4) + hex2.charAt(5), 16) / 255;
+ ColorManagement2.toWorkingColorSpace(this, colorSpace);
+ return this;
+ }
+ }
+ if (style && style.length > 0) {
+ return this.setColorName(style, colorSpace);
+ }
+ return this;
+ }
+ setColorName(style, colorSpace = SRGBColorSpace2) {
+ const hex2 = _colorKeywords2[style.toLowerCase()];
+ if (hex2 !== void 0) {
+ this.setHex(hex2, colorSpace);
+ } else {
+ console.warn("THREE.Color: Unknown color " + style);
+ }
+ return this;
+ }
+ clone() {
+ return new this.constructor(this.r, this.g, this.b);
+ }
+ copy(color2) {
+ this.r = color2.r;
+ this.g = color2.g;
+ this.b = color2.b;
+ return this;
+ }
+ copySRGBToLinear(color2) {
+ this.r = SRGBToLinear2(color2.r);
+ this.g = SRGBToLinear2(color2.g);
+ this.b = SRGBToLinear2(color2.b);
+ return this;
+ }
+ copyLinearToSRGB(color2) {
+ this.r = LinearToSRGB2(color2.r);
+ this.g = LinearToSRGB2(color2.g);
+ this.b = LinearToSRGB2(color2.b);
+ return this;
+ }
+ convertSRGBToLinear() {
+ this.copySRGBToLinear(this);
+ return this;
+ }
+ convertLinearToSRGB() {
+ this.copyLinearToSRGB(this);
+ return this;
+ }
+ getHex(colorSpace = SRGBColorSpace2) {
+ ColorManagement2.fromWorkingColorSpace(toComponents2(this, _rgb2), colorSpace);
+ return clamp2(_rgb2.r * 255, 0, 255) << 16 ^ clamp2(_rgb2.g * 255, 0, 255) << 8 ^ clamp2(_rgb2.b * 255, 0, 255) << 0;
+ }
+ getHexString(colorSpace = SRGBColorSpace2) {
+ return ("000000" + this.getHex(colorSpace).toString(16)).slice(-6);
+ }
+ getHSL(target, colorSpace = LinearSRGBColorSpace2) {
+ ColorManagement2.fromWorkingColorSpace(toComponents2(this, _rgb2), colorSpace);
+ const r = _rgb2.r, g = _rgb2.g, b = _rgb2.b;
+ const max2 = Math.max(r, g, b);
+ const min2 = Math.min(r, g, b);
+ let hue, saturation;
+ const lightness = (min2 + max2) / 2;
+ if (min2 === max2) {
+ hue = 0;
+ saturation = 0;
+ } else {
+ const delta = max2 - min2;
+ saturation = lightness <= 0.5 ? delta / (max2 + min2) : delta / (2 - max2 - min2);
+ switch (max2) {
+ case r:
+ hue = (g - b) / delta + (g < b ? 6 : 0);
+ break;
+ case g:
+ hue = (b - r) / delta + 2;
+ break;
+ case b:
+ hue = (r - g) / delta + 4;
+ break;
+ }
+ hue /= 6;
+ }
+ target.h = hue;
+ target.s = saturation;
+ target.l = lightness;
+ return target;
+ }
+ getRGB(target, colorSpace = LinearSRGBColorSpace2) {
+ ColorManagement2.fromWorkingColorSpace(toComponents2(this, _rgb2), colorSpace);
+ target.r = _rgb2.r;
+ target.g = _rgb2.g;
+ target.b = _rgb2.b;
+ return target;
+ }
+ getStyle(colorSpace = SRGBColorSpace2) {
+ ColorManagement2.fromWorkingColorSpace(toComponents2(this, _rgb2), colorSpace);
+ if (colorSpace !== SRGBColorSpace2) {
+ return `color(${colorSpace} ${_rgb2.r} ${_rgb2.g} ${_rgb2.b})`;
+ }
+ return `rgb(${_rgb2.r * 255 | 0},${_rgb2.g * 255 | 0},${_rgb2.b * 255 | 0})`;
+ }
+ offsetHSL(h, s, l) {
+ this.getHSL(_hslA2);
+ _hslA2.h += h;
+ _hslA2.s += s;
+ _hslA2.l += l;
+ this.setHSL(_hslA2.h, _hslA2.s, _hslA2.l);
+ return this;
+ }
+ add(color2) {
+ this.r += color2.r;
+ this.g += color2.g;
+ this.b += color2.b;
+ return this;
+ }
+ addColors(color1, color2) {
+ this.r = color1.r + color2.r;
+ this.g = color1.g + color2.g;
+ this.b = color1.b + color2.b;
+ return this;
+ }
+ addScalar(s) {
+ this.r += s;
+ this.g += s;
+ this.b += s;
+ return this;
+ }
+ sub(color2) {
+ this.r = Math.max(0, this.r - color2.r);
+ this.g = Math.max(0, this.g - color2.g);
+ this.b = Math.max(0, this.b - color2.b);
+ return this;
+ }
+ multiply(color2) {
+ this.r *= color2.r;
+ this.g *= color2.g;
+ this.b *= color2.b;
+ return this;
+ }
+ multiplyScalar(s) {
+ this.r *= s;
+ this.g *= s;
+ this.b *= s;
+ return this;
+ }
+ lerp(color2, alpha) {
+ this.r += (color2.r - this.r) * alpha;
+ this.g += (color2.g - this.g) * alpha;
+ this.b += (color2.b - this.b) * alpha;
+ return this;
+ }
+ lerpColors(color1, color2, alpha) {
+ this.r = color1.r + (color2.r - color1.r) * alpha;
+ this.g = color1.g + (color2.g - color1.g) * alpha;
+ this.b = color1.b + (color2.b - color1.b) * alpha;
+ return this;
+ }
+ lerpHSL(color2, alpha) {
+ this.getHSL(_hslA2);
+ color2.getHSL(_hslB2);
+ const h = lerp2(_hslA2.h, _hslB2.h, alpha);
+ const s = lerp2(_hslA2.s, _hslB2.s, alpha);
+ const l = lerp2(_hslA2.l, _hslB2.l, alpha);
+ this.setHSL(h, s, l);
+ return this;
+ }
+ equals(c2) {
+ return c2.r === this.r && c2.g === this.g && c2.b === this.b;
+ }
+ fromArray(array2, offset = 0) {
+ this.r = array2[offset];
+ this.g = array2[offset + 1];
+ this.b = array2[offset + 2];
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ array2[offset] = this.r;
+ array2[offset + 1] = this.g;
+ array2[offset + 2] = this.b;
+ return array2;
+ }
+ fromBufferAttribute(attribute, index2) {
+ this.r = attribute.getX(index2);
+ this.g = attribute.getY(index2);
+ this.b = attribute.getZ(index2);
+ if (attribute.normalized === true) {
+ this.r /= 255;
+ this.g /= 255;
+ this.b /= 255;
+ }
+ return this;
+ }
+ toJSON() {
+ return this.getHex();
+ }
+ *[Symbol.iterator]() {
+ yield this.r;
+ yield this.g;
+ yield this.b;
+ }
+ };
+ Color3.NAMES = _colorKeywords2;
+ var _canvas2;
+ var ImageUtils2 = class {
+ static getDataURL(image) {
+ if (/^data:/i.test(image.src)) {
+ return image.src;
+ }
+ if (typeof HTMLCanvasElement == "undefined") {
+ return image.src;
+ }
+ let canvas;
+ if (image instanceof HTMLCanvasElement) {
+ canvas = image;
+ } else {
+ if (_canvas2 === void 0)
+ _canvas2 = createElementNS2("canvas");
+ _canvas2.width = image.width;
+ _canvas2.height = image.height;
+ const context = _canvas2.getContext("2d");
+ if (image instanceof ImageData) {
+ context.putImageData(image, 0, 0);
+ } else {
+ context.drawImage(image, 0, 0, image.width, image.height);
+ }
+ canvas = _canvas2;
+ }
+ if (canvas.width > 2048 || canvas.height > 2048) {
+ console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons", image);
+ return canvas.toDataURL("image/jpeg", 0.6);
+ } else {
+ return canvas.toDataURL("image/png");
+ }
+ }
+ static sRGBToLinear(image) {
+ if (typeof HTMLImageElement !== "undefined" && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== "undefined" && image instanceof HTMLCanvasElement || typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
+ const canvas = createElementNS2("canvas");
+ canvas.width = image.width;
+ canvas.height = image.height;
+ const context = canvas.getContext("2d");
+ context.drawImage(image, 0, 0, image.width, image.height);
+ const imageData = context.getImageData(0, 0, image.width, image.height);
+ const data = imageData.data;
+ for (let i = 0; i < data.length; i++) {
+ data[i] = SRGBToLinear2(data[i] / 255) * 255;
+ }
+ context.putImageData(imageData, 0, 0);
+ return canvas;
+ } else if (image.data) {
+ const data = image.data.slice(0);
+ for (let i = 0; i < data.length; i++) {
+ if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
+ data[i] = Math.floor(SRGBToLinear2(data[i] / 255) * 255);
+ } else {
+ data[i] = SRGBToLinear2(data[i]);
+ }
+ }
+ return {
+ data,
+ width: image.width,
+ height: image.height
+ };
+ } else {
+ console.warn("THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.");
+ return image;
+ }
+ }
+ };
+ var Source2 = class {
+ constructor(data = null) {
+ this.isSource = true;
+ this.uuid = generateUUID2();
+ this.data = data;
+ this.version = 0;
+ }
+ set needsUpdate(value) {
+ if (value === true)
+ this.version++;
+ }
+ toJSON(meta) {
+ const isRootObject = meta === void 0 || typeof meta === "string";
+ if (!isRootObject && meta.images[this.uuid] !== void 0) {
+ return meta.images[this.uuid];
+ }
+ const output = {
+ uuid: this.uuid,
+ url: ""
+ };
+ const data = this.data;
+ if (data !== null) {
+ let url;
+ if (Array.isArray(data)) {
+ url = [];
+ for (let i = 0, l = data.length; i < l; i++) {
+ if (data[i].isDataTexture) {
+ url.push(serializeImage2(data[i].image));
+ } else {
+ url.push(serializeImage2(data[i]));
+ }
+ }
+ } else {
+ url = serializeImage2(data);
+ }
+ output.url = url;
+ }
+ if (!isRootObject) {
+ meta.images[this.uuid] = output;
+ }
+ return output;
+ }
+ };
+ function serializeImage2(image) {
+ if (typeof HTMLImageElement !== "undefined" && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== "undefined" && image instanceof HTMLCanvasElement || typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
+ return ImageUtils2.getDataURL(image);
+ } else {
+ if (image.data) {
+ return {
+ data: Array.from(image.data),
+ width: image.width,
+ height: image.height,
+ type: image.data.constructor.name
+ };
+ } else {
+ console.warn("THREE.Texture: Unable to serialize Texture.");
+ return {};
+ }
+ }
+ }
+ var textureId2 = 0;
+ var Texture2 = class extends EventDispatcher2 {
+ constructor(image = Texture2.DEFAULT_IMAGE, mapping = Texture2.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping2, wrapT = ClampToEdgeWrapping2, magFilter = LinearFilter2, minFilter = LinearMipmapLinearFilter2, format2 = RGBAFormat2, type2 = UnsignedByteType2, anisotropy = 1, encoding = LinearEncoding2) {
+ super();
+ this.isTexture = true;
+ Object.defineProperty(this, "id", {
+ value: textureId2++
+ });
+ this.uuid = generateUUID2();
+ this.name = "";
+ this.source = new Source2(image);
+ this.mipmaps = [];
+ this.mapping = mapping;
+ this.wrapS = wrapS;
+ this.wrapT = wrapT;
+ this.magFilter = magFilter;
+ this.minFilter = minFilter;
+ this.anisotropy = anisotropy;
+ this.format = format2;
+ this.internalFormat = null;
+ this.type = type2;
+ this.offset = new Vector22(0, 0);
+ this.repeat = new Vector22(1, 1);
+ this.center = new Vector22(0, 0);
+ this.rotation = 0;
+ this.matrixAutoUpdate = true;
+ this.matrix = new Matrix32();
+ this.generateMipmaps = true;
+ this.premultiplyAlpha = false;
+ this.flipY = true;
+ this.unpackAlignment = 4;
+ this.encoding = encoding;
+ this.userData = {};
+ this.version = 0;
+ this.onUpdate = null;
+ this.isRenderTargetTexture = false;
+ this.needsPMREMUpdate = false;
+ }
+ get image() {
+ return this.source.data;
+ }
+ set image(value) {
+ this.source.data = value;
+ }
+ updateMatrix() {
+ this.matrix.setUvTransform(this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y);
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ copy(source) {
+ this.name = source.name;
+ this.source = source.source;
+ this.mipmaps = source.mipmaps.slice(0);
+ this.mapping = source.mapping;
+ this.wrapS = source.wrapS;
+ this.wrapT = source.wrapT;
+ this.magFilter = source.magFilter;
+ this.minFilter = source.minFilter;
+ this.anisotropy = source.anisotropy;
+ this.format = source.format;
+ this.internalFormat = source.internalFormat;
+ this.type = source.type;
+ this.offset.copy(source.offset);
+ this.repeat.copy(source.repeat);
+ this.center.copy(source.center);
+ this.rotation = source.rotation;
+ this.matrixAutoUpdate = source.matrixAutoUpdate;
+ this.matrix.copy(source.matrix);
+ this.generateMipmaps = source.generateMipmaps;
+ this.premultiplyAlpha = source.premultiplyAlpha;
+ this.flipY = source.flipY;
+ this.unpackAlignment = source.unpackAlignment;
+ this.encoding = source.encoding;
+ this.userData = JSON.parse(JSON.stringify(source.userData));
+ this.needsUpdate = true;
+ return this;
+ }
+ toJSON(meta) {
+ const isRootObject = meta === void 0 || typeof meta === "string";
+ if (!isRootObject && meta.textures[this.uuid] !== void 0) {
+ return meta.textures[this.uuid];
+ }
+ const output = {
+ metadata: {
+ version: 4.5,
+ type: "Texture",
+ generator: "Texture.toJSON"
+ },
+ uuid: this.uuid,
+ name: this.name,
+ image: this.source.toJSON(meta).uuid,
+ mapping: this.mapping,
+ repeat: [this.repeat.x, this.repeat.y],
+ offset: [this.offset.x, this.offset.y],
+ center: [this.center.x, this.center.y],
+ rotation: this.rotation,
+ wrap: [this.wrapS, this.wrapT],
+ format: this.format,
+ type: this.type,
+ encoding: this.encoding,
+ minFilter: this.minFilter,
+ magFilter: this.magFilter,
+ anisotropy: this.anisotropy,
+ flipY: this.flipY,
+ premultiplyAlpha: this.premultiplyAlpha,
+ unpackAlignment: this.unpackAlignment
+ };
+ if (JSON.stringify(this.userData) !== "{}")
+ output.userData = this.userData;
+ if (!isRootObject) {
+ meta.textures[this.uuid] = output;
+ }
+ return output;
+ }
+ dispose() {
+ this.dispatchEvent({
+ type: "dispose"
+ });
+ }
+ transformUv(uv) {
+ if (this.mapping !== UVMapping2)
+ return uv;
+ uv.applyMatrix3(this.matrix);
+ if (uv.x < 0 || uv.x > 1) {
+ switch (this.wrapS) {
+ case RepeatWrapping2:
+ uv.x = uv.x - Math.floor(uv.x);
+ break;
+ case ClampToEdgeWrapping2:
+ uv.x = uv.x < 0 ? 0 : 1;
+ break;
+ case MirroredRepeatWrapping2:
+ if (Math.abs(Math.floor(uv.x) % 2) === 1) {
+ uv.x = Math.ceil(uv.x) - uv.x;
+ } else {
+ uv.x = uv.x - Math.floor(uv.x);
+ }
+ break;
+ }
+ }
+ if (uv.y < 0 || uv.y > 1) {
+ switch (this.wrapT) {
+ case RepeatWrapping2:
+ uv.y = uv.y - Math.floor(uv.y);
+ break;
+ case ClampToEdgeWrapping2:
+ uv.y = uv.y < 0 ? 0 : 1;
+ break;
+ case MirroredRepeatWrapping2:
+ if (Math.abs(Math.floor(uv.y) % 2) === 1) {
+ uv.y = Math.ceil(uv.y) - uv.y;
+ } else {
+ uv.y = uv.y - Math.floor(uv.y);
+ }
+ break;
+ }
+ }
+ if (this.flipY) {
+ uv.y = 1 - uv.y;
+ }
+ return uv;
+ }
+ set needsUpdate(value) {
+ if (value === true) {
+ this.version++;
+ this.source.needsUpdate = true;
+ }
+ }
+ };
+ Texture2.DEFAULT_IMAGE = null;
+ Texture2.DEFAULT_MAPPING = UVMapping2;
+ var Vector42 = class {
+ constructor(x2 = 0, y2 = 0, z = 0, w = 1) {
+ Vector42.prototype.isVector4 = true;
+ this.x = x2;
+ this.y = y2;
+ this.z = z;
+ this.w = w;
+ }
+ get width() {
+ return this.z;
+ }
+ set width(value) {
+ this.z = value;
+ }
+ get height() {
+ return this.w;
+ }
+ set height(value) {
+ this.w = value;
+ }
+ set(x2, y2, z, w) {
+ this.x = x2;
+ this.y = y2;
+ this.z = z;
+ this.w = w;
+ return this;
+ }
+ setScalar(scalar) {
+ this.x = scalar;
+ this.y = scalar;
+ this.z = scalar;
+ this.w = scalar;
+ return this;
+ }
+ setX(x2) {
+ this.x = x2;
+ return this;
+ }
+ setY(y2) {
+ this.y = y2;
+ return this;
+ }
+ setZ(z) {
+ this.z = z;
+ return this;
+ }
+ setW(w) {
+ this.w = w;
+ return this;
+ }
+ setComponent(index2, value) {
+ switch (index2) {
+ case 0:
+ this.x = value;
+ break;
+ case 1:
+ this.y = value;
+ break;
+ case 2:
+ this.z = value;
+ break;
+ case 3:
+ this.w = value;
+ break;
+ default:
+ throw new Error("index is out of range: " + index2);
+ }
+ return this;
+ }
+ getComponent(index2) {
+ switch (index2) {
+ case 0:
+ return this.x;
+ case 1:
+ return this.y;
+ case 2:
+ return this.z;
+ case 3:
+ return this.w;
+ default:
+ throw new Error("index is out of range: " + index2);
+ }
+ }
+ clone() {
+ return new this.constructor(this.x, this.y, this.z, this.w);
+ }
+ copy(v) {
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ this.w = v.w !== void 0 ? v.w : 1;
+ return this;
+ }
+ add(v) {
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+ this.w += v.w;
+ return this;
+ }
+ addScalar(s) {
+ this.x += s;
+ this.y += s;
+ this.z += s;
+ this.w += s;
+ return this;
+ }
+ addVectors(a2, b) {
+ this.x = a2.x + b.x;
+ this.y = a2.y + b.y;
+ this.z = a2.z + b.z;
+ this.w = a2.w + b.w;
+ return this;
+ }
+ addScaledVector(v, s) {
+ this.x += v.x * s;
+ this.y += v.y * s;
+ this.z += v.z * s;
+ this.w += v.w * s;
+ return this;
+ }
+ sub(v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+ this.w -= v.w;
+ return this;
+ }
+ subScalar(s) {
+ this.x -= s;
+ this.y -= s;
+ this.z -= s;
+ this.w -= s;
+ return this;
+ }
+ subVectors(a2, b) {
+ this.x = a2.x - b.x;
+ this.y = a2.y - b.y;
+ this.z = a2.z - b.z;
+ this.w = a2.w - b.w;
+ return this;
+ }
+ multiply(v) {
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+ this.w *= v.w;
+ return this;
+ }
+ multiplyScalar(scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ this.w *= scalar;
+ return this;
+ }
+ applyMatrix4(m2) {
+ const x2 = this.x, y2 = this.y, z = this.z, w = this.w;
+ const e = m2.elements;
+ this.x = e[0] * x2 + e[4] * y2 + e[8] * z + e[12] * w;
+ this.y = e[1] * x2 + e[5] * y2 + e[9] * z + e[13] * w;
+ this.z = e[2] * x2 + e[6] * y2 + e[10] * z + e[14] * w;
+ this.w = e[3] * x2 + e[7] * y2 + e[11] * z + e[15] * w;
+ return this;
+ }
+ divideScalar(scalar) {
+ return this.multiplyScalar(1 / scalar);
+ }
+ setAxisAngleFromQuaternion(q) {
+ this.w = 2 * Math.acos(q.w);
+ const s = Math.sqrt(1 - q.w * q.w);
+ if (s < 1e-4) {
+ this.x = 1;
+ this.y = 0;
+ this.z = 0;
+ } else {
+ this.x = q.x / s;
+ this.y = q.y / s;
+ this.z = q.z / s;
+ }
+ return this;
+ }
+ setAxisAngleFromRotationMatrix(m2) {
+ let angle, x2, y2, z;
+ const epsilon = 0.01, epsilon2 = 0.1, te = m2.elements, m11 = te[0], m12 = te[4], m13 = te[8], m21 = te[1], m22 = te[5], m23 = te[9], m31 = te[2], m32 = te[6], m33 = te[10];
+ if (Math.abs(m12 - m21) < epsilon && Math.abs(m13 - m31) < epsilon && Math.abs(m23 - m32) < epsilon) {
+ if (Math.abs(m12 + m21) < epsilon2 && Math.abs(m13 + m31) < epsilon2 && Math.abs(m23 + m32) < epsilon2 && Math.abs(m11 + m22 + m33 - 3) < epsilon2) {
+ this.set(1, 0, 0, 0);
+ return this;
+ }
+ angle = Math.PI;
+ const xx = (m11 + 1) / 2;
+ const yy = (m22 + 1) / 2;
+ const zz = (m33 + 1) / 2;
+ const xy = (m12 + m21) / 4;
+ const xz = (m13 + m31) / 4;
+ const yz = (m23 + m32) / 4;
+ if (xx > yy && xx > zz) {
+ if (xx < epsilon) {
+ x2 = 0;
+ y2 = 0.707106781;
+ z = 0.707106781;
+ } else {
+ x2 = Math.sqrt(xx);
+ y2 = xy / x2;
+ z = xz / x2;
+ }
+ } else if (yy > zz) {
+ if (yy < epsilon) {
+ x2 = 0.707106781;
+ y2 = 0;
+ z = 0.707106781;
+ } else {
+ y2 = Math.sqrt(yy);
+ x2 = xy / y2;
+ z = yz / y2;
+ }
+ } else {
+ if (zz < epsilon) {
+ x2 = 0.707106781;
+ y2 = 0.707106781;
+ z = 0;
+ } else {
+ z = Math.sqrt(zz);
+ x2 = xz / z;
+ y2 = yz / z;
+ }
+ }
+ this.set(x2, y2, z, angle);
+ return this;
+ }
+ let s = Math.sqrt((m32 - m23) * (m32 - m23) + (m13 - m31) * (m13 - m31) + (m21 - m12) * (m21 - m12));
+ if (Math.abs(s) < 1e-3)
+ s = 1;
+ this.x = (m32 - m23) / s;
+ this.y = (m13 - m31) / s;
+ this.z = (m21 - m12) / s;
+ this.w = Math.acos((m11 + m22 + m33 - 1) / 2);
+ return this;
+ }
+ min(v) {
+ this.x = Math.min(this.x, v.x);
+ this.y = Math.min(this.y, v.y);
+ this.z = Math.min(this.z, v.z);
+ this.w = Math.min(this.w, v.w);
+ return this;
+ }
+ max(v) {
+ this.x = Math.max(this.x, v.x);
+ this.y = Math.max(this.y, v.y);
+ this.z = Math.max(this.z, v.z);
+ this.w = Math.max(this.w, v.w);
+ return this;
+ }
+ clamp(min2, max2) {
+ this.x = Math.max(min2.x, Math.min(max2.x, this.x));
+ this.y = Math.max(min2.y, Math.min(max2.y, this.y));
+ this.z = Math.max(min2.z, Math.min(max2.z, this.z));
+ this.w = Math.max(min2.w, Math.min(max2.w, this.w));
+ return this;
+ }
+ clampScalar(minVal, maxVal) {
+ this.x = Math.max(minVal, Math.min(maxVal, this.x));
+ this.y = Math.max(minVal, Math.min(maxVal, this.y));
+ this.z = Math.max(minVal, Math.min(maxVal, this.z));
+ this.w = Math.max(minVal, Math.min(maxVal, this.w));
+ return this;
+ }
+ clampLength(min2, max2) {
+ const length = this.length();
+ return this.divideScalar(length || 1).multiplyScalar(Math.max(min2, Math.min(max2, length)));
+ }
+ floor() {
+ this.x = Math.floor(this.x);
+ this.y = Math.floor(this.y);
+ this.z = Math.floor(this.z);
+ this.w = Math.floor(this.w);
+ return this;
+ }
+ ceil() {
+ this.x = Math.ceil(this.x);
+ this.y = Math.ceil(this.y);
+ this.z = Math.ceil(this.z);
+ this.w = Math.ceil(this.w);
+ return this;
+ }
+ round() {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ this.z = Math.round(this.z);
+ this.w = Math.round(this.w);
+ return this;
+ }
+ roundToZero() {
+ this.x = this.x < 0 ? Math.ceil(this.x) : Math.floor(this.x);
+ this.y = this.y < 0 ? Math.ceil(this.y) : Math.floor(this.y);
+ this.z = this.z < 0 ? Math.ceil(this.z) : Math.floor(this.z);
+ this.w = this.w < 0 ? Math.ceil(this.w) : Math.floor(this.w);
+ return this;
+ }
+ negate() {
+ this.x = -this.x;
+ this.y = -this.y;
+ this.z = -this.z;
+ this.w = -this.w;
+ return this;
+ }
+ dot(v) {
+ return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
+ }
+ lengthSq() {
+ return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+ }
+ length() {
+ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
+ }
+ manhattanLength() {
+ return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z) + Math.abs(this.w);
+ }
+ normalize() {
+ return this.divideScalar(this.length() || 1);
+ }
+ setLength(length) {
+ return this.normalize().multiplyScalar(length);
+ }
+ lerp(v, alpha) {
+ this.x += (v.x - this.x) * alpha;
+ this.y += (v.y - this.y) * alpha;
+ this.z += (v.z - this.z) * alpha;
+ this.w += (v.w - this.w) * alpha;
+ return this;
+ }
+ lerpVectors(v1, v2, alpha) {
+ this.x = v1.x + (v2.x - v1.x) * alpha;
+ this.y = v1.y + (v2.y - v1.y) * alpha;
+ this.z = v1.z + (v2.z - v1.z) * alpha;
+ this.w = v1.w + (v2.w - v1.w) * alpha;
+ return this;
+ }
+ equals(v) {
+ return v.x === this.x && v.y === this.y && v.z === this.z && v.w === this.w;
+ }
+ fromArray(array2, offset = 0) {
+ this.x = array2[offset];
+ this.y = array2[offset + 1];
+ this.z = array2[offset + 2];
+ this.w = array2[offset + 3];
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ array2[offset] = this.x;
+ array2[offset + 1] = this.y;
+ array2[offset + 2] = this.z;
+ array2[offset + 3] = this.w;
+ return array2;
+ }
+ fromBufferAttribute(attribute, index2) {
+ this.x = attribute.getX(index2);
+ this.y = attribute.getY(index2);
+ this.z = attribute.getZ(index2);
+ this.w = attribute.getW(index2);
+ return this;
+ }
+ random() {
+ this.x = Math.random();
+ this.y = Math.random();
+ this.z = Math.random();
+ this.w = Math.random();
+ return this;
+ }
+ *[Symbol.iterator]() {
+ yield this.x;
+ yield this.y;
+ yield this.z;
+ yield this.w;
+ }
+ };
+ var WebGLRenderTarget2 = class extends EventDispatcher2 {
+ constructor(width, height, options = {}) {
+ super();
+ this.isWebGLRenderTarget = true;
+ this.width = width;
+ this.height = height;
+ this.depth = 1;
+ this.scissor = new Vector42(0, 0, width, height);
+ this.scissorTest = false;
+ this.viewport = new Vector42(0, 0, width, height);
+ const image = {
+ width,
+ height,
+ depth: 1
+ };
+ this.texture = new Texture2(image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding);
+ this.texture.isRenderTargetTexture = true;
+ this.texture.flipY = false;
+ this.texture.generateMipmaps = options.generateMipmaps !== void 0 ? options.generateMipmaps : false;
+ this.texture.internalFormat = options.internalFormat !== void 0 ? options.internalFormat : null;
+ this.texture.minFilter = options.minFilter !== void 0 ? options.minFilter : LinearFilter2;
+ this.depthBuffer = options.depthBuffer !== void 0 ? options.depthBuffer : true;
+ this.stencilBuffer = options.stencilBuffer !== void 0 ? options.stencilBuffer : false;
+ this.depthTexture = options.depthTexture !== void 0 ? options.depthTexture : null;
+ this.samples = options.samples !== void 0 ? options.samples : 0;
+ }
+ setSize(width, height, depth = 1) {
+ if (this.width !== width || this.height !== height || this.depth !== depth) {
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+ this.texture.image.width = width;
+ this.texture.image.height = height;
+ this.texture.image.depth = depth;
+ this.dispose();
+ }
+ this.viewport.set(0, 0, width, height);
+ this.scissor.set(0, 0, width, height);
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ copy(source) {
+ this.width = source.width;
+ this.height = source.height;
+ this.depth = source.depth;
+ this.viewport.copy(source.viewport);
+ this.texture = source.texture.clone();
+ this.texture.isRenderTargetTexture = true;
+ const image = Object.assign({}, source.texture.image);
+ this.texture.source = new Source2(image);
+ this.depthBuffer = source.depthBuffer;
+ this.stencilBuffer = source.stencilBuffer;
+ if (source.depthTexture !== null)
+ this.depthTexture = source.depthTexture.clone();
+ this.samples = source.samples;
+ return this;
+ }
+ dispose() {
+ this.dispatchEvent({
+ type: "dispose"
+ });
+ }
+ };
+ var DataArrayTexture2 = class extends Texture2 {
+ constructor(data = null, width = 1, height = 1, depth = 1) {
+ super(null);
+ this.isDataArrayTexture = true;
+ this.image = {
+ data,
+ width,
+ height,
+ depth
+ };
+ this.magFilter = NearestFilter2;
+ this.minFilter = NearestFilter2;
+ this.wrapR = ClampToEdgeWrapping2;
+ this.generateMipmaps = false;
+ this.flipY = false;
+ this.unpackAlignment = 1;
+ }
+ };
+ var WebGLArrayRenderTarget = class extends WebGLRenderTarget2 {
+ constructor(width, height, depth) {
+ super(width, height);
+ this.isWebGLArrayRenderTarget = true;
+ this.depth = depth;
+ this.texture = new DataArrayTexture2(null, width, height, depth);
+ this.texture.isRenderTargetTexture = true;
+ }
+ };
+ var Data3DTexture2 = class extends Texture2 {
+ constructor(data = null, width = 1, height = 1, depth = 1) {
+ super(null);
+ this.isData3DTexture = true;
+ this.image = {
+ data,
+ width,
+ height,
+ depth
+ };
+ this.magFilter = NearestFilter2;
+ this.minFilter = NearestFilter2;
+ this.wrapR = ClampToEdgeWrapping2;
+ this.generateMipmaps = false;
+ this.flipY = false;
+ this.unpackAlignment = 1;
+ }
+ };
+ var WebGL3DRenderTarget = class extends WebGLRenderTarget2 {
+ constructor(width, height, depth) {
+ super(width, height);
+ this.isWebGL3DRenderTarget = true;
+ this.depth = depth;
+ this.texture = new Data3DTexture2(null, width, height, depth);
+ this.texture.isRenderTargetTexture = true;
+ }
+ };
+ var WebGLMultipleRenderTargets = class extends WebGLRenderTarget2 {
+ constructor(width, height, count, options = {}) {
+ super(width, height, options);
+ this.isWebGLMultipleRenderTargets = true;
+ const texture = this.texture;
+ this.texture = [];
+ for (let i = 0; i < count; i++) {
+ this.texture[i] = texture.clone();
+ this.texture[i].isRenderTargetTexture = true;
+ }
+ }
+ setSize(width, height, depth = 1) {
+ if (this.width !== width || this.height !== height || this.depth !== depth) {
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+ for (let i = 0, il = this.texture.length; i < il; i++) {
+ this.texture[i].image.width = width;
+ this.texture[i].image.height = height;
+ this.texture[i].image.depth = depth;
+ }
+ this.dispose();
+ }
+ this.viewport.set(0, 0, width, height);
+ this.scissor.set(0, 0, width, height);
+ return this;
+ }
+ copy(source) {
+ this.dispose();
+ this.width = source.width;
+ this.height = source.height;
+ this.depth = source.depth;
+ this.viewport.set(0, 0, this.width, this.height);
+ this.scissor.set(0, 0, this.width, this.height);
+ this.depthBuffer = source.depthBuffer;
+ this.stencilBuffer = source.stencilBuffer;
+ if (source.depthTexture !== null)
+ this.depthTexture = source.depthTexture.clone();
+ this.texture.length = 0;
+ for (let i = 0, il = source.texture.length; i < il; i++) {
+ this.texture[i] = source.texture[i].clone();
+ this.texture[i].isRenderTargetTexture = true;
+ }
+ return this;
+ }
+ };
+ var Quaternion2 = class {
+ constructor(x2 = 0, y2 = 0, z = 0, w = 1) {
+ this.isQuaternion = true;
+ this._x = x2;
+ this._y = y2;
+ this._z = z;
+ this._w = w;
+ }
+ static slerpFlat(dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t) {
+ let x0 = src0[srcOffset0 + 0], y0 = src0[srcOffset0 + 1], z0 = src0[srcOffset0 + 2], w0 = src0[srcOffset0 + 3];
+ const x1 = src1[srcOffset1 + 0], y1 = src1[srcOffset1 + 1], z1 = src1[srcOffset1 + 2], w1 = src1[srcOffset1 + 3];
+ if (t === 0) {
+ dst[dstOffset + 0] = x0;
+ dst[dstOffset + 1] = y0;
+ dst[dstOffset + 2] = z0;
+ dst[dstOffset + 3] = w0;
+ return;
+ }
+ if (t === 1) {
+ dst[dstOffset + 0] = x1;
+ dst[dstOffset + 1] = y1;
+ dst[dstOffset + 2] = z1;
+ dst[dstOffset + 3] = w1;
+ return;
+ }
+ if (w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1) {
+ let s = 1 - t;
+ const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, dir = cos >= 0 ? 1 : -1, sqrSin = 1 - cos * cos;
+ if (sqrSin > Number.EPSILON) {
+ const sin = Math.sqrt(sqrSin), len = Math.atan2(sin, cos * dir);
+ s = Math.sin(s * len) / sin;
+ t = Math.sin(t * len) / sin;
+ }
+ const tDir = t * dir;
+ x0 = x0 * s + x1 * tDir;
+ y0 = y0 * s + y1 * tDir;
+ z0 = z0 * s + z1 * tDir;
+ w0 = w0 * s + w1 * tDir;
+ if (s === 1 - t) {
+ const f = 1 / Math.sqrt(x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0);
+ x0 *= f;
+ y0 *= f;
+ z0 *= f;
+ w0 *= f;
+ }
+ }
+ dst[dstOffset] = x0;
+ dst[dstOffset + 1] = y0;
+ dst[dstOffset + 2] = z0;
+ dst[dstOffset + 3] = w0;
+ }
+ static multiplyQuaternionsFlat(dst, dstOffset, src0, srcOffset0, src1, srcOffset1) {
+ const x0 = src0[srcOffset0];
+ const y0 = src0[srcOffset0 + 1];
+ const z0 = src0[srcOffset0 + 2];
+ const w0 = src0[srcOffset0 + 3];
+ const x1 = src1[srcOffset1];
+ const y1 = src1[srcOffset1 + 1];
+ const z1 = src1[srcOffset1 + 2];
+ const w1 = src1[srcOffset1 + 3];
+ dst[dstOffset] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1;
+ dst[dstOffset + 1] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1;
+ dst[dstOffset + 2] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1;
+ dst[dstOffset + 3] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
+ return dst;
+ }
+ get x() {
+ return this._x;
+ }
+ set x(value) {
+ this._x = value;
+ this._onChangeCallback();
+ }
+ get y() {
+ return this._y;
+ }
+ set y(value) {
+ this._y = value;
+ this._onChangeCallback();
+ }
+ get z() {
+ return this._z;
+ }
+ set z(value) {
+ this._z = value;
+ this._onChangeCallback();
+ }
+ get w() {
+ return this._w;
+ }
+ set w(value) {
+ this._w = value;
+ this._onChangeCallback();
+ }
+ set(x2, y2, z, w) {
+ this._x = x2;
+ this._y = y2;
+ this._z = z;
+ this._w = w;
+ this._onChangeCallback();
+ return this;
+ }
+ clone() {
+ return new this.constructor(this._x, this._y, this._z, this._w);
+ }
+ copy(quaternion) {
+ this._x = quaternion.x;
+ this._y = quaternion.y;
+ this._z = quaternion.z;
+ this._w = quaternion.w;
+ this._onChangeCallback();
+ return this;
+ }
+ setFromEuler(euler, update) {
+ if (!(euler && euler.isEuler)) {
+ throw new Error("THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.");
+ }
+ const x2 = euler._x, y2 = euler._y, z = euler._z, order = euler._order;
+ const cos = Math.cos;
+ const sin = Math.sin;
+ const c1 = cos(x2 / 2);
+ const c2 = cos(y2 / 2);
+ const c3 = cos(z / 2);
+ const s1 = sin(x2 / 2);
+ const s2 = sin(y2 / 2);
+ const s3 = sin(z / 2);
+ switch (order) {
+ case "XYZ":
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+ case "YXZ":
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+ case "ZXY":
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+ case "ZYX":
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+ case "YZX":
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+ case "XZY":
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+ default:
+ console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: " + order);
+ }
+ if (update !== false)
+ this._onChangeCallback();
+ return this;
+ }
+ setFromAxisAngle(axis, angle) {
+ const halfAngle = angle / 2, s = Math.sin(halfAngle);
+ this._x = axis.x * s;
+ this._y = axis.y * s;
+ this._z = axis.z * s;
+ this._w = Math.cos(halfAngle);
+ this._onChangeCallback();
+ return this;
+ }
+ setFromRotationMatrix(m2) {
+ const te = m2.elements, m11 = te[0], m12 = te[4], m13 = te[8], m21 = te[1], m22 = te[5], m23 = te[9], m31 = te[2], m32 = te[6], m33 = te[10], trace = m11 + m22 + m33;
+ if (trace > 0) {
+ const s = 0.5 / Math.sqrt(trace + 1);
+ this._w = 0.25 / s;
+ this._x = (m32 - m23) * s;
+ this._y = (m13 - m31) * s;
+ this._z = (m21 - m12) * s;
+ } else if (m11 > m22 && m11 > m33) {
+ const s = 2 * Math.sqrt(1 + m11 - m22 - m33);
+ this._w = (m32 - m23) / s;
+ this._x = 0.25 * s;
+ this._y = (m12 + m21) / s;
+ this._z = (m13 + m31) / s;
+ } else if (m22 > m33) {
+ const s = 2 * Math.sqrt(1 + m22 - m11 - m33);
+ this._w = (m13 - m31) / s;
+ this._x = (m12 + m21) / s;
+ this._y = 0.25 * s;
+ this._z = (m23 + m32) / s;
+ } else {
+ const s = 2 * Math.sqrt(1 + m33 - m11 - m22);
+ this._w = (m21 - m12) / s;
+ this._x = (m13 + m31) / s;
+ this._y = (m23 + m32) / s;
+ this._z = 0.25 * s;
+ }
+ this._onChangeCallback();
+ return this;
+ }
+ setFromUnitVectors(vFrom, vTo) {
+ let r = vFrom.dot(vTo) + 1;
+ if (r < Number.EPSILON) {
+ r = 0;
+ if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) {
+ this._x = -vFrom.y;
+ this._y = vFrom.x;
+ this._z = 0;
+ this._w = r;
+ } else {
+ this._x = 0;
+ this._y = -vFrom.z;
+ this._z = vFrom.y;
+ this._w = r;
+ }
+ } else {
+ this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
+ this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
+ this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
+ this._w = r;
+ }
+ return this.normalize();
+ }
+ angleTo(q) {
+ return 2 * Math.acos(Math.abs(clamp2(this.dot(q), -1, 1)));
+ }
+ rotateTowards(q, step) {
+ const angle = this.angleTo(q);
+ if (angle === 0)
+ return this;
+ const t = Math.min(1, step / angle);
+ this.slerp(q, t);
+ return this;
+ }
+ identity() {
+ return this.set(0, 0, 0, 1);
+ }
+ invert() {
+ return this.conjugate();
+ }
+ conjugate() {
+ this._x *= -1;
+ this._y *= -1;
+ this._z *= -1;
+ this._onChangeCallback();
+ return this;
+ }
+ dot(v) {
+ return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
+ }
+ lengthSq() {
+ return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+ }
+ length() {
+ return Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w);
+ }
+ normalize() {
+ let l = this.length();
+ if (l === 0) {
+ this._x = 0;
+ this._y = 0;
+ this._z = 0;
+ this._w = 1;
+ } else {
+ l = 1 / l;
+ this._x = this._x * l;
+ this._y = this._y * l;
+ this._z = this._z * l;
+ this._w = this._w * l;
+ }
+ this._onChangeCallback();
+ return this;
+ }
+ multiply(q) {
+ return this.multiplyQuaternions(this, q);
+ }
+ premultiply(q) {
+ return this.multiplyQuaternions(q, this);
+ }
+ multiplyQuaternions(a2, b) {
+ const qax = a2._x, qay = a2._y, qaz = a2._z, qaw = a2._w;
+ const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
+ this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+ this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+ this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+ this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+ this._onChangeCallback();
+ return this;
+ }
+ slerp(qb, t) {
+ if (t === 0)
+ return this;
+ if (t === 1)
+ return this.copy(qb);
+ const x2 = this._x, y2 = this._y, z = this._z, w = this._w;
+ let cosHalfTheta = w * qb._w + x2 * qb._x + y2 * qb._y + z * qb._z;
+ if (cosHalfTheta < 0) {
+ this._w = -qb._w;
+ this._x = -qb._x;
+ this._y = -qb._y;
+ this._z = -qb._z;
+ cosHalfTheta = -cosHalfTheta;
+ } else {
+ this.copy(qb);
+ }
+ if (cosHalfTheta >= 1) {
+ this._w = w;
+ this._x = x2;
+ this._y = y2;
+ this._z = z;
+ return this;
+ }
+ const sqrSinHalfTheta = 1 - cosHalfTheta * cosHalfTheta;
+ if (sqrSinHalfTheta <= Number.EPSILON) {
+ const s = 1 - t;
+ this._w = s * w + t * this._w;
+ this._x = s * x2 + t * this._x;
+ this._y = s * y2 + t * this._y;
+ this._z = s * z + t * this._z;
+ this.normalize();
+ this._onChangeCallback();
+ return this;
+ }
+ const sinHalfTheta = Math.sqrt(sqrSinHalfTheta);
+ const halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta);
+ const ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta, ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
+ this._w = w * ratioA + this._w * ratioB;
+ this._x = x2 * ratioA + this._x * ratioB;
+ this._y = y2 * ratioA + this._y * ratioB;
+ this._z = z * ratioA + this._z * ratioB;
+ this._onChangeCallback();
+ return this;
+ }
+ slerpQuaternions(qa, qb, t) {
+ return this.copy(qa).slerp(qb, t);
+ }
+ random() {
+ const u1 = Math.random();
+ const sqrt1u1 = Math.sqrt(1 - u1);
+ const sqrtu1 = Math.sqrt(u1);
+ const u2 = 2 * Math.PI * Math.random();
+ const u3 = 2 * Math.PI * Math.random();
+ return this.set(sqrt1u1 * Math.cos(u2), sqrtu1 * Math.sin(u3), sqrtu1 * Math.cos(u3), sqrt1u1 * Math.sin(u2));
+ }
+ equals(quaternion) {
+ return quaternion._x === this._x && quaternion._y === this._y && quaternion._z === this._z && quaternion._w === this._w;
+ }
+ fromArray(array2, offset = 0) {
+ this._x = array2[offset];
+ this._y = array2[offset + 1];
+ this._z = array2[offset + 2];
+ this._w = array2[offset + 3];
+ this._onChangeCallback();
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ array2[offset] = this._x;
+ array2[offset + 1] = this._y;
+ array2[offset + 2] = this._z;
+ array2[offset + 3] = this._w;
+ return array2;
+ }
+ fromBufferAttribute(attribute, index2) {
+ this._x = attribute.getX(index2);
+ this._y = attribute.getY(index2);
+ this._z = attribute.getZ(index2);
+ this._w = attribute.getW(index2);
+ return this;
+ }
+ _onChange(callback) {
+ this._onChangeCallback = callback;
+ return this;
+ }
+ _onChangeCallback() {
+ }
+ *[Symbol.iterator]() {
+ yield this._x;
+ yield this._y;
+ yield this._z;
+ yield this._w;
+ }
+ };
+ var Vector32 = class {
+ constructor(x2 = 0, y2 = 0, z = 0) {
+ Vector32.prototype.isVector3 = true;
+ this.x = x2;
+ this.y = y2;
+ this.z = z;
+ }
+ set(x2, y2, z) {
+ if (z === void 0)
+ z = this.z;
+ this.x = x2;
+ this.y = y2;
+ this.z = z;
+ return this;
+ }
+ setScalar(scalar) {
+ this.x = scalar;
+ this.y = scalar;
+ this.z = scalar;
+ return this;
+ }
+ setX(x2) {
+ this.x = x2;
+ return this;
+ }
+ setY(y2) {
+ this.y = y2;
+ return this;
+ }
+ setZ(z) {
+ this.z = z;
+ return this;
+ }
+ setComponent(index2, value) {
+ switch (index2) {
+ case 0:
+ this.x = value;
+ break;
+ case 1:
+ this.y = value;
+ break;
+ case 2:
+ this.z = value;
+ break;
+ default:
+ throw new Error("index is out of range: " + index2);
+ }
+ return this;
+ }
+ getComponent(index2) {
+ switch (index2) {
+ case 0:
+ return this.x;
+ case 1:
+ return this.y;
+ case 2:
+ return this.z;
+ default:
+ throw new Error("index is out of range: " + index2);
+ }
+ }
+ clone() {
+ return new this.constructor(this.x, this.y, this.z);
+ }
+ copy(v) {
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ return this;
+ }
+ add(v) {
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+ return this;
+ }
+ addScalar(s) {
+ this.x += s;
+ this.y += s;
+ this.z += s;
+ return this;
+ }
+ addVectors(a2, b) {
+ this.x = a2.x + b.x;
+ this.y = a2.y + b.y;
+ this.z = a2.z + b.z;
+ return this;
+ }
+ addScaledVector(v, s) {
+ this.x += v.x * s;
+ this.y += v.y * s;
+ this.z += v.z * s;
+ return this;
+ }
+ sub(v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+ return this;
+ }
+ subScalar(s) {
+ this.x -= s;
+ this.y -= s;
+ this.z -= s;
+ return this;
+ }
+ subVectors(a2, b) {
+ this.x = a2.x - b.x;
+ this.y = a2.y - b.y;
+ this.z = a2.z - b.z;
+ return this;
+ }
+ multiply(v) {
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+ return this;
+ }
+ multiplyScalar(scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ return this;
+ }
+ multiplyVectors(a2, b) {
+ this.x = a2.x * b.x;
+ this.y = a2.y * b.y;
+ this.z = a2.z * b.z;
+ return this;
+ }
+ applyEuler(euler) {
+ return this.applyQuaternion(_quaternion$42.setFromEuler(euler));
+ }
+ applyAxisAngle(axis, angle) {
+ return this.applyQuaternion(_quaternion$42.setFromAxisAngle(axis, angle));
+ }
+ applyMatrix3(m2) {
+ const x2 = this.x, y2 = this.y, z = this.z;
+ const e = m2.elements;
+ this.x = e[0] * x2 + e[3] * y2 + e[6] * z;
+ this.y = e[1] * x2 + e[4] * y2 + e[7] * z;
+ this.z = e[2] * x2 + e[5] * y2 + e[8] * z;
+ return this;
+ }
+ applyNormalMatrix(m2) {
+ return this.applyMatrix3(m2).normalize();
+ }
+ applyMatrix4(m2) {
+ const x2 = this.x, y2 = this.y, z = this.z;
+ const e = m2.elements;
+ const w = 1 / (e[3] * x2 + e[7] * y2 + e[11] * z + e[15]);
+ this.x = (e[0] * x2 + e[4] * y2 + e[8] * z + e[12]) * w;
+ this.y = (e[1] * x2 + e[5] * y2 + e[9] * z + e[13]) * w;
+ this.z = (e[2] * x2 + e[6] * y2 + e[10] * z + e[14]) * w;
+ return this;
+ }
+ applyQuaternion(q) {
+ const x2 = this.x, y2 = this.y, z = this.z;
+ const qx = q.x, qy = q.y, qz = q.z, qw = q.w;
+ const ix = qw * x2 + qy * z - qz * y2;
+ const iy = qw * y2 + qz * x2 - qx * z;
+ const iz = qw * z + qx * y2 - qy * x2;
+ const iw = -qx * x2 - qy * y2 - qz * z;
+ this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+ this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+ this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+ return this;
+ }
+ project(camera) {
+ return this.applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);
+ }
+ unproject(camera) {
+ return this.applyMatrix4(camera.projectionMatrixInverse).applyMatrix4(camera.matrixWorld);
+ }
+ transformDirection(m2) {
+ const x2 = this.x, y2 = this.y, z = this.z;
+ const e = m2.elements;
+ this.x = e[0] * x2 + e[4] * y2 + e[8] * z;
+ this.y = e[1] * x2 + e[5] * y2 + e[9] * z;
+ this.z = e[2] * x2 + e[6] * y2 + e[10] * z;
+ return this.normalize();
+ }
+ divide(v) {
+ this.x /= v.x;
+ this.y /= v.y;
+ this.z /= v.z;
+ return this;
+ }
+ divideScalar(scalar) {
+ return this.multiplyScalar(1 / scalar);
+ }
+ min(v) {
+ this.x = Math.min(this.x, v.x);
+ this.y = Math.min(this.y, v.y);
+ this.z = Math.min(this.z, v.z);
+ return this;
+ }
+ max(v) {
+ this.x = Math.max(this.x, v.x);
+ this.y = Math.max(this.y, v.y);
+ this.z = Math.max(this.z, v.z);
+ return this;
+ }
+ clamp(min2, max2) {
+ this.x = Math.max(min2.x, Math.min(max2.x, this.x));
+ this.y = Math.max(min2.y, Math.min(max2.y, this.y));
+ this.z = Math.max(min2.z, Math.min(max2.z, this.z));
+ return this;
+ }
+ clampScalar(minVal, maxVal) {
+ this.x = Math.max(minVal, Math.min(maxVal, this.x));
+ this.y = Math.max(minVal, Math.min(maxVal, this.y));
+ this.z = Math.max(minVal, Math.min(maxVal, this.z));
+ return this;
+ }
+ clampLength(min2, max2) {
+ const length = this.length();
+ return this.divideScalar(length || 1).multiplyScalar(Math.max(min2, Math.min(max2, length)));
+ }
+ floor() {
+ this.x = Math.floor(this.x);
+ this.y = Math.floor(this.y);
+ this.z = Math.floor(this.z);
+ return this;
+ }
+ ceil() {
+ this.x = Math.ceil(this.x);
+ this.y = Math.ceil(this.y);
+ this.z = Math.ceil(this.z);
+ return this;
+ }
+ round() {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ this.z = Math.round(this.z);
+ return this;
+ }
+ roundToZero() {
+ this.x = this.x < 0 ? Math.ceil(this.x) : Math.floor(this.x);
+ this.y = this.y < 0 ? Math.ceil(this.y) : Math.floor(this.y);
+ this.z = this.z < 0 ? Math.ceil(this.z) : Math.floor(this.z);
+ return this;
+ }
+ negate() {
+ this.x = -this.x;
+ this.y = -this.y;
+ this.z = -this.z;
+ return this;
+ }
+ dot(v) {
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+ }
+ lengthSq() {
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+ }
+ length() {
+ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+ }
+ manhattanLength() {
+ return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z);
+ }
+ normalize() {
+ return this.divideScalar(this.length() || 1);
+ }
+ setLength(length) {
+ return this.normalize().multiplyScalar(length);
+ }
+ lerp(v, alpha) {
+ this.x += (v.x - this.x) * alpha;
+ this.y += (v.y - this.y) * alpha;
+ this.z += (v.z - this.z) * alpha;
+ return this;
+ }
+ lerpVectors(v1, v2, alpha) {
+ this.x = v1.x + (v2.x - v1.x) * alpha;
+ this.y = v1.y + (v2.y - v1.y) * alpha;
+ this.z = v1.z + (v2.z - v1.z) * alpha;
+ return this;
+ }
+ cross(v) {
+ return this.crossVectors(this, v);
+ }
+ crossVectors(a2, b) {
+ const ax = a2.x, ay = a2.y, az = a2.z;
+ const bx = b.x, by = b.y, bz = b.z;
+ this.x = ay * bz - az * by;
+ this.y = az * bx - ax * bz;
+ this.z = ax * by - ay * bx;
+ return this;
+ }
+ projectOnVector(v) {
+ const denominator = v.lengthSq();
+ if (denominator === 0)
+ return this.set(0, 0, 0);
+ const scalar = v.dot(this) / denominator;
+ return this.copy(v).multiplyScalar(scalar);
+ }
+ projectOnPlane(planeNormal) {
+ _vector$c2.copy(this).projectOnVector(planeNormal);
+ return this.sub(_vector$c2);
+ }
+ reflect(normal) {
+ return this.sub(_vector$c2.copy(normal).multiplyScalar(2 * this.dot(normal)));
+ }
+ angleTo(v) {
+ const denominator = Math.sqrt(this.lengthSq() * v.lengthSq());
+ if (denominator === 0)
+ return Math.PI / 2;
+ const theta = this.dot(v) / denominator;
+ return Math.acos(clamp2(theta, -1, 1));
+ }
+ distanceTo(v) {
+ return Math.sqrt(this.distanceToSquared(v));
+ }
+ distanceToSquared(v) {
+ const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
+ return dx * dx + dy * dy + dz * dz;
+ }
+ manhattanDistanceTo(v) {
+ return Math.abs(this.x - v.x) + Math.abs(this.y - v.y) + Math.abs(this.z - v.z);
+ }
+ setFromSpherical(s) {
+ return this.setFromSphericalCoords(s.radius, s.phi, s.theta);
+ }
+ setFromSphericalCoords(radius, phi, theta) {
+ const sinPhiRadius = Math.sin(phi) * radius;
+ this.x = sinPhiRadius * Math.sin(theta);
+ this.y = Math.cos(phi) * radius;
+ this.z = sinPhiRadius * Math.cos(theta);
+ return this;
+ }
+ setFromCylindrical(c2) {
+ return this.setFromCylindricalCoords(c2.radius, c2.theta, c2.y);
+ }
+ setFromCylindricalCoords(radius, theta, y2) {
+ this.x = radius * Math.sin(theta);
+ this.y = y2;
+ this.z = radius * Math.cos(theta);
+ return this;
+ }
+ setFromMatrixPosition(m2) {
+ const e = m2.elements;
+ this.x = e[12];
+ this.y = e[13];
+ this.z = e[14];
+ return this;
+ }
+ setFromMatrixScale(m2) {
+ const sx = this.setFromMatrixColumn(m2, 0).length();
+ const sy = this.setFromMatrixColumn(m2, 1).length();
+ const sz = this.setFromMatrixColumn(m2, 2).length();
+ this.x = sx;
+ this.y = sy;
+ this.z = sz;
+ return this;
+ }
+ setFromMatrixColumn(m2, index2) {
+ return this.fromArray(m2.elements, index2 * 4);
+ }
+ setFromMatrix3Column(m2, index2) {
+ return this.fromArray(m2.elements, index2 * 3);
+ }
+ setFromEuler(e) {
+ this.x = e._x;
+ this.y = e._y;
+ this.z = e._z;
+ return this;
+ }
+ equals(v) {
+ return v.x === this.x && v.y === this.y && v.z === this.z;
+ }
+ fromArray(array2, offset = 0) {
+ this.x = array2[offset];
+ this.y = array2[offset + 1];
+ this.z = array2[offset + 2];
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ array2[offset] = this.x;
+ array2[offset + 1] = this.y;
+ array2[offset + 2] = this.z;
+ return array2;
+ }
+ fromBufferAttribute(attribute, index2) {
+ this.x = attribute.getX(index2);
+ this.y = attribute.getY(index2);
+ this.z = attribute.getZ(index2);
+ return this;
+ }
+ random() {
+ this.x = Math.random();
+ this.y = Math.random();
+ this.z = Math.random();
+ return this;
+ }
+ randomDirection() {
+ const u = (Math.random() - 0.5) * 2;
+ const t = Math.random() * Math.PI * 2;
+ const f = Math.sqrt(1 - u ** 2);
+ this.x = f * Math.cos(t);
+ this.y = f * Math.sin(t);
+ this.z = u;
+ return this;
+ }
+ *[Symbol.iterator]() {
+ yield this.x;
+ yield this.y;
+ yield this.z;
+ }
+ };
+ var _vector$c2 = /* @__PURE__ */ new Vector32();
+ var _quaternion$42 = /* @__PURE__ */ new Quaternion2();
+ var Box32 = class {
+ constructor(min2 = new Vector32(Infinity, Infinity, Infinity), max2 = new Vector32(-Infinity, -Infinity, -Infinity)) {
+ this.isBox3 = true;
+ this.min = min2;
+ this.max = max2;
+ }
+ set(min2, max2) {
+ this.min.copy(min2);
+ this.max.copy(max2);
+ return this;
+ }
+ setFromArray(array2) {
+ let minX = Infinity;
+ let minY = Infinity;
+ let minZ = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+ let maxZ = -Infinity;
+ for (let i = 0, l = array2.length; i < l; i += 3) {
+ const x2 = array2[i];
+ const y2 = array2[i + 1];
+ const z = array2[i + 2];
+ if (x2 < minX)
+ minX = x2;
+ if (y2 < minY)
+ minY = y2;
+ if (z < minZ)
+ minZ = z;
+ if (x2 > maxX)
+ maxX = x2;
+ if (y2 > maxY)
+ maxY = y2;
+ if (z > maxZ)
+ maxZ = z;
+ }
+ this.min.set(minX, minY, minZ);
+ this.max.set(maxX, maxY, maxZ);
+ return this;
+ }
+ setFromBufferAttribute(attribute) {
+ let minX = Infinity;
+ let minY = Infinity;
+ let minZ = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+ let maxZ = -Infinity;
+ for (let i = 0, l = attribute.count; i < l; i++) {
+ const x2 = attribute.getX(i);
+ const y2 = attribute.getY(i);
+ const z = attribute.getZ(i);
+ if (x2 < minX)
+ minX = x2;
+ if (y2 < minY)
+ minY = y2;
+ if (z < minZ)
+ minZ = z;
+ if (x2 > maxX)
+ maxX = x2;
+ if (y2 > maxY)
+ maxY = y2;
+ if (z > maxZ)
+ maxZ = z;
+ }
+ this.min.set(minX, minY, minZ);
+ this.max.set(maxX, maxY, maxZ);
+ return this;
+ }
+ setFromPoints(points) {
+ this.makeEmpty();
+ for (let i = 0, il = points.length; i < il; i++) {
+ this.expandByPoint(points[i]);
+ }
+ return this;
+ }
+ setFromCenterAndSize(center, size) {
+ const halfSize = _vector$b2.copy(size).multiplyScalar(0.5);
+ this.min.copy(center).sub(halfSize);
+ this.max.copy(center).add(halfSize);
+ return this;
+ }
+ setFromObject(object, precise = false) {
+ this.makeEmpty();
+ return this.expandByObject(object, precise);
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ copy(box) {
+ this.min.copy(box.min);
+ this.max.copy(box.max);
+ return this;
+ }
+ makeEmpty() {
+ this.min.x = this.min.y = this.min.z = Infinity;
+ this.max.x = this.max.y = this.max.z = -Infinity;
+ return this;
+ }
+ isEmpty() {
+ return this.max.x < this.min.x || this.max.y < this.min.y || this.max.z < this.min.z;
+ }
+ getCenter(target) {
+ return this.isEmpty() ? target.set(0, 0, 0) : target.addVectors(this.min, this.max).multiplyScalar(0.5);
+ }
+ getSize(target) {
+ return this.isEmpty() ? target.set(0, 0, 0) : target.subVectors(this.max, this.min);
+ }
+ expandByPoint(point) {
+ this.min.min(point);
+ this.max.max(point);
+ return this;
+ }
+ expandByVector(vector) {
+ this.min.sub(vector);
+ this.max.add(vector);
+ return this;
+ }
+ expandByScalar(scalar) {
+ this.min.addScalar(-scalar);
+ this.max.addScalar(scalar);
+ return this;
+ }
+ expandByObject(object, precise = false) {
+ object.updateWorldMatrix(false, false);
+ const geometry = object.geometry;
+ if (geometry !== void 0) {
+ if (precise && geometry.attributes != void 0 && geometry.attributes.position !== void 0) {
+ const position = geometry.attributes.position;
+ for (let i = 0, l = position.count; i < l; i++) {
+ _vector$b2.fromBufferAttribute(position, i).applyMatrix4(object.matrixWorld);
+ this.expandByPoint(_vector$b2);
+ }
+ } else {
+ if (geometry.boundingBox === null) {
+ geometry.computeBoundingBox();
+ }
+ _box$32.copy(geometry.boundingBox);
+ _box$32.applyMatrix4(object.matrixWorld);
+ this.union(_box$32);
+ }
+ }
+ const children2 = object.children;
+ for (let i = 0, l = children2.length; i < l; i++) {
+ this.expandByObject(children2[i], precise);
+ }
+ return this;
+ }
+ containsPoint(point) {
+ return point.x < this.min.x || point.x > this.max.x || point.y < this.min.y || point.y > this.max.y || point.z < this.min.z || point.z > this.max.z ? false : true;
+ }
+ containsBox(box) {
+ return this.min.x <= box.min.x && box.max.x <= this.max.x && this.min.y <= box.min.y && box.max.y <= this.max.y && this.min.z <= box.min.z && box.max.z <= this.max.z;
+ }
+ getParameter(point, target) {
+ return target.set((point.x - this.min.x) / (this.max.x - this.min.x), (point.y - this.min.y) / (this.max.y - this.min.y), (point.z - this.min.z) / (this.max.z - this.min.z));
+ }
+ intersectsBox(box) {
+ return box.max.x < this.min.x || box.min.x > this.max.x || box.max.y < this.min.y || box.min.y > this.max.y || box.max.z < this.min.z || box.min.z > this.max.z ? false : true;
+ }
+ intersectsSphere(sphere) {
+ this.clampPoint(sphere.center, _vector$b2);
+ return _vector$b2.distanceToSquared(sphere.center) <= sphere.radius * sphere.radius;
+ }
+ intersectsPlane(plane) {
+ let min2, max2;
+ if (plane.normal.x > 0) {
+ min2 = plane.normal.x * this.min.x;
+ max2 = plane.normal.x * this.max.x;
+ } else {
+ min2 = plane.normal.x * this.max.x;
+ max2 = plane.normal.x * this.min.x;
+ }
+ if (plane.normal.y > 0) {
+ min2 += plane.normal.y * this.min.y;
+ max2 += plane.normal.y * this.max.y;
+ } else {
+ min2 += plane.normal.y * this.max.y;
+ max2 += plane.normal.y * this.min.y;
+ }
+ if (plane.normal.z > 0) {
+ min2 += plane.normal.z * this.min.z;
+ max2 += plane.normal.z * this.max.z;
+ } else {
+ min2 += plane.normal.z * this.max.z;
+ max2 += plane.normal.z * this.min.z;
+ }
+ return min2 <= -plane.constant && max2 >= -plane.constant;
+ }
+ intersectsTriangle(triangle) {
+ if (this.isEmpty()) {
+ return false;
+ }
+ this.getCenter(_center2);
+ _extents2.subVectors(this.max, _center2);
+ _v0$22.subVectors(triangle.a, _center2);
+ _v1$72.subVectors(triangle.b, _center2);
+ _v2$32.subVectors(triangle.c, _center2);
+ _f02.subVectors(_v1$72, _v0$22);
+ _f12.subVectors(_v2$32, _v1$72);
+ _f22.subVectors(_v0$22, _v2$32);
+ let axes = [0, -_f02.z, _f02.y, 0, -_f12.z, _f12.y, 0, -_f22.z, _f22.y, _f02.z, 0, -_f02.x, _f12.z, 0, -_f12.x, _f22.z, 0, -_f22.x, -_f02.y, _f02.x, 0, -_f12.y, _f12.x, 0, -_f22.y, _f22.x, 0];
+ if (!satForAxes2(axes, _v0$22, _v1$72, _v2$32, _extents2)) {
+ return false;
+ }
+ axes = [1, 0, 0, 0, 1, 0, 0, 0, 1];
+ if (!satForAxes2(axes, _v0$22, _v1$72, _v2$32, _extents2)) {
+ return false;
+ }
+ _triangleNormal2.crossVectors(_f02, _f12);
+ axes = [_triangleNormal2.x, _triangleNormal2.y, _triangleNormal2.z];
+ return satForAxes2(axes, _v0$22, _v1$72, _v2$32, _extents2);
+ }
+ clampPoint(point, target) {
+ return target.copy(point).clamp(this.min, this.max);
+ }
+ distanceToPoint(point) {
+ const clampedPoint = _vector$b2.copy(point).clamp(this.min, this.max);
+ return clampedPoint.sub(point).length();
+ }
+ getBoundingSphere(target) {
+ this.getCenter(target.center);
+ target.radius = this.getSize(_vector$b2).length() * 0.5;
+ return target;
+ }
+ intersect(box) {
+ this.min.max(box.min);
+ this.max.min(box.max);
+ if (this.isEmpty())
+ this.makeEmpty();
+ return this;
+ }
+ union(box) {
+ this.min.min(box.min);
+ this.max.max(box.max);
+ return this;
+ }
+ applyMatrix4(matrix) {
+ if (this.isEmpty())
+ return this;
+ _points2[0].set(this.min.x, this.min.y, this.min.z).applyMatrix4(matrix);
+ _points2[1].set(this.min.x, this.min.y, this.max.z).applyMatrix4(matrix);
+ _points2[2].set(this.min.x, this.max.y, this.min.z).applyMatrix4(matrix);
+ _points2[3].set(this.min.x, this.max.y, this.max.z).applyMatrix4(matrix);
+ _points2[4].set(this.max.x, this.min.y, this.min.z).applyMatrix4(matrix);
+ _points2[5].set(this.max.x, this.min.y, this.max.z).applyMatrix4(matrix);
+ _points2[6].set(this.max.x, this.max.y, this.min.z).applyMatrix4(matrix);
+ _points2[7].set(this.max.x, this.max.y, this.max.z).applyMatrix4(matrix);
+ this.setFromPoints(_points2);
+ return this;
+ }
+ translate(offset) {
+ this.min.add(offset);
+ this.max.add(offset);
+ return this;
+ }
+ equals(box) {
+ return box.min.equals(this.min) && box.max.equals(this.max);
+ }
+ };
+ var _points2 = [/* @__PURE__ */ new Vector32(), /* @__PURE__ */ new Vector32(), /* @__PURE__ */ new Vector32(), /* @__PURE__ */ new Vector32(), /* @__PURE__ */ new Vector32(), /* @__PURE__ */ new Vector32(), /* @__PURE__ */ new Vector32(), /* @__PURE__ */ new Vector32()];
+ var _vector$b2 = /* @__PURE__ */ new Vector32();
+ var _box$32 = /* @__PURE__ */ new Box32();
+ var _v0$22 = /* @__PURE__ */ new Vector32();
+ var _v1$72 = /* @__PURE__ */ new Vector32();
+ var _v2$32 = /* @__PURE__ */ new Vector32();
+ var _f02 = /* @__PURE__ */ new Vector32();
+ var _f12 = /* @__PURE__ */ new Vector32();
+ var _f22 = /* @__PURE__ */ new Vector32();
+ var _center2 = /* @__PURE__ */ new Vector32();
+ var _extents2 = /* @__PURE__ */ new Vector32();
+ var _triangleNormal2 = /* @__PURE__ */ new Vector32();
+ var _testAxis2 = /* @__PURE__ */ new Vector32();
+ function satForAxes2(axes, v0, v1, v2, extents) {
+ for (let i = 0, j = axes.length - 3; i <= j; i += 3) {
+ _testAxis2.fromArray(axes, i);
+ const r = extents.x * Math.abs(_testAxis2.x) + extents.y * Math.abs(_testAxis2.y) + extents.z * Math.abs(_testAxis2.z);
+ const p0 = v0.dot(_testAxis2);
+ const p1 = v1.dot(_testAxis2);
+ const p2 = v2.dot(_testAxis2);
+ if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) {
+ return false;
+ }
+ }
+ return true;
+ }
+ var _box$22 = /* @__PURE__ */ new Box32();
+ var _v1$62 = /* @__PURE__ */ new Vector32();
+ var _toFarthestPoint2 = /* @__PURE__ */ new Vector32();
+ var _toPoint2 = /* @__PURE__ */ new Vector32();
+ var Sphere2 = class {
+ constructor(center = new Vector32(), radius = -1) {
+ this.center = center;
+ this.radius = radius;
+ }
+ set(center, radius) {
+ this.center.copy(center);
+ this.radius = radius;
+ return this;
+ }
+ setFromPoints(points, optionalCenter) {
+ const center = this.center;
+ if (optionalCenter !== void 0) {
+ center.copy(optionalCenter);
+ } else {
+ _box$22.setFromPoints(points).getCenter(center);
+ }
+ let maxRadiusSq = 0;
+ for (let i = 0, il = points.length; i < il; i++) {
+ maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(points[i]));
+ }
+ this.radius = Math.sqrt(maxRadiusSq);
+ return this;
+ }
+ copy(sphere) {
+ this.center.copy(sphere.center);
+ this.radius = sphere.radius;
+ return this;
+ }
+ isEmpty() {
+ return this.radius < 0;
+ }
+ makeEmpty() {
+ this.center.set(0, 0, 0);
+ this.radius = -1;
+ return this;
+ }
+ containsPoint(point) {
+ return point.distanceToSquared(this.center) <= this.radius * this.radius;
+ }
+ distanceToPoint(point) {
+ return point.distanceTo(this.center) - this.radius;
+ }
+ intersectsSphere(sphere) {
+ const radiusSum = this.radius + sphere.radius;
+ return sphere.center.distanceToSquared(this.center) <= radiusSum * radiusSum;
+ }
+ intersectsBox(box) {
+ return box.intersectsSphere(this);
+ }
+ intersectsPlane(plane) {
+ return Math.abs(plane.distanceToPoint(this.center)) <= this.radius;
+ }
+ clampPoint(point, target) {
+ const deltaLengthSq = this.center.distanceToSquared(point);
+ target.copy(point);
+ if (deltaLengthSq > this.radius * this.radius) {
+ target.sub(this.center).normalize();
+ target.multiplyScalar(this.radius).add(this.center);
+ }
+ return target;
+ }
+ getBoundingBox(target) {
+ if (this.isEmpty()) {
+ target.makeEmpty();
+ return target;
+ }
+ target.set(this.center, this.center);
+ target.expandByScalar(this.radius);
+ return target;
+ }
+ applyMatrix4(matrix) {
+ this.center.applyMatrix4(matrix);
+ this.radius = this.radius * matrix.getMaxScaleOnAxis();
+ return this;
+ }
+ translate(offset) {
+ this.center.add(offset);
+ return this;
+ }
+ expandByPoint(point) {
+ _toPoint2.subVectors(point, this.center);
+ const lengthSq = _toPoint2.lengthSq();
+ if (lengthSq > this.radius * this.radius) {
+ const length = Math.sqrt(lengthSq);
+ const missingRadiusHalf = (length - this.radius) * 0.5;
+ this.center.add(_toPoint2.multiplyScalar(missingRadiusHalf / length));
+ this.radius += missingRadiusHalf;
+ }
+ return this;
+ }
+ union(sphere) {
+ if (this.center.equals(sphere.center) === true) {
+ _toFarthestPoint2.set(0, 0, 1).multiplyScalar(sphere.radius);
+ } else {
+ _toFarthestPoint2.subVectors(sphere.center, this.center).normalize().multiplyScalar(sphere.radius);
+ }
+ this.expandByPoint(_v1$62.copy(sphere.center).add(_toFarthestPoint2));
+ this.expandByPoint(_v1$62.copy(sphere.center).sub(_toFarthestPoint2));
+ return this;
+ }
+ equals(sphere) {
+ return sphere.center.equals(this.center) && sphere.radius === this.radius;
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ };
+ var _vector$a2 = /* @__PURE__ */ new Vector32();
+ var _segCenter2 = /* @__PURE__ */ new Vector32();
+ var _segDir2 = /* @__PURE__ */ new Vector32();
+ var _diff2 = /* @__PURE__ */ new Vector32();
+ var _edge12 = /* @__PURE__ */ new Vector32();
+ var _edge22 = /* @__PURE__ */ new Vector32();
+ var _normal$12 = /* @__PURE__ */ new Vector32();
+ var Ray2 = class {
+ constructor(origin = new Vector32(), direction = new Vector32(0, 0, -1)) {
+ this.origin = origin;
+ this.direction = direction;
+ }
+ set(origin, direction) {
+ this.origin.copy(origin);
+ this.direction.copy(direction);
+ return this;
+ }
+ copy(ray) {
+ this.origin.copy(ray.origin);
+ this.direction.copy(ray.direction);
+ return this;
+ }
+ at(t, target) {
+ return target.copy(this.direction).multiplyScalar(t).add(this.origin);
+ }
+ lookAt(v) {
+ this.direction.copy(v).sub(this.origin).normalize();
+ return this;
+ }
+ recast(t) {
+ this.origin.copy(this.at(t, _vector$a2));
+ return this;
+ }
+ closestPointToPoint(point, target) {
+ target.subVectors(point, this.origin);
+ const directionDistance = target.dot(this.direction);
+ if (directionDistance < 0) {
+ return target.copy(this.origin);
+ }
+ return target.copy(this.direction).multiplyScalar(directionDistance).add(this.origin);
+ }
+ distanceToPoint(point) {
+ return Math.sqrt(this.distanceSqToPoint(point));
+ }
+ distanceSqToPoint(point) {
+ const directionDistance = _vector$a2.subVectors(point, this.origin).dot(this.direction);
+ if (directionDistance < 0) {
+ return this.origin.distanceToSquared(point);
+ }
+ _vector$a2.copy(this.direction).multiplyScalar(directionDistance).add(this.origin);
+ return _vector$a2.distanceToSquared(point);
+ }
+ distanceSqToSegment(v0, v1, optionalPointOnRay, optionalPointOnSegment) {
+ _segCenter2.copy(v0).add(v1).multiplyScalar(0.5);
+ _segDir2.copy(v1).sub(v0).normalize();
+ _diff2.copy(this.origin).sub(_segCenter2);
+ const segExtent = v0.distanceTo(v1) * 0.5;
+ const a01 = -this.direction.dot(_segDir2);
+ const b0 = _diff2.dot(this.direction);
+ const b1 = -_diff2.dot(_segDir2);
+ const c2 = _diff2.lengthSq();
+ const det = Math.abs(1 - a01 * a01);
+ let s0, s1, sqrDist, extDet;
+ if (det > 0) {
+ s0 = a01 * b1 - b0;
+ s1 = a01 * b0 - b1;
+ extDet = segExtent * det;
+ if (s0 >= 0) {
+ if (s1 >= -extDet) {
+ if (s1 <= extDet) {
+ const invDet = 1 / det;
+ s0 *= invDet;
+ s1 *= invDet;
+ sqrDist = s0 * (s0 + a01 * s1 + 2 * b0) + s1 * (a01 * s0 + s1 + 2 * b1) + c2;
+ } else {
+ s1 = segExtent;
+ s0 = Math.max(0, -(a01 * s1 + b0));
+ sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c2;
+ }
+ } else {
+ s1 = -segExtent;
+ s0 = Math.max(0, -(a01 * s1 + b0));
+ sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c2;
+ }
+ } else {
+ if (s1 <= -extDet) {
+ s0 = Math.max(0, -(-a01 * segExtent + b0));
+ s1 = s0 > 0 ? -segExtent : Math.min(Math.max(-segExtent, -b1), segExtent);
+ sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c2;
+ } else if (s1 <= extDet) {
+ s0 = 0;
+ s1 = Math.min(Math.max(-segExtent, -b1), segExtent);
+ sqrDist = s1 * (s1 + 2 * b1) + c2;
+ } else {
+ s0 = Math.max(0, -(a01 * segExtent + b0));
+ s1 = s0 > 0 ? segExtent : Math.min(Math.max(-segExtent, -b1), segExtent);
+ sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c2;
+ }
+ }
+ } else {
+ s1 = a01 > 0 ? -segExtent : segExtent;
+ s0 = Math.max(0, -(a01 * s1 + b0));
+ sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c2;
+ }
+ if (optionalPointOnRay) {
+ optionalPointOnRay.copy(this.direction).multiplyScalar(s0).add(this.origin);
+ }
+ if (optionalPointOnSegment) {
+ optionalPointOnSegment.copy(_segDir2).multiplyScalar(s1).add(_segCenter2);
+ }
+ return sqrDist;
+ }
+ intersectSphere(sphere, target) {
+ _vector$a2.subVectors(sphere.center, this.origin);
+ const tca = _vector$a2.dot(this.direction);
+ const d2 = _vector$a2.dot(_vector$a2) - tca * tca;
+ const radius2 = sphere.radius * sphere.radius;
+ if (d2 > radius2)
+ return null;
+ const thc = Math.sqrt(radius2 - d2);
+ const t0 = tca - thc;
+ const t1 = tca + thc;
+ if (t0 < 0 && t1 < 0)
+ return null;
+ if (t0 < 0)
+ return this.at(t1, target);
+ return this.at(t0, target);
+ }
+ intersectsSphere(sphere) {
+ return this.distanceSqToPoint(sphere.center) <= sphere.radius * sphere.radius;
+ }
+ distanceToPlane(plane) {
+ const denominator = plane.normal.dot(this.direction);
+ if (denominator === 0) {
+ if (plane.distanceToPoint(this.origin) === 0) {
+ return 0;
+ }
+ return null;
+ }
+ const t = -(this.origin.dot(plane.normal) + plane.constant) / denominator;
+ return t >= 0 ? t : null;
+ }
+ intersectPlane(plane, target) {
+ const t = this.distanceToPlane(plane);
+ if (t === null) {
+ return null;
+ }
+ return this.at(t, target);
+ }
+ intersectsPlane(plane) {
+ const distToPoint = plane.distanceToPoint(this.origin);
+ if (distToPoint === 0) {
+ return true;
+ }
+ const denominator = plane.normal.dot(this.direction);
+ if (denominator * distToPoint < 0) {
+ return true;
+ }
+ return false;
+ }
+ intersectBox(box, target) {
+ let tmin, tmax, tymin, tymax, tzmin, tzmax;
+ const invdirx = 1 / this.direction.x, invdiry = 1 / this.direction.y, invdirz = 1 / this.direction.z;
+ const origin = this.origin;
+ if (invdirx >= 0) {
+ tmin = (box.min.x - origin.x) * invdirx;
+ tmax = (box.max.x - origin.x) * invdirx;
+ } else {
+ tmin = (box.max.x - origin.x) * invdirx;
+ tmax = (box.min.x - origin.x) * invdirx;
+ }
+ if (invdiry >= 0) {
+ tymin = (box.min.y - origin.y) * invdiry;
+ tymax = (box.max.y - origin.y) * invdiry;
+ } else {
+ tymin = (box.max.y - origin.y) * invdiry;
+ tymax = (box.min.y - origin.y) * invdiry;
+ }
+ if (tmin > tymax || tymin > tmax)
+ return null;
+ if (tymin > tmin || tmin !== tmin)
+ tmin = tymin;
+ if (tymax < tmax || tmax !== tmax)
+ tmax = tymax;
+ if (invdirz >= 0) {
+ tzmin = (box.min.z - origin.z) * invdirz;
+ tzmax = (box.max.z - origin.z) * invdirz;
+ } else {
+ tzmin = (box.max.z - origin.z) * invdirz;
+ tzmax = (box.min.z - origin.z) * invdirz;
+ }
+ if (tmin > tzmax || tzmin > tmax)
+ return null;
+ if (tzmin > tmin || tmin !== tmin)
+ tmin = tzmin;
+ if (tzmax < tmax || tmax !== tmax)
+ tmax = tzmax;
+ if (tmax < 0)
+ return null;
+ return this.at(tmin >= 0 ? tmin : tmax, target);
+ }
+ intersectsBox(box) {
+ return this.intersectBox(box, _vector$a2) !== null;
+ }
+ intersectTriangle(a2, b, c2, backfaceCulling, target) {
+ _edge12.subVectors(b, a2);
+ _edge22.subVectors(c2, a2);
+ _normal$12.crossVectors(_edge12, _edge22);
+ let DdN = this.direction.dot(_normal$12);
+ let sign2;
+ if (DdN > 0) {
+ if (backfaceCulling)
+ return null;
+ sign2 = 1;
+ } else if (DdN < 0) {
+ sign2 = -1;
+ DdN = -DdN;
+ } else {
+ return null;
+ }
+ _diff2.subVectors(this.origin, a2);
+ const DdQxE2 = sign2 * this.direction.dot(_edge22.crossVectors(_diff2, _edge22));
+ if (DdQxE2 < 0) {
+ return null;
+ }
+ const DdE1xQ = sign2 * this.direction.dot(_edge12.cross(_diff2));
+ if (DdE1xQ < 0) {
+ return null;
+ }
+ if (DdQxE2 + DdE1xQ > DdN) {
+ return null;
+ }
+ const QdN = -sign2 * _diff2.dot(_normal$12);
+ if (QdN < 0) {
+ return null;
+ }
+ return this.at(QdN / DdN, target);
+ }
+ applyMatrix4(matrix4) {
+ this.origin.applyMatrix4(matrix4);
+ this.direction.transformDirection(matrix4);
+ return this;
+ }
+ equals(ray) {
+ return ray.origin.equals(this.origin) && ray.direction.equals(this.direction);
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ };
+ var Matrix42 = class {
+ constructor() {
+ Matrix42.prototype.isMatrix4 = true;
+ this.elements = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
+ }
+ set(n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) {
+ const te = this.elements;
+ te[0] = n11;
+ te[4] = n12;
+ te[8] = n13;
+ te[12] = n14;
+ te[1] = n21;
+ te[5] = n22;
+ te[9] = n23;
+ te[13] = n24;
+ te[2] = n31;
+ te[6] = n32;
+ te[10] = n33;
+ te[14] = n34;
+ te[3] = n41;
+ te[7] = n42;
+ te[11] = n43;
+ te[15] = n44;
+ return this;
+ }
+ identity() {
+ this.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+ return this;
+ }
+ clone() {
+ return new Matrix42().fromArray(this.elements);
+ }
+ copy(m2) {
+ const te = this.elements;
+ const me = m2.elements;
+ te[0] = me[0];
+ te[1] = me[1];
+ te[2] = me[2];
+ te[3] = me[3];
+ te[4] = me[4];
+ te[5] = me[5];
+ te[6] = me[6];
+ te[7] = me[7];
+ te[8] = me[8];
+ te[9] = me[9];
+ te[10] = me[10];
+ te[11] = me[11];
+ te[12] = me[12];
+ te[13] = me[13];
+ te[14] = me[14];
+ te[15] = me[15];
+ return this;
+ }
+ copyPosition(m2) {
+ const te = this.elements, me = m2.elements;
+ te[12] = me[12];
+ te[13] = me[13];
+ te[14] = me[14];
+ return this;
+ }
+ setFromMatrix3(m2) {
+ const me = m2.elements;
+ this.set(me[0], me[3], me[6], 0, me[1], me[4], me[7], 0, me[2], me[5], me[8], 0, 0, 0, 0, 1);
+ return this;
+ }
+ extractBasis(xAxis, yAxis, zAxis) {
+ xAxis.setFromMatrixColumn(this, 0);
+ yAxis.setFromMatrixColumn(this, 1);
+ zAxis.setFromMatrixColumn(this, 2);
+ return this;
+ }
+ makeBasis(xAxis, yAxis, zAxis) {
+ this.set(xAxis.x, yAxis.x, zAxis.x, 0, xAxis.y, yAxis.y, zAxis.y, 0, xAxis.z, yAxis.z, zAxis.z, 0, 0, 0, 0, 1);
+ return this;
+ }
+ extractRotation(m2) {
+ const te = this.elements;
+ const me = m2.elements;
+ const scaleX = 1 / _v1$52.setFromMatrixColumn(m2, 0).length();
+ const scaleY = 1 / _v1$52.setFromMatrixColumn(m2, 1).length();
+ const scaleZ = 1 / _v1$52.setFromMatrixColumn(m2, 2).length();
+ te[0] = me[0] * scaleX;
+ te[1] = me[1] * scaleX;
+ te[2] = me[2] * scaleX;
+ te[3] = 0;
+ te[4] = me[4] * scaleY;
+ te[5] = me[5] * scaleY;
+ te[6] = me[6] * scaleY;
+ te[7] = 0;
+ te[8] = me[8] * scaleZ;
+ te[9] = me[9] * scaleZ;
+ te[10] = me[10] * scaleZ;
+ te[11] = 0;
+ te[12] = 0;
+ te[13] = 0;
+ te[14] = 0;
+ te[15] = 1;
+ return this;
+ }
+ makeRotationFromEuler(euler) {
+ const te = this.elements;
+ const x2 = euler.x, y2 = euler.y, z = euler.z;
+ const a2 = Math.cos(x2), b = Math.sin(x2);
+ const c2 = Math.cos(y2), d = Math.sin(y2);
+ const e = Math.cos(z), f = Math.sin(z);
+ if (euler.order === "XYZ") {
+ const ae = a2 * e, af = a2 * f, be = b * e, bf = b * f;
+ te[0] = c2 * e;
+ te[4] = -c2 * f;
+ te[8] = d;
+ te[1] = af + be * d;
+ te[5] = ae - bf * d;
+ te[9] = -b * c2;
+ te[2] = bf - ae * d;
+ te[6] = be + af * d;
+ te[10] = a2 * c2;
+ } else if (euler.order === "YXZ") {
+ const ce = c2 * e, cf = c2 * f, de = d * e, df = d * f;
+ te[0] = ce + df * b;
+ te[4] = de * b - cf;
+ te[8] = a2 * d;
+ te[1] = a2 * f;
+ te[5] = a2 * e;
+ te[9] = -b;
+ te[2] = cf * b - de;
+ te[6] = df + ce * b;
+ te[10] = a2 * c2;
+ } else if (euler.order === "ZXY") {
+ const ce = c2 * e, cf = c2 * f, de = d * e, df = d * f;
+ te[0] = ce - df * b;
+ te[4] = -a2 * f;
+ te[8] = de + cf * b;
+ te[1] = cf + de * b;
+ te[5] = a2 * e;
+ te[9] = df - ce * b;
+ te[2] = -a2 * d;
+ te[6] = b;
+ te[10] = a2 * c2;
+ } else if (euler.order === "ZYX") {
+ const ae = a2 * e, af = a2 * f, be = b * e, bf = b * f;
+ te[0] = c2 * e;
+ te[4] = be * d - af;
+ te[8] = ae * d + bf;
+ te[1] = c2 * f;
+ te[5] = bf * d + ae;
+ te[9] = af * d - be;
+ te[2] = -d;
+ te[6] = b * c2;
+ te[10] = a2 * c2;
+ } else if (euler.order === "YZX") {
+ const ac = a2 * c2, ad = a2 * d, bc = b * c2, bd = b * d;
+ te[0] = c2 * e;
+ te[4] = bd - ac * f;
+ te[8] = bc * f + ad;
+ te[1] = f;
+ te[5] = a2 * e;
+ te[9] = -b * e;
+ te[2] = -d * e;
+ te[6] = ad * f + bc;
+ te[10] = ac - bd * f;
+ } else if (euler.order === "XZY") {
+ const ac = a2 * c2, ad = a2 * d, bc = b * c2, bd = b * d;
+ te[0] = c2 * e;
+ te[4] = -f;
+ te[8] = d * e;
+ te[1] = ac * f + bd;
+ te[5] = a2 * e;
+ te[9] = ad * f - bc;
+ te[2] = bc * f - ad;
+ te[6] = b * e;
+ te[10] = bd * f + ac;
+ }
+ te[3] = 0;
+ te[7] = 0;
+ te[11] = 0;
+ te[12] = 0;
+ te[13] = 0;
+ te[14] = 0;
+ te[15] = 1;
+ return this;
+ }
+ makeRotationFromQuaternion(q) {
+ return this.compose(_zero2, q, _one2);
+ }
+ lookAt(eye, target, up) {
+ const te = this.elements;
+ _z2.subVectors(eye, target);
+ if (_z2.lengthSq() === 0) {
+ _z2.z = 1;
+ }
+ _z2.normalize();
+ _x2.crossVectors(up, _z2);
+ if (_x2.lengthSq() === 0) {
+ if (Math.abs(up.z) === 1) {
+ _z2.x += 1e-4;
+ } else {
+ _z2.z += 1e-4;
+ }
+ _z2.normalize();
+ _x2.crossVectors(up, _z2);
+ }
+ _x2.normalize();
+ _y2.crossVectors(_z2, _x2);
+ te[0] = _x2.x;
+ te[4] = _y2.x;
+ te[8] = _z2.x;
+ te[1] = _x2.y;
+ te[5] = _y2.y;
+ te[9] = _z2.y;
+ te[2] = _x2.z;
+ te[6] = _y2.z;
+ te[10] = _z2.z;
+ return this;
+ }
+ multiply(m2) {
+ return this.multiplyMatrices(this, m2);
+ }
+ premultiply(m2) {
+ return this.multiplyMatrices(m2, this);
+ }
+ multiplyMatrices(a2, b) {
+ const ae = a2.elements;
+ const be = b.elements;
+ const te = this.elements;
+ const a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12];
+ const a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13];
+ const a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14];
+ const a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15];
+ const b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12];
+ const b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13];
+ const b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14];
+ const b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15];
+ te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
+ te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
+ te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
+ te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
+ te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
+ te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
+ te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
+ te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
+ te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
+ te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
+ te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
+ te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
+ te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
+ te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
+ te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
+ te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
+ return this;
+ }
+ multiplyScalar(s) {
+ const te = this.elements;
+ te[0] *= s;
+ te[4] *= s;
+ te[8] *= s;
+ te[12] *= s;
+ te[1] *= s;
+ te[5] *= s;
+ te[9] *= s;
+ te[13] *= s;
+ te[2] *= s;
+ te[6] *= s;
+ te[10] *= s;
+ te[14] *= s;
+ te[3] *= s;
+ te[7] *= s;
+ te[11] *= s;
+ te[15] *= s;
+ return this;
+ }
+ determinant() {
+ const te = this.elements;
+ const n11 = te[0], n12 = te[4], n13 = te[8], n14 = te[12];
+ const n21 = te[1], n22 = te[5], n23 = te[9], n24 = te[13];
+ const n31 = te[2], n32 = te[6], n33 = te[10], n34 = te[14];
+ const n41 = te[3], n42 = te[7], n43 = te[11], n44 = te[15];
+ return n41 * (+n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34) + n42 * (+n11 * n23 * n34 - n11 * n24 * n33 + n14 * n21 * n33 - n13 * n21 * n34 + n13 * n24 * n31 - n14 * n23 * n31) + n43 * (+n11 * n24 * n32 - n11 * n22 * n34 - n14 * n21 * n32 + n12 * n21 * n34 + n14 * n22 * n31 - n12 * n24 * n31) + n44 * (-n13 * n22 * n31 - n11 * n23 * n32 + n11 * n22 * n33 + n13 * n21 * n32 - n12 * n21 * n33 + n12 * n23 * n31);
+ }
+ transpose() {
+ const te = this.elements;
+ let tmp2;
+ tmp2 = te[1];
+ te[1] = te[4];
+ te[4] = tmp2;
+ tmp2 = te[2];
+ te[2] = te[8];
+ te[8] = tmp2;
+ tmp2 = te[6];
+ te[6] = te[9];
+ te[9] = tmp2;
+ tmp2 = te[3];
+ te[3] = te[12];
+ te[12] = tmp2;
+ tmp2 = te[7];
+ te[7] = te[13];
+ te[13] = tmp2;
+ tmp2 = te[11];
+ te[11] = te[14];
+ te[14] = tmp2;
+ return this;
+ }
+ setPosition(x2, y2, z) {
+ const te = this.elements;
+ if (x2.isVector3) {
+ te[12] = x2.x;
+ te[13] = x2.y;
+ te[14] = x2.z;
+ } else {
+ te[12] = x2;
+ te[13] = y2;
+ te[14] = z;
+ }
+ return this;
+ }
+ invert() {
+ const te = this.elements, n11 = te[0], n21 = te[1], n31 = te[2], n41 = te[3], n12 = te[4], n22 = te[5], n32 = te[6], n42 = te[7], n13 = te[8], n23 = te[9], n33 = te[10], n43 = te[11], n14 = te[12], n24 = te[13], n34 = te[14], n44 = te[15], t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
+ const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
+ if (det === 0)
+ return this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ const detInv = 1 / det;
+ te[0] = t11 * detInv;
+ te[1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * detInv;
+ te[2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * detInv;
+ te[3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * detInv;
+ te[4] = t12 * detInv;
+ te[5] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * detInv;
+ te[6] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * detInv;
+ te[7] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * detInv;
+ te[8] = t13 * detInv;
+ te[9] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * detInv;
+ te[10] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * detInv;
+ te[11] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * detInv;
+ te[12] = t14 * detInv;
+ te[13] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * detInv;
+ te[14] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * detInv;
+ te[15] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * detInv;
+ return this;
+ }
+ scale(v) {
+ const te = this.elements;
+ const x2 = v.x, y2 = v.y, z = v.z;
+ te[0] *= x2;
+ te[4] *= y2;
+ te[8] *= z;
+ te[1] *= x2;
+ te[5] *= y2;
+ te[9] *= z;
+ te[2] *= x2;
+ te[6] *= y2;
+ te[10] *= z;
+ te[3] *= x2;
+ te[7] *= y2;
+ te[11] *= z;
+ return this;
+ }
+ getMaxScaleOnAxis() {
+ const te = this.elements;
+ const scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
+ const scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
+ const scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];
+ return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq));
+ }
+ makeTranslation(x2, y2, z) {
+ this.set(1, 0, 0, x2, 0, 1, 0, y2, 0, 0, 1, z, 0, 0, 0, 1);
+ return this;
+ }
+ makeRotationX(theta) {
+ const c2 = Math.cos(theta), s = Math.sin(theta);
+ this.set(1, 0, 0, 0, 0, c2, -s, 0, 0, s, c2, 0, 0, 0, 0, 1);
+ return this;
+ }
+ makeRotationY(theta) {
+ const c2 = Math.cos(theta), s = Math.sin(theta);
+ this.set(c2, 0, s, 0, 0, 1, 0, 0, -s, 0, c2, 0, 0, 0, 0, 1);
+ return this;
+ }
+ makeRotationZ(theta) {
+ const c2 = Math.cos(theta), s = Math.sin(theta);
+ this.set(c2, -s, 0, 0, s, c2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+ return this;
+ }
+ makeRotationAxis(axis, angle) {
+ const c2 = Math.cos(angle);
+ const s = Math.sin(angle);
+ const t = 1 - c2;
+ const x2 = axis.x, y2 = axis.y, z = axis.z;
+ const tx = t * x2, ty = t * y2;
+ this.set(tx * x2 + c2, tx * y2 - s * z, tx * z + s * y2, 0, tx * y2 + s * z, ty * y2 + c2, ty * z - s * x2, 0, tx * z - s * y2, ty * z + s * x2, t * z * z + c2, 0, 0, 0, 0, 1);
+ return this;
+ }
+ makeScale(x2, y2, z) {
+ this.set(x2, 0, 0, 0, 0, y2, 0, 0, 0, 0, z, 0, 0, 0, 0, 1);
+ return this;
+ }
+ makeShear(xy, xz, yx, yz, zx, zy) {
+ this.set(1, yx, zx, 0, xy, 1, zy, 0, xz, yz, 1, 0, 0, 0, 0, 1);
+ return this;
+ }
+ compose(position, quaternion, scale) {
+ const te = this.elements;
+ const x2 = quaternion._x, y2 = quaternion._y, z = quaternion._z, w = quaternion._w;
+ const x22 = x2 + x2, y22 = y2 + y2, z2 = z + z;
+ const xx = x2 * x22, xy = x2 * y22, xz = x2 * z2;
+ const yy = y2 * y22, yz = y2 * z2, zz = z * z2;
+ const wx = w * x22, wy = w * y22, wz = w * z2;
+ const sx = scale.x, sy = scale.y, sz = scale.z;
+ te[0] = (1 - (yy + zz)) * sx;
+ te[1] = (xy + wz) * sx;
+ te[2] = (xz - wy) * sx;
+ te[3] = 0;
+ te[4] = (xy - wz) * sy;
+ te[5] = (1 - (xx + zz)) * sy;
+ te[6] = (yz + wx) * sy;
+ te[7] = 0;
+ te[8] = (xz + wy) * sz;
+ te[9] = (yz - wx) * sz;
+ te[10] = (1 - (xx + yy)) * sz;
+ te[11] = 0;
+ te[12] = position.x;
+ te[13] = position.y;
+ te[14] = position.z;
+ te[15] = 1;
+ return this;
+ }
+ decompose(position, quaternion, scale) {
+ const te = this.elements;
+ let sx = _v1$52.set(te[0], te[1], te[2]).length();
+ const sy = _v1$52.set(te[4], te[5], te[6]).length();
+ const sz = _v1$52.set(te[8], te[9], te[10]).length();
+ const det = this.determinant();
+ if (det < 0)
+ sx = -sx;
+ position.x = te[12];
+ position.y = te[13];
+ position.z = te[14];
+ _m1$22.copy(this);
+ const invSX = 1 / sx;
+ const invSY = 1 / sy;
+ const invSZ = 1 / sz;
+ _m1$22.elements[0] *= invSX;
+ _m1$22.elements[1] *= invSX;
+ _m1$22.elements[2] *= invSX;
+ _m1$22.elements[4] *= invSY;
+ _m1$22.elements[5] *= invSY;
+ _m1$22.elements[6] *= invSY;
+ _m1$22.elements[8] *= invSZ;
+ _m1$22.elements[9] *= invSZ;
+ _m1$22.elements[10] *= invSZ;
+ quaternion.setFromRotationMatrix(_m1$22);
+ scale.x = sx;
+ scale.y = sy;
+ scale.z = sz;
+ return this;
+ }
+ makePerspective(left, right, top, bottom, near, far) {
+ const te = this.elements;
+ const x2 = 2 * near / (right - left);
+ const y2 = 2 * near / (top - bottom);
+ const a2 = (right + left) / (right - left);
+ const b = (top + bottom) / (top - bottom);
+ const c2 = -(far + near) / (far - near);
+ const d = -2 * far * near / (far - near);
+ te[0] = x2;
+ te[4] = 0;
+ te[8] = a2;
+ te[12] = 0;
+ te[1] = 0;
+ te[5] = y2;
+ te[9] = b;
+ te[13] = 0;
+ te[2] = 0;
+ te[6] = 0;
+ te[10] = c2;
+ te[14] = d;
+ te[3] = 0;
+ te[7] = 0;
+ te[11] = -1;
+ te[15] = 0;
+ return this;
+ }
+ makeOrthographic(left, right, top, bottom, near, far) {
+ const te = this.elements;
+ const w = 1 / (right - left);
+ const h = 1 / (top - bottom);
+ const p = 1 / (far - near);
+ const x2 = (right + left) * w;
+ const y2 = (top + bottom) * h;
+ const z = (far + near) * p;
+ te[0] = 2 * w;
+ te[4] = 0;
+ te[8] = 0;
+ te[12] = -x2;
+ te[1] = 0;
+ te[5] = 2 * h;
+ te[9] = 0;
+ te[13] = -y2;
+ te[2] = 0;
+ te[6] = 0;
+ te[10] = -2 * p;
+ te[14] = -z;
+ te[3] = 0;
+ te[7] = 0;
+ te[11] = 0;
+ te[15] = 1;
+ return this;
+ }
+ equals(matrix) {
+ const te = this.elements;
+ const me = matrix.elements;
+ for (let i = 0; i < 16; i++) {
+ if (te[i] !== me[i])
+ return false;
+ }
+ return true;
+ }
+ fromArray(array2, offset = 0) {
+ for (let i = 0; i < 16; i++) {
+ this.elements[i] = array2[i + offset];
+ }
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ const te = this.elements;
+ array2[offset] = te[0];
+ array2[offset + 1] = te[1];
+ array2[offset + 2] = te[2];
+ array2[offset + 3] = te[3];
+ array2[offset + 4] = te[4];
+ array2[offset + 5] = te[5];
+ array2[offset + 6] = te[6];
+ array2[offset + 7] = te[7];
+ array2[offset + 8] = te[8];
+ array2[offset + 9] = te[9];
+ array2[offset + 10] = te[10];
+ array2[offset + 11] = te[11];
+ array2[offset + 12] = te[12];
+ array2[offset + 13] = te[13];
+ array2[offset + 14] = te[14];
+ array2[offset + 15] = te[15];
+ return array2;
+ }
+ };
+ var _v1$52 = /* @__PURE__ */ new Vector32();
+ var _m1$22 = /* @__PURE__ */ new Matrix42();
+ var _zero2 = /* @__PURE__ */ new Vector32(0, 0, 0);
+ var _one2 = /* @__PURE__ */ new Vector32(1, 1, 1);
+ var _x2 = /* @__PURE__ */ new Vector32();
+ var _y2 = /* @__PURE__ */ new Vector32();
+ var _z2 = /* @__PURE__ */ new Vector32();
+ var _matrix$12 = /* @__PURE__ */ new Matrix42();
+ var _quaternion$32 = /* @__PURE__ */ new Quaternion2();
+ var Euler2 = class {
+ constructor(x2 = 0, y2 = 0, z = 0, order = Euler2.DefaultOrder) {
+ this.isEuler = true;
+ this._x = x2;
+ this._y = y2;
+ this._z = z;
+ this._order = order;
+ }
+ get x() {
+ return this._x;
+ }
+ set x(value) {
+ this._x = value;
+ this._onChangeCallback();
+ }
+ get y() {
+ return this._y;
+ }
+ set y(value) {
+ this._y = value;
+ this._onChangeCallback();
+ }
+ get z() {
+ return this._z;
+ }
+ set z(value) {
+ this._z = value;
+ this._onChangeCallback();
+ }
+ get order() {
+ return this._order;
+ }
+ set order(value) {
+ this._order = value;
+ this._onChangeCallback();
+ }
+ set(x2, y2, z, order = this._order) {
+ this._x = x2;
+ this._y = y2;
+ this._z = z;
+ this._order = order;
+ this._onChangeCallback();
+ return this;
+ }
+ clone() {
+ return new this.constructor(this._x, this._y, this._z, this._order);
+ }
+ copy(euler) {
+ this._x = euler._x;
+ this._y = euler._y;
+ this._z = euler._z;
+ this._order = euler._order;
+ this._onChangeCallback();
+ return this;
+ }
+ setFromRotationMatrix(m2, order = this._order, update = true) {
+ const te = m2.elements;
+ const m11 = te[0], m12 = te[4], m13 = te[8];
+ const m21 = te[1], m22 = te[5], m23 = te[9];
+ const m31 = te[2], m32 = te[6], m33 = te[10];
+ switch (order) {
+ case "XYZ":
+ this._y = Math.asin(clamp2(m13, -1, 1));
+ if (Math.abs(m13) < 0.9999999) {
+ this._x = Math.atan2(-m23, m33);
+ this._z = Math.atan2(-m12, m11);
+ } else {
+ this._x = Math.atan2(m32, m22);
+ this._z = 0;
+ }
+ break;
+ case "YXZ":
+ this._x = Math.asin(-clamp2(m23, -1, 1));
+ if (Math.abs(m23) < 0.9999999) {
+ this._y = Math.atan2(m13, m33);
+ this._z = Math.atan2(m21, m22);
+ } else {
+ this._y = Math.atan2(-m31, m11);
+ this._z = 0;
+ }
+ break;
+ case "ZXY":
+ this._x = Math.asin(clamp2(m32, -1, 1));
+ if (Math.abs(m32) < 0.9999999) {
+ this._y = Math.atan2(-m31, m33);
+ this._z = Math.atan2(-m12, m22);
+ } else {
+ this._y = 0;
+ this._z = Math.atan2(m21, m11);
+ }
+ break;
+ case "ZYX":
+ this._y = Math.asin(-clamp2(m31, -1, 1));
+ if (Math.abs(m31) < 0.9999999) {
+ this._x = Math.atan2(m32, m33);
+ this._z = Math.atan2(m21, m11);
+ } else {
+ this._x = 0;
+ this._z = Math.atan2(-m12, m22);
+ }
+ break;
+ case "YZX":
+ this._z = Math.asin(clamp2(m21, -1, 1));
+ if (Math.abs(m21) < 0.9999999) {
+ this._x = Math.atan2(-m23, m22);
+ this._y = Math.atan2(-m31, m11);
+ } else {
+ this._x = 0;
+ this._y = Math.atan2(m13, m33);
+ }
+ break;
+ case "XZY":
+ this._z = Math.asin(-clamp2(m12, -1, 1));
+ if (Math.abs(m12) < 0.9999999) {
+ this._x = Math.atan2(m32, m22);
+ this._y = Math.atan2(m13, m11);
+ } else {
+ this._x = Math.atan2(-m23, m33);
+ this._y = 0;
+ }
+ break;
+ default:
+ console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: " + order);
+ }
+ this._order = order;
+ if (update === true)
+ this._onChangeCallback();
+ return this;
+ }
+ setFromQuaternion(q, order, update) {
+ _matrix$12.makeRotationFromQuaternion(q);
+ return this.setFromRotationMatrix(_matrix$12, order, update);
+ }
+ setFromVector3(v, order = this._order) {
+ return this.set(v.x, v.y, v.z, order);
+ }
+ reorder(newOrder) {
+ _quaternion$32.setFromEuler(this);
+ return this.setFromQuaternion(_quaternion$32, newOrder);
+ }
+ equals(euler) {
+ return euler._x === this._x && euler._y === this._y && euler._z === this._z && euler._order === this._order;
+ }
+ fromArray(array2) {
+ this._x = array2[0];
+ this._y = array2[1];
+ this._z = array2[2];
+ if (array2[3] !== void 0)
+ this._order = array2[3];
+ this._onChangeCallback();
+ return this;
+ }
+ toArray(array2 = [], offset = 0) {
+ array2[offset] = this._x;
+ array2[offset + 1] = this._y;
+ array2[offset + 2] = this._z;
+ array2[offset + 3] = this._order;
+ return array2;
+ }
+ _onChange(callback) {
+ this._onChangeCallback = callback;
+ return this;
+ }
+ _onChangeCallback() {
+ }
+ *[Symbol.iterator]() {
+ yield this._x;
+ yield this._y;
+ yield this._z;
+ yield this._order;
+ }
+ toVector3() {
+ console.error("THREE.Euler: .toVector3() has been removed. Use Vector3.setFromEuler() instead");
+ }
+ };
+ Euler2.DefaultOrder = "XYZ";
+ Euler2.RotationOrders = ["XYZ", "YZX", "ZXY", "XZY", "YXZ", "ZYX"];
+ var Layers2 = class {
+ constructor() {
+ this.mask = 1 | 0;
+ }
+ set(channel) {
+ this.mask = (1 << channel | 0) >>> 0;
+ }
+ enable(channel) {
+ this.mask |= 1 << channel | 0;
+ }
+ enableAll() {
+ this.mask = 4294967295 | 0;
+ }
+ toggle(channel) {
+ this.mask ^= 1 << channel | 0;
+ }
+ disable(channel) {
+ this.mask &= ~(1 << channel | 0);
+ }
+ disableAll() {
+ this.mask = 0;
+ }
+ test(layers) {
+ return (this.mask & layers.mask) !== 0;
+ }
+ isEnabled(channel) {
+ return (this.mask & (1 << channel | 0)) !== 0;
+ }
+ };
+ var _object3DId2 = 0;
+ var _v1$42 = /* @__PURE__ */ new Vector32();
+ var _q12 = /* @__PURE__ */ new Quaternion2();
+ var _m1$12 = /* @__PURE__ */ new Matrix42();
+ var _target2 = /* @__PURE__ */ new Vector32();
+ var _position$32 = /* @__PURE__ */ new Vector32();
+ var _scale$22 = /* @__PURE__ */ new Vector32();
+ var _quaternion$22 = /* @__PURE__ */ new Quaternion2();
+ var _xAxis2 = /* @__PURE__ */ new Vector32(1, 0, 0);
+ var _yAxis2 = /* @__PURE__ */ new Vector32(0, 1, 0);
+ var _zAxis2 = /* @__PURE__ */ new Vector32(0, 0, 1);
+ var _addedEvent2 = {
+ type: "added"
+ };
+ var _removedEvent2 = {
+ type: "removed"
+ };
+ var Object3D2 = class extends EventDispatcher2 {
+ constructor() {
+ super();
+ this.isObject3D = true;
+ Object.defineProperty(this, "id", {
+ value: _object3DId2++
+ });
+ this.uuid = generateUUID2();
+ this.name = "";
+ this.type = "Object3D";
+ this.parent = null;
+ this.children = [];
+ this.up = Object3D2.DefaultUp.clone();
+ const position = new Vector32();
+ const rotation = new Euler2();
+ const quaternion = new Quaternion2();
+ const scale = new Vector32(1, 1, 1);
+ function onRotationChange() {
+ quaternion.setFromEuler(rotation, false);
+ }
+ function onQuaternionChange() {
+ rotation.setFromQuaternion(quaternion, void 0, false);
+ }
+ rotation._onChange(onRotationChange);
+ quaternion._onChange(onQuaternionChange);
+ Object.defineProperties(this, {
+ position: {
+ configurable: true,
+ enumerable: true,
+ value: position
+ },
+ rotation: {
+ configurable: true,
+ enumerable: true,
+ value: rotation
+ },
+ quaternion: {
+ configurable: true,
+ enumerable: true,
+ value: quaternion
+ },
+ scale: {
+ configurable: true,
+ enumerable: true,
+ value: scale
+ },
+ modelViewMatrix: {
+ value: new Matrix42()
+ },
+ normalMatrix: {
+ value: new Matrix32()
+ }
+ });
+ this.matrix = new Matrix42();
+ this.matrixWorld = new Matrix42();
+ this.matrixAutoUpdate = Object3D2.DefaultMatrixAutoUpdate;
+ this.matrixWorldNeedsUpdate = false;
+ this.layers = new Layers2();
+ this.visible = true;
+ this.castShadow = false;
+ this.receiveShadow = false;
+ this.frustumCulled = true;
+ this.renderOrder = 0;
+ this.animations = [];
+ this.userData = {};
+ }
+ onBeforeRender() {
+ }
+ onAfterRender() {
+ }
+ applyMatrix4(matrix) {
+ if (this.matrixAutoUpdate)
+ this.updateMatrix();
+ this.matrix.premultiply(matrix);
+ this.matrix.decompose(this.position, this.quaternion, this.scale);
+ }
+ applyQuaternion(q) {
+ this.quaternion.premultiply(q);
+ return this;
+ }
+ setRotationFromAxisAngle(axis, angle) {
+ this.quaternion.setFromAxisAngle(axis, angle);
+ }
+ setRotationFromEuler(euler) {
+ this.quaternion.setFromEuler(euler, true);
+ }
+ setRotationFromMatrix(m2) {
+ this.quaternion.setFromRotationMatrix(m2);
+ }
+ setRotationFromQuaternion(q) {
+ this.quaternion.copy(q);
+ }
+ rotateOnAxis(axis, angle) {
+ _q12.setFromAxisAngle(axis, angle);
+ this.quaternion.multiply(_q12);
+ return this;
+ }
+ rotateOnWorldAxis(axis, angle) {
+ _q12.setFromAxisAngle(axis, angle);
+ this.quaternion.premultiply(_q12);
+ return this;
+ }
+ rotateX(angle) {
+ return this.rotateOnAxis(_xAxis2, angle);
+ }
+ rotateY(angle) {
+ return this.rotateOnAxis(_yAxis2, angle);
+ }
+ rotateZ(angle) {
+ return this.rotateOnAxis(_zAxis2, angle);
+ }
+ translateOnAxis(axis, distance) {
+ _v1$42.copy(axis).applyQuaternion(this.quaternion);
+ this.position.add(_v1$42.multiplyScalar(distance));
+ return this;
+ }
+ translateX(distance) {
+ return this.translateOnAxis(_xAxis2, distance);
+ }
+ translateY(distance) {
+ return this.translateOnAxis(_yAxis2, distance);
+ }
+ translateZ(distance) {
+ return this.translateOnAxis(_zAxis2, distance);
+ }
+ localToWorld(vector) {
+ return vector.applyMatrix4(this.matrixWorld);
+ }
+ worldToLocal(vector) {
+ return vector.applyMatrix4(_m1$12.copy(this.matrixWorld).invert());
+ }
+ lookAt(x2, y2, z) {
+ if (x2.isVector3) {
+ _target2.copy(x2);
+ } else {
+ _target2.set(x2, y2, z);
+ }
+ const parent = this.parent;
+ this.updateWorldMatrix(true, false);
+ _position$32.setFromMatrixPosition(this.matrixWorld);
+ if (this.isCamera || this.isLight) {
+ _m1$12.lookAt(_position$32, _target2, this.up);
+ } else {
+ _m1$12.lookAt(_target2, _position$32, this.up);
+ }
+ this.quaternion.setFromRotationMatrix(_m1$12);
+ if (parent) {
+ _m1$12.extractRotation(parent.matrixWorld);
+ _q12.setFromRotationMatrix(_m1$12);
+ this.quaternion.premultiply(_q12.invert());
+ }
+ }
+ add(object) {
+ if (arguments.length > 1) {
+ for (let i = 0; i < arguments.length; i++) {
+ this.add(arguments[i]);
+ }
+ return this;
+ }
+ if (object === this) {
+ console.error("THREE.Object3D.add: object can't be added as a child of itself.", object);
+ return this;
+ }
+ if (object && object.isObject3D) {
+ if (object.parent !== null) {
+ object.parent.remove(object);
+ }
+ object.parent = this;
+ this.children.push(object);
+ object.dispatchEvent(_addedEvent2);
+ } else {
+ console.error("THREE.Object3D.add: object not an instance of THREE.Object3D.", object);
+ }
+ return this;
+ }
+ remove(object) {
+ if (arguments.length > 1) {
+ for (let i = 0; i < arguments.length; i++) {
+ this.remove(arguments[i]);
+ }
+ return this;
+ }
+ const index2 = this.children.indexOf(object);
+ if (index2 !== -1) {
+ object.parent = null;
+ this.children.splice(index2, 1);
+ object.dispatchEvent(_removedEvent2);
+ }
+ return this;
+ }
+ removeFromParent() {
+ const parent = this.parent;
+ if (parent !== null) {
+ parent.remove(this);
+ }
+ return this;
+ }
+ clear() {
+ for (let i = 0; i < this.children.length; i++) {
+ const object = this.children[i];
+ object.parent = null;
+ object.dispatchEvent(_removedEvent2);
+ }
+ this.children.length = 0;
+ return this;
+ }
+ attach(object) {
+ this.updateWorldMatrix(true, false);
+ _m1$12.copy(this.matrixWorld).invert();
+ if (object.parent !== null) {
+ object.parent.updateWorldMatrix(true, false);
+ _m1$12.multiply(object.parent.matrixWorld);
+ }
+ object.applyMatrix4(_m1$12);
+ this.add(object);
+ object.updateWorldMatrix(false, true);
+ return this;
+ }
+ getObjectById(id3) {
+ return this.getObjectByProperty("id", id3);
+ }
+ getObjectByName(name) {
+ return this.getObjectByProperty("name", name);
+ }
+ getObjectByProperty(name, value) {
+ if (this[name] === value)
+ return this;
+ for (let i = 0, l = this.children.length; i < l; i++) {
+ const child = this.children[i];
+ const object = child.getObjectByProperty(name, value);
+ if (object !== void 0) {
+ return object;
+ }
+ }
+ return void 0;
+ }
+ getWorldPosition(target) {
+ this.updateWorldMatrix(true, false);
+ return target.setFromMatrixPosition(this.matrixWorld);
+ }
+ getWorldQuaternion(target) {
+ this.updateWorldMatrix(true, false);
+ this.matrixWorld.decompose(_position$32, target, _scale$22);
+ return target;
+ }
+ getWorldScale(target) {
+ this.updateWorldMatrix(true, false);
+ this.matrixWorld.decompose(_position$32, _quaternion$22, target);
+ return target;
+ }
+ getWorldDirection(target) {
+ this.updateWorldMatrix(true, false);
+ const e = this.matrixWorld.elements;
+ return target.set(e[8], e[9], e[10]).normalize();
+ }
+ raycast() {
+ }
+ traverse(callback) {
+ callback(this);
+ const children2 = this.children;
+ for (let i = 0, l = children2.length; i < l; i++) {
+ children2[i].traverse(callback);
+ }
+ }
+ traverseVisible(callback) {
+ if (this.visible === false)
+ return;
+ callback(this);
+ const children2 = this.children;
+ for (let i = 0, l = children2.length; i < l; i++) {
+ children2[i].traverseVisible(callback);
+ }
+ }
+ traverseAncestors(callback) {
+ const parent = this.parent;
+ if (parent !== null) {
+ callback(parent);
+ parent.traverseAncestors(callback);
+ }
+ }
+ updateMatrix() {
+ this.matrix.compose(this.position, this.quaternion, this.scale);
+ this.matrixWorldNeedsUpdate = true;
+ }
+ updateMatrixWorld(force) {
+ if (this.matrixAutoUpdate)
+ this.updateMatrix();
+ if (this.matrixWorldNeedsUpdate || force) {
+ if (this.parent === null) {
+ this.matrixWorld.copy(this.matrix);
+ } else {
+ this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix);
+ }
+ this.matrixWorldNeedsUpdate = false;
+ force = true;
+ }
+ const children2 = this.children;
+ for (let i = 0, l = children2.length; i < l; i++) {
+ children2[i].updateMatrixWorld(force);
+ }
+ }
+ updateWorldMatrix(updateParents, updateChildren) {
+ const parent = this.parent;
+ if (updateParents === true && parent !== null) {
+ parent.updateWorldMatrix(true, false);
+ }
+ if (this.matrixAutoUpdate)
+ this.updateMatrix();
+ if (this.parent === null) {
+ this.matrixWorld.copy(this.matrix);
+ } else {
+ this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix);
+ }
+ if (updateChildren === true) {
+ const children2 = this.children;
+ for (let i = 0, l = children2.length; i < l; i++) {
+ children2[i].updateWorldMatrix(false, true);
+ }
+ }
+ }
+ toJSON(meta) {
+ const isRootObject = meta === void 0 || typeof meta === "string";
+ const output = {};
+ if (isRootObject) {
+ meta = {
+ geometries: {},
+ materials: {},
+ textures: {},
+ images: {},
+ shapes: {},
+ skeletons: {},
+ animations: {},
+ nodes: {}
+ };
+ output.metadata = {
+ version: 4.5,
+ type: "Object",
+ generator: "Object3D.toJSON"
+ };
+ }
+ const object = {};
+ object.uuid = this.uuid;
+ object.type = this.type;
+ if (this.name !== "")
+ object.name = this.name;
+ if (this.castShadow === true)
+ object.castShadow = true;
+ if (this.receiveShadow === true)
+ object.receiveShadow = true;
+ if (this.visible === false)
+ object.visible = false;
+ if (this.frustumCulled === false)
+ object.frustumCulled = false;
+ if (this.renderOrder !== 0)
+ object.renderOrder = this.renderOrder;
+ if (JSON.stringify(this.userData) !== "{}")
+ object.userData = this.userData;
+ object.layers = this.layers.mask;
+ object.matrix = this.matrix.toArray();
+ if (this.matrixAutoUpdate === false)
+ object.matrixAutoUpdate = false;
+ if (this.isInstancedMesh) {
+ object.type = "InstancedMesh";
+ object.count = this.count;
+ object.instanceMatrix = this.instanceMatrix.toJSON();
+ if (this.instanceColor !== null)
+ object.instanceColor = this.instanceColor.toJSON();
+ }
+ function serialize(library, element) {
+ if (library[element.uuid] === void 0) {
+ library[element.uuid] = element.toJSON(meta);
+ }
+ return element.uuid;
+ }
+ if (this.isScene) {
+ if (this.background) {
+ if (this.background.isColor) {
+ object.background = this.background.toJSON();
+ } else if (this.background.isTexture) {
+ object.background = this.background.toJSON(meta).uuid;
+ }
+ }
+ if (this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true) {
+ object.environment = this.environment.toJSON(meta).uuid;
+ }
+ } else if (this.isMesh || this.isLine || this.isPoints) {
+ object.geometry = serialize(meta.geometries, this.geometry);
+ const parameters = this.geometry.parameters;
+ if (parameters !== void 0 && parameters.shapes !== void 0) {
+ const shapes = parameters.shapes;
+ if (Array.isArray(shapes)) {
+ for (let i = 0, l = shapes.length; i < l; i++) {
+ const shape = shapes[i];
+ serialize(meta.shapes, shape);
+ }
+ } else {
+ serialize(meta.shapes, shapes);
+ }
+ }
+ }
+ if (this.isSkinnedMesh) {
+ object.bindMode = this.bindMode;
+ object.bindMatrix = this.bindMatrix.toArray();
+ if (this.skeleton !== void 0) {
+ serialize(meta.skeletons, this.skeleton);
+ object.skeleton = this.skeleton.uuid;
+ }
+ }
+ if (this.material !== void 0) {
+ if (Array.isArray(this.material)) {
+ const uuids = [];
+ for (let i = 0, l = this.material.length; i < l; i++) {
+ uuids.push(serialize(meta.materials, this.material[i]));
+ }
+ object.material = uuids;
+ } else {
+ object.material = serialize(meta.materials, this.material);
+ }
+ }
+ if (this.children.length > 0) {
+ object.children = [];
+ for (let i = 0; i < this.children.length; i++) {
+ object.children.push(this.children[i].toJSON(meta).object);
+ }
+ }
+ if (this.animations.length > 0) {
+ object.animations = [];
+ for (let i = 0; i < this.animations.length; i++) {
+ const animation = this.animations[i];
+ object.animations.push(serialize(meta.animations, animation));
+ }
+ }
+ if (isRootObject) {
+ const geometries = extractFromCache(meta.geometries);
+ const materials = extractFromCache(meta.materials);
+ const textures = extractFromCache(meta.textures);
+ const images = extractFromCache(meta.images);
+ const shapes = extractFromCache(meta.shapes);
+ const skeletons = extractFromCache(meta.skeletons);
+ const animations = extractFromCache(meta.animations);
+ const nodes = extractFromCache(meta.nodes);
+ if (geometries.length > 0)
+ output.geometries = geometries;
+ if (materials.length > 0)
+ output.materials = materials;
+ if (textures.length > 0)
+ output.textures = textures;
+ if (images.length > 0)
+ output.images = images;
+ if (shapes.length > 0)
+ output.shapes = shapes;
+ if (skeletons.length > 0)
+ output.skeletons = skeletons;
+ if (animations.length > 0)
+ output.animations = animations;
+ if (nodes.length > 0)
+ output.nodes = nodes;
+ }
+ output.object = object;
+ return output;
+ function extractFromCache(cache2) {
+ const values = [];
+ for (const key in cache2) {
+ const data = cache2[key];
+ delete data.metadata;
+ values.push(data);
+ }
+ return values;
+ }
+ }
+ clone(recursive) {
+ return new this.constructor().copy(this, recursive);
+ }
+ copy(source, recursive = true) {
+ this.name = source.name;
+ this.up.copy(source.up);
+ this.position.copy(source.position);
+ this.rotation.order = source.rotation.order;
+ this.quaternion.copy(source.quaternion);
+ this.scale.copy(source.scale);
+ this.matrix.copy(source.matrix);
+ this.matrixWorld.copy(source.matrixWorld);
+ this.matrixAutoUpdate = source.matrixAutoUpdate;
+ this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;
+ this.layers.mask = source.layers.mask;
+ this.visible = source.visible;
+ this.castShadow = source.castShadow;
+ this.receiveShadow = source.receiveShadow;
+ this.frustumCulled = source.frustumCulled;
+ this.renderOrder = source.renderOrder;
+ this.userData = JSON.parse(JSON.stringify(source.userData));
+ if (recursive === true) {
+ for (let i = 0; i < source.children.length; i++) {
+ const child = source.children[i];
+ this.add(child.clone());
+ }
+ }
+ return this;
+ }
+ };
+ Object3D2.DefaultUp = /* @__PURE__ */ new Vector32(0, 1, 0);
+ Object3D2.DefaultMatrixAutoUpdate = true;
+ var _v0$12 = /* @__PURE__ */ new Vector32();
+ var _v1$32 = /* @__PURE__ */ new Vector32();
+ var _v2$22 = /* @__PURE__ */ new Vector32();
+ var _v3$12 = /* @__PURE__ */ new Vector32();
+ var _vab2 = /* @__PURE__ */ new Vector32();
+ var _vac2 = /* @__PURE__ */ new Vector32();
+ var _vbc2 = /* @__PURE__ */ new Vector32();
+ var _vap2 = /* @__PURE__ */ new Vector32();
+ var _vbp2 = /* @__PURE__ */ new Vector32();
+ var _vcp2 = /* @__PURE__ */ new Vector32();
+ var Triangle2 = class {
+ constructor(a2 = new Vector32(), b = new Vector32(), c2 = new Vector32()) {
+ this.a = a2;
+ this.b = b;
+ this.c = c2;
+ }
+ static getNormal(a2, b, c2, target) {
+ target.subVectors(c2, b);
+ _v0$12.subVectors(a2, b);
+ target.cross(_v0$12);
+ const targetLengthSq = target.lengthSq();
+ if (targetLengthSq > 0) {
+ return target.multiplyScalar(1 / Math.sqrt(targetLengthSq));
+ }
+ return target.set(0, 0, 0);
+ }
+ static getBarycoord(point, a2, b, c2, target) {
+ _v0$12.subVectors(c2, a2);
+ _v1$32.subVectors(b, a2);
+ _v2$22.subVectors(point, a2);
+ const dot00 = _v0$12.dot(_v0$12);
+ const dot01 = _v0$12.dot(_v1$32);
+ const dot02 = _v0$12.dot(_v2$22);
+ const dot11 = _v1$32.dot(_v1$32);
+ const dot12 = _v1$32.dot(_v2$22);
+ const denom = dot00 * dot11 - dot01 * dot01;
+ if (denom === 0) {
+ return target.set(-2, -1, -1);
+ }
+ const invDenom = 1 / denom;
+ const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
+ const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
+ return target.set(1 - u - v, v, u);
+ }
+ static containsPoint(point, a2, b, c2) {
+ this.getBarycoord(point, a2, b, c2, _v3$12);
+ return _v3$12.x >= 0 && _v3$12.y >= 0 && _v3$12.x + _v3$12.y <= 1;
+ }
+ static getUV(point, p1, p2, p3, uv1, uv2, uv3, target) {
+ this.getBarycoord(point, p1, p2, p3, _v3$12);
+ target.set(0, 0);
+ target.addScaledVector(uv1, _v3$12.x);
+ target.addScaledVector(uv2, _v3$12.y);
+ target.addScaledVector(uv3, _v3$12.z);
+ return target;
+ }
+ static isFrontFacing(a2, b, c2, direction) {
+ _v0$12.subVectors(c2, b);
+ _v1$32.subVectors(a2, b);
+ return _v0$12.cross(_v1$32).dot(direction) < 0 ? true : false;
+ }
+ set(a2, b, c2) {
+ this.a.copy(a2);
+ this.b.copy(b);
+ this.c.copy(c2);
+ return this;
+ }
+ setFromPointsAndIndices(points, i0, i1, i2) {
+ this.a.copy(points[i0]);
+ this.b.copy(points[i1]);
+ this.c.copy(points[i2]);
+ return this;
+ }
+ setFromAttributeAndIndices(attribute, i0, i1, i2) {
+ this.a.fromBufferAttribute(attribute, i0);
+ this.b.fromBufferAttribute(attribute, i1);
+ this.c.fromBufferAttribute(attribute, i2);
+ return this;
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ copy(triangle) {
+ this.a.copy(triangle.a);
+ this.b.copy(triangle.b);
+ this.c.copy(triangle.c);
+ return this;
+ }
+ getArea() {
+ _v0$12.subVectors(this.c, this.b);
+ _v1$32.subVectors(this.a, this.b);
+ return _v0$12.cross(_v1$32).length() * 0.5;
+ }
+ getMidpoint(target) {
+ return target.addVectors(this.a, this.b).add(this.c).multiplyScalar(1 / 3);
+ }
+ getNormal(target) {
+ return Triangle2.getNormal(this.a, this.b, this.c, target);
+ }
+ getPlane(target) {
+ return target.setFromCoplanarPoints(this.a, this.b, this.c);
+ }
+ getBarycoord(point, target) {
+ return Triangle2.getBarycoord(point, this.a, this.b, this.c, target);
+ }
+ getUV(point, uv1, uv2, uv3, target) {
+ return Triangle2.getUV(point, this.a, this.b, this.c, uv1, uv2, uv3, target);
+ }
+ containsPoint(point) {
+ return Triangle2.containsPoint(point, this.a, this.b, this.c);
+ }
+ isFrontFacing(direction) {
+ return Triangle2.isFrontFacing(this.a, this.b, this.c, direction);
+ }
+ intersectsBox(box) {
+ return box.intersectsTriangle(this);
+ }
+ closestPointToPoint(p, target) {
+ const a2 = this.a, b = this.b, c2 = this.c;
+ let v, w;
+ _vab2.subVectors(b, a2);
+ _vac2.subVectors(c2, a2);
+ _vap2.subVectors(p, a2);
+ const d1 = _vab2.dot(_vap2);
+ const d2 = _vac2.dot(_vap2);
+ if (d1 <= 0 && d2 <= 0) {
+ return target.copy(a2);
+ }
+ _vbp2.subVectors(p, b);
+ const d3 = _vab2.dot(_vbp2);
+ const d4 = _vac2.dot(_vbp2);
+ if (d3 >= 0 && d4 <= d3) {
+ return target.copy(b);
+ }
+ const vc = d1 * d4 - d3 * d2;
+ if (vc <= 0 && d1 >= 0 && d3 <= 0) {
+ v = d1 / (d1 - d3);
+ return target.copy(a2).addScaledVector(_vab2, v);
+ }
+ _vcp2.subVectors(p, c2);
+ const d5 = _vab2.dot(_vcp2);
+ const d6 = _vac2.dot(_vcp2);
+ if (d6 >= 0 && d5 <= d6) {
+ return target.copy(c2);
+ }
+ const vb = d5 * d2 - d1 * d6;
+ if (vb <= 0 && d2 >= 0 && d6 <= 0) {
+ w = d2 / (d2 - d6);
+ return target.copy(a2).addScaledVector(_vac2, w);
+ }
+ const va = d3 * d6 - d5 * d4;
+ if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) {
+ _vbc2.subVectors(c2, b);
+ w = (d4 - d3) / (d4 - d3 + (d5 - d6));
+ return target.copy(b).addScaledVector(_vbc2, w);
+ }
+ const denom = 1 / (va + vb + vc);
+ v = vb * denom;
+ w = vc * denom;
+ return target.copy(a2).addScaledVector(_vab2, v).addScaledVector(_vac2, w);
+ }
+ equals(triangle) {
+ return triangle.a.equals(this.a) && triangle.b.equals(this.b) && triangle.c.equals(this.c);
+ }
+ };
+ var materialId2 = 0;
+ var Material2 = class extends EventDispatcher2 {
+ constructor() {
+ super();
+ this.isMaterial = true;
+ Object.defineProperty(this, "id", {
+ value: materialId2++
+ });
+ this.uuid = generateUUID2();
+ this.name = "";
+ this.type = "Material";
+ this.blending = NormalBlending2;
+ this.side = FrontSide2;
+ this.vertexColors = false;
+ this.opacity = 1;
+ this.transparent = false;
+ this.blendSrc = SrcAlphaFactor2;
+ this.blendDst = OneMinusSrcAlphaFactor2;
+ this.blendEquation = AddEquation2;
+ this.blendSrcAlpha = null;
+ this.blendDstAlpha = null;
+ this.blendEquationAlpha = null;
+ this.depthFunc = LessEqualDepth2;
+ this.depthTest = true;
+ this.depthWrite = true;
+ this.stencilWriteMask = 255;
+ this.stencilFunc = AlwaysStencilFunc2;
+ this.stencilRef = 0;
+ this.stencilFuncMask = 255;
+ this.stencilFail = KeepStencilOp2;
+ this.stencilZFail = KeepStencilOp2;
+ this.stencilZPass = KeepStencilOp2;
+ this.stencilWrite = false;
+ this.clippingPlanes = null;
+ this.clipIntersection = false;
+ this.clipShadows = false;
+ this.shadowSide = null;
+ this.colorWrite = true;
+ this.precision = null;
+ this.polygonOffset = false;
+ this.polygonOffsetFactor = 0;
+ this.polygonOffsetUnits = 0;
+ this.dithering = false;
+ this.alphaToCoverage = false;
+ this.premultipliedAlpha = false;
+ this.visible = true;
+ this.toneMapped = true;
+ this.userData = {};
+ this.version = 0;
+ this._alphaTest = 0;
+ }
+ get alphaTest() {
+ return this._alphaTest;
+ }
+ set alphaTest(value) {
+ if (this._alphaTest > 0 !== value > 0) {
+ this.version++;
+ }
+ this._alphaTest = value;
+ }
+ onBuild() {
+ }
+ onBeforeRender() {
+ }
+ onBeforeCompile() {
+ }
+ customProgramCacheKey() {
+ return this.onBeforeCompile.toString();
+ }
+ setValues(values) {
+ if (values === void 0)
+ return;
+ for (const key in values) {
+ const newValue = values[key];
+ if (newValue === void 0) {
+ console.warn("THREE.Material: '" + key + "' parameter is undefined.");
+ continue;
+ }
+ if (key === "shading") {
+ console.warn("THREE." + this.type + ": .shading has been removed. Use the boolean .flatShading instead.");
+ this.flatShading = newValue === FlatShading2 ? true : false;
+ continue;
+ }
+ const currentValue = this[key];
+ if (currentValue === void 0) {
+ console.warn("THREE." + this.type + ": '" + key + "' is not a property of this material.");
+ continue;
+ }
+ if (currentValue && currentValue.isColor) {
+ currentValue.set(newValue);
+ } else if (currentValue && currentValue.isVector3 && newValue && newValue.isVector3) {
+ currentValue.copy(newValue);
+ } else {
+ this[key] = newValue;
+ }
+ }
+ }
+ toJSON(meta) {
+ const isRootObject = meta === void 0 || typeof meta === "string";
+ if (isRootObject) {
+ meta = {
+ textures: {},
+ images: {}
+ };
+ }
+ const data = {
+ metadata: {
+ version: 4.5,
+ type: "Material",
+ generator: "Material.toJSON"
+ }
+ };
+ data.uuid = this.uuid;
+ data.type = this.type;
+ if (this.name !== "")
+ data.name = this.name;
+ if (this.color && this.color.isColor)
+ data.color = this.color.getHex();
+ if (this.roughness !== void 0)
+ data.roughness = this.roughness;
+ if (this.metalness !== void 0)
+ data.metalness = this.metalness;
+ if (this.sheen !== void 0)
+ data.sheen = this.sheen;
+ if (this.sheenColor && this.sheenColor.isColor)
+ data.sheenColor = this.sheenColor.getHex();
+ if (this.sheenRoughness !== void 0)
+ data.sheenRoughness = this.sheenRoughness;
+ if (this.emissive && this.emissive.isColor)
+ data.emissive = this.emissive.getHex();
+ if (this.emissiveIntensity && this.emissiveIntensity !== 1)
+ data.emissiveIntensity = this.emissiveIntensity;
+ if (this.specular && this.specular.isColor)
+ data.specular = this.specular.getHex();
+ if (this.specularIntensity !== void 0)
+ data.specularIntensity = this.specularIntensity;
+ if (this.specularColor && this.specularColor.isColor)
+ data.specularColor = this.specularColor.getHex();
+ if (this.shininess !== void 0)
+ data.shininess = this.shininess;
+ if (this.clearcoat !== void 0)
+ data.clearcoat = this.clearcoat;
+ if (this.clearcoatRoughness !== void 0)
+ data.clearcoatRoughness = this.clearcoatRoughness;
+ if (this.clearcoatMap && this.clearcoatMap.isTexture) {
+ data.clearcoatMap = this.clearcoatMap.toJSON(meta).uuid;
+ }
+ if (this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture) {
+ data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON(meta).uuid;
+ }
+ if (this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture) {
+ data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON(meta).uuid;
+ data.clearcoatNormalScale = this.clearcoatNormalScale.toArray();
+ }
+ if (this.iridescence !== void 0)
+ data.iridescence = this.iridescence;
+ if (this.iridescenceIOR !== void 0)
+ data.iridescenceIOR = this.iridescenceIOR;
+ if (this.iridescenceThicknessRange !== void 0)
+ data.iridescenceThicknessRange = this.iridescenceThicknessRange;
+ if (this.iridescenceMap && this.iridescenceMap.isTexture) {
+ data.iridescenceMap = this.iridescenceMap.toJSON(meta).uuid;
+ }
+ if (this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture) {
+ data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON(meta).uuid;
+ }
+ if (this.map && this.map.isTexture)
+ data.map = this.map.toJSON(meta).uuid;
+ if (this.matcap && this.matcap.isTexture)
+ data.matcap = this.matcap.toJSON(meta).uuid;
+ if (this.alphaMap && this.alphaMap.isTexture)
+ data.alphaMap = this.alphaMap.toJSON(meta).uuid;
+ if (this.lightMap && this.lightMap.isTexture) {
+ data.lightMap = this.lightMap.toJSON(meta).uuid;
+ data.lightMapIntensity = this.lightMapIntensity;
+ }
+ if (this.aoMap && this.aoMap.isTexture) {
+ data.aoMap = this.aoMap.toJSON(meta).uuid;
+ data.aoMapIntensity = this.aoMapIntensity;
+ }
+ if (this.bumpMap && this.bumpMap.isTexture) {
+ data.bumpMap = this.bumpMap.toJSON(meta).uuid;
+ data.bumpScale = this.bumpScale;
+ }
+ if (this.normalMap && this.normalMap.isTexture) {
+ data.normalMap = this.normalMap.toJSON(meta).uuid;
+ data.normalMapType = this.normalMapType;
+ data.normalScale = this.normalScale.toArray();
+ }
+ if (this.displacementMap && this.displacementMap.isTexture) {
+ data.displacementMap = this.displacementMap.toJSON(meta).uuid;
+ data.displacementScale = this.displacementScale;
+ data.displacementBias = this.displacementBias;
+ }
+ if (this.roughnessMap && this.roughnessMap.isTexture)
+ data.roughnessMap = this.roughnessMap.toJSON(meta).uuid;
+ if (this.metalnessMap && this.metalnessMap.isTexture)
+ data.metalnessMap = this.metalnessMap.toJSON(meta).uuid;
+ if (this.emissiveMap && this.emissiveMap.isTexture)
+ data.emissiveMap = this.emissiveMap.toJSON(meta).uuid;
+ if (this.specularMap && this.specularMap.isTexture)
+ data.specularMap = this.specularMap.toJSON(meta).uuid;
+ if (this.specularIntensityMap && this.specularIntensityMap.isTexture)
+ data.specularIntensityMap = this.specularIntensityMap.toJSON(meta).uuid;
+ if (this.specularColorMap && this.specularColorMap.isTexture)
+ data.specularColorMap = this.specularColorMap.toJSON(meta).uuid;
+ if (this.envMap && this.envMap.isTexture) {
+ data.envMap = this.envMap.toJSON(meta).uuid;
+ if (this.combine !== void 0)
+ data.combine = this.combine;
+ }
+ if (this.envMapIntensity !== void 0)
+ data.envMapIntensity = this.envMapIntensity;
+ if (this.reflectivity !== void 0)
+ data.reflectivity = this.reflectivity;
+ if (this.refractionRatio !== void 0)
+ data.refractionRatio = this.refractionRatio;
+ if (this.gradientMap && this.gradientMap.isTexture) {
+ data.gradientMap = this.gradientMap.toJSON(meta).uuid;
+ }
+ if (this.transmission !== void 0)
+ data.transmission = this.transmission;
+ if (this.transmissionMap && this.transmissionMap.isTexture)
+ data.transmissionMap = this.transmissionMap.toJSON(meta).uuid;
+ if (this.thickness !== void 0)
+ data.thickness = this.thickness;
+ if (this.thicknessMap && this.thicknessMap.isTexture)
+ data.thicknessMap = this.thicknessMap.toJSON(meta).uuid;
+ if (this.attenuationDistance !== void 0)
+ data.attenuationDistance = this.attenuationDistance;
+ if (this.attenuationColor !== void 0)
+ data.attenuationColor = this.attenuationColor.getHex();
+ if (this.size !== void 0)
+ data.size = this.size;
+ if (this.shadowSide !== null)
+ data.shadowSide = this.shadowSide;
+ if (this.sizeAttenuation !== void 0)
+ data.sizeAttenuation = this.sizeAttenuation;
+ if (this.blending !== NormalBlending2)
+ data.blending = this.blending;
+ if (this.side !== FrontSide2)
+ data.side = this.side;
+ if (this.vertexColors)
+ data.vertexColors = true;
+ if (this.opacity < 1)
+ data.opacity = this.opacity;
+ if (this.transparent === true)
+ data.transparent = this.transparent;
+ data.depthFunc = this.depthFunc;
+ data.depthTest = this.depthTest;
+ data.depthWrite = this.depthWrite;
+ data.colorWrite = this.colorWrite;
+ data.stencilWrite = this.stencilWrite;
+ data.stencilWriteMask = this.stencilWriteMask;
+ data.stencilFunc = this.stencilFunc;
+ data.stencilRef = this.stencilRef;
+ data.stencilFuncMask = this.stencilFuncMask;
+ data.stencilFail = this.stencilFail;
+ data.stencilZFail = this.stencilZFail;
+ data.stencilZPass = this.stencilZPass;
+ if (this.rotation !== void 0 && this.rotation !== 0)
+ data.rotation = this.rotation;
+ if (this.polygonOffset === true)
+ data.polygonOffset = true;
+ if (this.polygonOffsetFactor !== 0)
+ data.polygonOffsetFactor = this.polygonOffsetFactor;
+ if (this.polygonOffsetUnits !== 0)
+ data.polygonOffsetUnits = this.polygonOffsetUnits;
+ if (this.linewidth !== void 0 && this.linewidth !== 1)
+ data.linewidth = this.linewidth;
+ if (this.dashSize !== void 0)
+ data.dashSize = this.dashSize;
+ if (this.gapSize !== void 0)
+ data.gapSize = this.gapSize;
+ if (this.scale !== void 0)
+ data.scale = this.scale;
+ if (this.dithering === true)
+ data.dithering = true;
+ if (this.alphaTest > 0)
+ data.alphaTest = this.alphaTest;
+ if (this.alphaToCoverage === true)
+ data.alphaToCoverage = this.alphaToCoverage;
+ if (this.premultipliedAlpha === true)
+ data.premultipliedAlpha = this.premultipliedAlpha;
+ if (this.wireframe === true)
+ data.wireframe = this.wireframe;
+ if (this.wireframeLinewidth > 1)
+ data.wireframeLinewidth = this.wireframeLinewidth;
+ if (this.wireframeLinecap !== "round")
+ data.wireframeLinecap = this.wireframeLinecap;
+ if (this.wireframeLinejoin !== "round")
+ data.wireframeLinejoin = this.wireframeLinejoin;
+ if (this.flatShading === true)
+ data.flatShading = this.flatShading;
+ if (this.visible === false)
+ data.visible = false;
+ if (this.toneMapped === false)
+ data.toneMapped = false;
+ if (this.fog === false)
+ data.fog = false;
+ if (JSON.stringify(this.userData) !== "{}")
+ data.userData = this.userData;
+ function extractFromCache(cache2) {
+ const values = [];
+ for (const key in cache2) {
+ const data2 = cache2[key];
+ delete data2.metadata;
+ values.push(data2);
+ }
+ return values;
+ }
+ if (isRootObject) {
+ const textures = extractFromCache(meta.textures);
+ const images = extractFromCache(meta.images);
+ if (textures.length > 0)
+ data.textures = textures;
+ if (images.length > 0)
+ data.images = images;
+ }
+ return data;
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ copy(source) {
+ this.name = source.name;
+ this.blending = source.blending;
+ this.side = source.side;
+ this.vertexColors = source.vertexColors;
+ this.opacity = source.opacity;
+ this.transparent = source.transparent;
+ this.blendSrc = source.blendSrc;
+ this.blendDst = source.blendDst;
+ this.blendEquation = source.blendEquation;
+ this.blendSrcAlpha = source.blendSrcAlpha;
+ this.blendDstAlpha = source.blendDstAlpha;
+ this.blendEquationAlpha = source.blendEquationAlpha;
+ this.depthFunc = source.depthFunc;
+ this.depthTest = source.depthTest;
+ this.depthWrite = source.depthWrite;
+ this.stencilWriteMask = source.stencilWriteMask;
+ this.stencilFunc = source.stencilFunc;
+ this.stencilRef = source.stencilRef;
+ this.stencilFuncMask = source.stencilFuncMask;
+ this.stencilFail = source.stencilFail;
+ this.stencilZFail = source.stencilZFail;
+ this.stencilZPass = source.stencilZPass;
+ this.stencilWrite = source.stencilWrite;
+ const srcPlanes = source.clippingPlanes;
+ let dstPlanes = null;
+ if (srcPlanes !== null) {
+ const n = srcPlanes.length;
+ dstPlanes = new Array(n);
+ for (let i = 0; i !== n; ++i) {
+ dstPlanes[i] = srcPlanes[i].clone();
+ }
+ }
+ this.clippingPlanes = dstPlanes;
+ this.clipIntersection = source.clipIntersection;
+ this.clipShadows = source.clipShadows;
+ this.shadowSide = source.shadowSide;
+ this.colorWrite = source.colorWrite;
+ this.precision = source.precision;
+ this.polygonOffset = source.polygonOffset;
+ this.polygonOffsetFactor = source.polygonOffsetFactor;
+ this.polygonOffsetUnits = source.polygonOffsetUnits;
+ this.dithering = source.dithering;
+ this.alphaTest = source.alphaTest;
+ this.alphaToCoverage = source.alphaToCoverage;
+ this.premultipliedAlpha = source.premultipliedAlpha;
+ this.visible = source.visible;
+ this.toneMapped = source.toneMapped;
+ this.userData = JSON.parse(JSON.stringify(source.userData));
+ return this;
+ }
+ dispose() {
+ this.dispatchEvent({
+ type: "dispose"
+ });
+ }
+ set needsUpdate(value) {
+ if (value === true)
+ this.version++;
+ }
+ };
+ var MeshBasicMaterial2 = class extends Material2 {
+ constructor(parameters) {
+ super();
+ this.isMeshBasicMaterial = true;
+ this.type = "MeshBasicMaterial";
+ this.color = new Color3(16777215);
+ this.map = null;
+ this.lightMap = null;
+ this.lightMapIntensity = 1;
+ this.aoMap = null;
+ this.aoMapIntensity = 1;
+ this.specularMap = null;
+ this.alphaMap = null;
+ this.envMap = null;
+ this.combine = MultiplyOperation2;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = "round";
+ this.wireframeLinejoin = "round";
+ this.fog = true;
+ this.setValues(parameters);
+ }
+ copy(source) {
+ super.copy(source);
+ this.color.copy(source.color);
+ this.map = source.map;
+ this.lightMap = source.lightMap;
+ this.lightMapIntensity = source.lightMapIntensity;
+ this.aoMap = source.aoMap;
+ this.aoMapIntensity = source.aoMapIntensity;
+ this.specularMap = source.specularMap;
+ this.alphaMap = source.alphaMap;
+ this.envMap = source.envMap;
+ this.combine = source.combine;
+ this.reflectivity = source.reflectivity;
+ this.refractionRatio = source.refractionRatio;
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+ this.wireframeLinecap = source.wireframeLinecap;
+ this.wireframeLinejoin = source.wireframeLinejoin;
+ this.fog = source.fog;
+ return this;
+ }
+ };
+ var _vector$92 = /* @__PURE__ */ new Vector32();
+ var _vector2$12 = /* @__PURE__ */ new Vector22();
+ var BufferAttribute2 = class {
+ constructor(array2, itemSize, normalized) {
+ if (Array.isArray(array2)) {
+ throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");
+ }
+ this.isBufferAttribute = true;
+ this.name = "";
+ this.array = array2;
+ this.itemSize = itemSize;
+ this.count = array2 !== void 0 ? array2.length / itemSize : 0;
+ this.normalized = normalized === true;
+ this.usage = StaticDrawUsage2;
+ this.updateRange = {
+ offset: 0,
+ count: -1
+ };
+ this.version = 0;
+ }
+ onUploadCallback() {
+ }
+ set needsUpdate(value) {
+ if (value === true)
+ this.version++;
+ }
+ setUsage(value) {
+ this.usage = value;
+ return this;
+ }
+ copy(source) {
+ this.name = source.name;
+ this.array = new source.array.constructor(source.array);
+ this.itemSize = source.itemSize;
+ this.count = source.count;
+ this.normalized = source.normalized;
+ this.usage = source.usage;
+ return this;
+ }
+ copyAt(index1, attribute, index2) {
+ index1 *= this.itemSize;
+ index2 *= attribute.itemSize;
+ for (let i = 0, l = this.itemSize; i < l; i++) {
+ this.array[index1 + i] = attribute.array[index2 + i];
+ }
+ return this;
+ }
+ copyArray(array2) {
+ this.array.set(array2);
+ return this;
+ }
+ copyColorsArray(colors) {
+ const array2 = this.array;
+ let offset = 0;
+ for (let i = 0, l = colors.length; i < l; i++) {
+ let color2 = colors[i];
+ if (color2 === void 0) {
+ console.warn("THREE.BufferAttribute.copyColorsArray(): color is undefined", i);
+ color2 = new Color3();
+ }
+ array2[offset++] = color2.r;
+ array2[offset++] = color2.g;
+ array2[offset++] = color2.b;
+ }
+ return this;
+ }
+ copyVector2sArray(vectors) {
+ const array2 = this.array;
+ let offset = 0;
+ for (let i = 0, l = vectors.length; i < l; i++) {
+ let vector = vectors[i];
+ if (vector === void 0) {
+ console.warn("THREE.BufferAttribute.copyVector2sArray(): vector is undefined", i);
+ vector = new Vector22();
+ }
+ array2[offset++] = vector.x;
+ array2[offset++] = vector.y;
+ }
+ return this;
+ }
+ copyVector3sArray(vectors) {
+ const array2 = this.array;
+ let offset = 0;
+ for (let i = 0, l = vectors.length; i < l; i++) {
+ let vector = vectors[i];
+ if (vector === void 0) {
+ console.warn("THREE.BufferAttribute.copyVector3sArray(): vector is undefined", i);
+ vector = new Vector32();
+ }
+ array2[offset++] = vector.x;
+ array2[offset++] = vector.y;
+ array2[offset++] = vector.z;
+ }
+ return this;
+ }
+ copyVector4sArray(vectors) {
+ const array2 = this.array;
+ let offset = 0;
+ for (let i = 0, l = vectors.length; i < l; i++) {
+ let vector = vectors[i];
+ if (vector === void 0) {
+ console.warn("THREE.BufferAttribute.copyVector4sArray(): vector is undefined", i);
+ vector = new Vector42();
+ }
+ array2[offset++] = vector.x;
+ array2[offset++] = vector.y;
+ array2[offset++] = vector.z;
+ array2[offset++] = vector.w;
+ }
+ return this;
+ }
+ applyMatrix3(m2) {
+ if (this.itemSize === 2) {
+ for (let i = 0, l = this.count; i < l; i++) {
+ _vector2$12.fromBufferAttribute(this, i);
+ _vector2$12.applyMatrix3(m2);
+ this.setXY(i, _vector2$12.x, _vector2$12.y);
+ }
+ } else if (this.itemSize === 3) {
+ for (let i = 0, l = this.count; i < l; i++) {
+ _vector$92.fromBufferAttribute(this, i);
+ _vector$92.applyMatrix3(m2);
+ this.setXYZ(i, _vector$92.x, _vector$92.y, _vector$92.z);
+ }
+ }
+ return this;
+ }
+ applyMatrix4(m2) {
+ for (let i = 0, l = this.count; i < l; i++) {
+ _vector$92.fromBufferAttribute(this, i);
+ _vector$92.applyMatrix4(m2);
+ this.setXYZ(i, _vector$92.x, _vector$92.y, _vector$92.z);
+ }
+ return this;
+ }
+ applyNormalMatrix(m2) {
+ for (let i = 0, l = this.count; i < l; i++) {
+ _vector$92.fromBufferAttribute(this, i);
+ _vector$92.applyNormalMatrix(m2);
+ this.setXYZ(i, _vector$92.x, _vector$92.y, _vector$92.z);
+ }
+ return this;
+ }
+ transformDirection(m2) {
+ for (let i = 0, l = this.count; i < l; i++) {
+ _vector$92.fromBufferAttribute(this, i);
+ _vector$92.transformDirection(m2);
+ this.setXYZ(i, _vector$92.x, _vector$92.y, _vector$92.z);
+ }
+ return this;
+ }
+ set(value, offset = 0) {
+ this.array.set(value, offset);
+ return this;
+ }
+ getX(index2) {
+ return this.array[index2 * this.itemSize];
+ }
+ setX(index2, x2) {
+ this.array[index2 * this.itemSize] = x2;
+ return this;
+ }
+ getY(index2) {
+ return this.array[index2 * this.itemSize + 1];
+ }
+ setY(index2, y2) {
+ this.array[index2 * this.itemSize + 1] = y2;
+ return this;
+ }
+ getZ(index2) {
+ return this.array[index2 * this.itemSize + 2];
+ }
+ setZ(index2, z) {
+ this.array[index2 * this.itemSize + 2] = z;
+ return this;
+ }
+ getW(index2) {
+ return this.array[index2 * this.itemSize + 3];
+ }
+ setW(index2, w) {
+ this.array[index2 * this.itemSize + 3] = w;
+ return this;
+ }
+ setXY(index2, x2, y2) {
+ index2 *= this.itemSize;
+ this.array[index2 + 0] = x2;
+ this.array[index2 + 1] = y2;
+ return this;
+ }
+ setXYZ(index2, x2, y2, z) {
+ index2 *= this.itemSize;
+ this.array[index2 + 0] = x2;
+ this.array[index2 + 1] = y2;
+ this.array[index2 + 2] = z;
+ return this;
+ }
+ setXYZW(index2, x2, y2, z, w) {
+ index2 *= this.itemSize;
+ this.array[index2 + 0] = x2;
+ this.array[index2 + 1] = y2;
+ this.array[index2 + 2] = z;
+ this.array[index2 + 3] = w;
+ return this;
+ }
+ onUpload(callback) {
+ this.onUploadCallback = callback;
+ return this;
+ }
+ clone() {
+ return new this.constructor(this.array, this.itemSize).copy(this);
+ }
+ toJSON() {
+ const data = {
+ itemSize: this.itemSize,
+ type: this.array.constructor.name,
+ array: Array.from(this.array),
+ normalized: this.normalized
+ };
+ if (this.name !== "")
+ data.name = this.name;
+ if (this.usage !== StaticDrawUsage2)
+ data.usage = this.usage;
+ if (this.updateRange.offset !== 0 || this.updateRange.count !== -1)
+ data.updateRange = this.updateRange;
+ return data;
+ }
+ };
+ var Int8BufferAttribute = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Int8Array(array2), itemSize, normalized);
+ }
+ };
+ var Uint8BufferAttribute = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Uint8Array(array2), itemSize, normalized);
+ }
+ };
+ var Uint8ClampedBufferAttribute = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Uint8ClampedArray(array2), itemSize, normalized);
+ }
+ };
+ var Int16BufferAttribute = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Int16Array(array2), itemSize, normalized);
+ }
+ };
+ var Uint16BufferAttribute2 = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Uint16Array(array2), itemSize, normalized);
+ }
+ };
+ var Int32BufferAttribute = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Int32Array(array2), itemSize, normalized);
+ }
+ };
+ var Uint32BufferAttribute2 = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Uint32Array(array2), itemSize, normalized);
+ }
+ };
+ var Float16BufferAttribute = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Uint16Array(array2), itemSize, normalized);
+ this.isFloat16BufferAttribute = true;
+ }
+ };
+ var Float32BufferAttribute2 = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Float32Array(array2), itemSize, normalized);
+ }
+ };
+ var Float64BufferAttribute = class extends BufferAttribute2 {
+ constructor(array2, itemSize, normalized) {
+ super(new Float64Array(array2), itemSize, normalized);
+ }
+ };
+ var _id$12 = 0;
+ var _m12 = /* @__PURE__ */ new Matrix42();
+ var _obj2 = /* @__PURE__ */ new Object3D2();
+ var _offset2 = /* @__PURE__ */ new Vector32();
+ var _box$12 = /* @__PURE__ */ new Box32();
+ var _boxMorphTargets2 = /* @__PURE__ */ new Box32();
+ var _vector$82 = /* @__PURE__ */ new Vector32();
+ var BufferGeometry2 = class extends EventDispatcher2 {
+ constructor() {
+ super();
+ this.isBufferGeometry = true;
+ Object.defineProperty(this, "id", {
+ value: _id$12++
+ });
+ this.uuid = generateUUID2();
+ this.name = "";
+ this.type = "BufferGeometry";
+ this.index = null;
+ this.attributes = {};
+ this.morphAttributes = {};
+ this.morphTargetsRelative = false;
+ this.groups = [];
+ this.boundingBox = null;
+ this.boundingSphere = null;
+ this.drawRange = {
+ start: 0,
+ count: Infinity
+ };
+ this.userData = {};
+ }
+ getIndex() {
+ return this.index;
+ }
+ setIndex(index2) {
+ if (Array.isArray(index2)) {
+ this.index = new (arrayNeedsUint322(index2) ? Uint32BufferAttribute2 : Uint16BufferAttribute2)(index2, 1);
+ } else {
+ this.index = index2;
+ }
+ return this;
+ }
+ getAttribute(name) {
+ return this.attributes[name];
+ }
+ setAttribute(name, attribute) {
+ this.attributes[name] = attribute;
+ return this;
+ }
+ deleteAttribute(name) {
+ delete this.attributes[name];
+ return this;
+ }
+ hasAttribute(name) {
+ return this.attributes[name] !== void 0;
+ }
+ addGroup(start2, count, materialIndex = 0) {
+ this.groups.push({
+ start: start2,
+ count,
+ materialIndex
+ });
+ }
+ clearGroups() {
+ this.groups = [];
+ }
+ setDrawRange(start2, count) {
+ this.drawRange.start = start2;
+ this.drawRange.count = count;
+ }
+ applyMatrix4(matrix) {
+ const position = this.attributes.position;
+ if (position !== void 0) {
+ position.applyMatrix4(matrix);
+ position.needsUpdate = true;
+ }
+ const normal = this.attributes.normal;
+ if (normal !== void 0) {
+ const normalMatrix = new Matrix32().getNormalMatrix(matrix);
+ normal.applyNormalMatrix(normalMatrix);
+ normal.needsUpdate = true;
+ }
+ const tangent = this.attributes.tangent;
+ if (tangent !== void 0) {
+ tangent.transformDirection(matrix);
+ tangent.needsUpdate = true;
+ }
+ if (this.boundingBox !== null) {
+ this.computeBoundingBox();
+ }
+ if (this.boundingSphere !== null) {
+ this.computeBoundingSphere();
+ }
+ return this;
+ }
+ applyQuaternion(q) {
+ _m12.makeRotationFromQuaternion(q);
+ this.applyMatrix4(_m12);
+ return this;
+ }
+ rotateX(angle) {
+ _m12.makeRotationX(angle);
+ this.applyMatrix4(_m12);
+ return this;
+ }
+ rotateY(angle) {
+ _m12.makeRotationY(angle);
+ this.applyMatrix4(_m12);
+ return this;
+ }
+ rotateZ(angle) {
+ _m12.makeRotationZ(angle);
+ this.applyMatrix4(_m12);
+ return this;
+ }
+ translate(x2, y2, z) {
+ _m12.makeTranslation(x2, y2, z);
+ this.applyMatrix4(_m12);
+ return this;
+ }
+ scale(x2, y2, z) {
+ _m12.makeScale(x2, y2, z);
+ this.applyMatrix4(_m12);
+ return this;
+ }
+ lookAt(vector) {
+ _obj2.lookAt(vector);
+ _obj2.updateMatrix();
+ this.applyMatrix4(_obj2.matrix);
+ return this;
+ }
+ center() {
+ this.computeBoundingBox();
+ this.boundingBox.getCenter(_offset2).negate();
+ this.translate(_offset2.x, _offset2.y, _offset2.z);
+ return this;
+ }
+ setFromPoints(points) {
+ const position = [];
+ for (let i = 0, l = points.length; i < l; i++) {
+ const point = points[i];
+ position.push(point.x, point.y, point.z || 0);
+ }
+ this.setAttribute("position", new Float32BufferAttribute2(position, 3));
+ return this;
+ }
+ computeBoundingBox() {
+ if (this.boundingBox === null) {
+ this.boundingBox = new Box32();
+ }
+ const position = this.attributes.position;
+ const morphAttributesPosition = this.morphAttributes.position;
+ if (position && position.isGLBufferAttribute) {
+ console.error('THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this);
+ this.boundingBox.set(new Vector32(-Infinity, -Infinity, -Infinity), new Vector32(Infinity, Infinity, Infinity));
+ return;
+ }
+ if (position !== void 0) {
+ this.boundingBox.setFromBufferAttribute(position);
+ if (morphAttributesPosition) {
+ for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {
+ const morphAttribute = morphAttributesPosition[i];
+ _box$12.setFromBufferAttribute(morphAttribute);
+ if (this.morphTargetsRelative) {
+ _vector$82.addVectors(this.boundingBox.min, _box$12.min);
+ this.boundingBox.expandByPoint(_vector$82);
+ _vector$82.addVectors(this.boundingBox.max, _box$12.max);
+ this.boundingBox.expandByPoint(_vector$82);
+ } else {
+ this.boundingBox.expandByPoint(_box$12.min);
+ this.boundingBox.expandByPoint(_box$12.max);
+ }
+ }
+ }
+ } else {
+ this.boundingBox.makeEmpty();
+ }
+ if (isNaN(this.boundingBox.min.x) || isNaN(this.boundingBox.min.y) || isNaN(this.boundingBox.min.z)) {
+ console.error('THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this);
+ }
+ }
+ computeBoundingSphere() {
+ if (this.boundingSphere === null) {
+ this.boundingSphere = new Sphere2();
+ }
+ const position = this.attributes.position;
+ const morphAttributesPosition = this.morphAttributes.position;
+ if (position && position.isGLBufferAttribute) {
+ console.error('THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this);
+ this.boundingSphere.set(new Vector32(), Infinity);
+ return;
+ }
+ if (position) {
+ const center = this.boundingSphere.center;
+ _box$12.setFromBufferAttribute(position);
+ if (morphAttributesPosition) {
+ for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {
+ const morphAttribute = morphAttributesPosition[i];
+ _boxMorphTargets2.setFromBufferAttribute(morphAttribute);
+ if (this.morphTargetsRelative) {
+ _vector$82.addVectors(_box$12.min, _boxMorphTargets2.min);
+ _box$12.expandByPoint(_vector$82);
+ _vector$82.addVectors(_box$12.max, _boxMorphTargets2.max);
+ _box$12.expandByPoint(_vector$82);
+ } else {
+ _box$12.expandByPoint(_boxMorphTargets2.min);
+ _box$12.expandByPoint(_boxMorphTargets2.max);
+ }
+ }
+ }
+ _box$12.getCenter(center);
+ let maxRadiusSq = 0;
+ for (let i = 0, il = position.count; i < il; i++) {
+ _vector$82.fromBufferAttribute(position, i);
+ maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector$82));
+ }
+ if (morphAttributesPosition) {
+ for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {
+ const morphAttribute = morphAttributesPosition[i];
+ const morphTargetsRelative = this.morphTargetsRelative;
+ for (let j = 0, jl = morphAttribute.count; j < jl; j++) {
+ _vector$82.fromBufferAttribute(morphAttribute, j);
+ if (morphTargetsRelative) {
+ _offset2.fromBufferAttribute(position, j);
+ _vector$82.add(_offset2);
+ }
+ maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector$82));
+ }
+ }
+ }
+ this.boundingSphere.radius = Math.sqrt(maxRadiusSq);
+ if (isNaN(this.boundingSphere.radius)) {
+ console.error('THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this);
+ }
+ }
+ }
+ computeTangents() {
+ const index2 = this.index;
+ const attributes = this.attributes;
+ if (index2 === null || attributes.position === void 0 || attributes.normal === void 0 || attributes.uv === void 0) {
+ console.error("THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)");
+ return;
+ }
+ const indices = index2.array;
+ const positions = attributes.position.array;
+ const normals = attributes.normal.array;
+ const uvs = attributes.uv.array;
+ const nVertices = positions.length / 3;
+ if (this.hasAttribute("tangent") === false) {
+ this.setAttribute("tangent", new BufferAttribute2(new Float32Array(4 * nVertices), 4));
+ }
+ const tangents = this.getAttribute("tangent").array;
+ const tan1 = [], tan2 = [];
+ for (let i = 0; i < nVertices; i++) {
+ tan1[i] = new Vector32();
+ tan2[i] = new Vector32();
+ }
+ const vA = new Vector32(), vB = new Vector32(), vC = new Vector32(), uvA = new Vector22(), uvB = new Vector22(), uvC = new Vector22(), sdir = new Vector32(), tdir = new Vector32();
+ function handleTriangle(a2, b, c2) {
+ vA.fromArray(positions, a2 * 3);
+ vB.fromArray(positions, b * 3);
+ vC.fromArray(positions, c2 * 3);
+ uvA.fromArray(uvs, a2 * 2);
+ uvB.fromArray(uvs, b * 2);
+ uvC.fromArray(uvs, c2 * 2);
+ vB.sub(vA);
+ vC.sub(vA);
+ uvB.sub(uvA);
+ uvC.sub(uvA);
+ const r = 1 / (uvB.x * uvC.y - uvC.x * uvB.y);
+ if (!isFinite(r))
+ return;
+ sdir.copy(vB).multiplyScalar(uvC.y).addScaledVector(vC, -uvB.y).multiplyScalar(r);
+ tdir.copy(vC).multiplyScalar(uvB.x).addScaledVector(vB, -uvC.x).multiplyScalar(r);
+ tan1[a2].add(sdir);
+ tan1[b].add(sdir);
+ tan1[c2].add(sdir);
+ tan2[a2].add(tdir);
+ tan2[b].add(tdir);
+ tan2[c2].add(tdir);
+ }
+ let groups = this.groups;
+ if (groups.length === 0) {
+ groups = [{
+ start: 0,
+ count: indices.length
+ }];
+ }
+ for (let i = 0, il = groups.length; i < il; ++i) {
+ const group = groups[i];
+ const start2 = group.start;
+ const count = group.count;
+ for (let j = start2, jl = start2 + count; j < jl; j += 3) {
+ handleTriangle(indices[j + 0], indices[j + 1], indices[j + 2]);
+ }
+ }
+ const tmp2 = new Vector32(), tmp22 = new Vector32();
+ const n = new Vector32(), n2 = new Vector32();
+ function handleVertex(v) {
+ n.fromArray(normals, v * 3);
+ n2.copy(n);
+ const t = tan1[v];
+ tmp2.copy(t);
+ tmp2.sub(n.multiplyScalar(n.dot(t))).normalize();
+ tmp22.crossVectors(n2, t);
+ const test = tmp22.dot(tan2[v]);
+ const w = test < 0 ? -1 : 1;
+ tangents[v * 4] = tmp2.x;
+ tangents[v * 4 + 1] = tmp2.y;
+ tangents[v * 4 + 2] = tmp2.z;
+ tangents[v * 4 + 3] = w;
+ }
+ for (let i = 0, il = groups.length; i < il; ++i) {
+ const group = groups[i];
+ const start2 = group.start;
+ const count = group.count;
+ for (let j = start2, jl = start2 + count; j < jl; j += 3) {
+ handleVertex(indices[j + 0]);
+ handleVertex(indices[j + 1]);
+ handleVertex(indices[j + 2]);
+ }
+ }
+ }
+ computeVertexNormals() {
+ const index2 = this.index;
+ const positionAttribute = this.getAttribute("position");
+ if (positionAttribute !== void 0) {
+ let normalAttribute = this.getAttribute("normal");
+ if (normalAttribute === void 0) {
+ normalAttribute = new BufferAttribute2(new Float32Array(positionAttribute.count * 3), 3);
+ this.setAttribute("normal", normalAttribute);
+ } else {
+ for (let i = 0, il = normalAttribute.count; i < il; i++) {
+ normalAttribute.setXYZ(i, 0, 0, 0);
+ }
+ }
+ const pA = new Vector32(), pB = new Vector32(), pC = new Vector32();
+ const nA = new Vector32(), nB = new Vector32(), nC = new Vector32();
+ const cb = new Vector32(), ab = new Vector32();
+ if (index2) {
+ for (let i = 0, il = index2.count; i < il; i += 3) {
+ const vA = index2.getX(i + 0);
+ const vB = index2.getX(i + 1);
+ const vC = index2.getX(i + 2);
+ pA.fromBufferAttribute(positionAttribute, vA);
+ pB.fromBufferAttribute(positionAttribute, vB);
+ pC.fromBufferAttribute(positionAttribute, vC);
+ cb.subVectors(pC, pB);
+ ab.subVectors(pA, pB);
+ cb.cross(ab);
+ nA.fromBufferAttribute(normalAttribute, vA);
+ nB.fromBufferAttribute(normalAttribute, vB);
+ nC.fromBufferAttribute(normalAttribute, vC);
+ nA.add(cb);
+ nB.add(cb);
+ nC.add(cb);
+ normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z);
+ normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z);
+ normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z);
+ }
+ } else {
+ for (let i = 0, il = positionAttribute.count; i < il; i += 3) {
+ pA.fromBufferAttribute(positionAttribute, i + 0);
+ pB.fromBufferAttribute(positionAttribute, i + 1);
+ pC.fromBufferAttribute(positionAttribute, i + 2);
+ cb.subVectors(pC, pB);
+ ab.subVectors(pA, pB);
+ cb.cross(ab);
+ normalAttribute.setXYZ(i + 0, cb.x, cb.y, cb.z);
+ normalAttribute.setXYZ(i + 1, cb.x, cb.y, cb.z);
+ normalAttribute.setXYZ(i + 2, cb.x, cb.y, cb.z);
+ }
+ }
+ this.normalizeNormals();
+ normalAttribute.needsUpdate = true;
+ }
+ }
+ merge(geometry, offset) {
+ if (!(geometry && geometry.isBufferGeometry)) {
+ console.error("THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.", geometry);
+ return;
+ }
+ if (offset === void 0) {
+ offset = 0;
+ console.warn("THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.");
+ }
+ const attributes = this.attributes;
+ for (const key in attributes) {
+ if (geometry.attributes[key] === void 0)
+ continue;
+ const attribute1 = attributes[key];
+ const attributeArray1 = attribute1.array;
+ const attribute2 = geometry.attributes[key];
+ const attributeArray2 = attribute2.array;
+ const attributeOffset = attribute2.itemSize * offset;
+ const length = Math.min(attributeArray2.length, attributeArray1.length - attributeOffset);
+ for (let i = 0, j = attributeOffset; i < length; i++, j++) {
+ attributeArray1[j] = attributeArray2[i];
+ }
+ }
+ return this;
+ }
+ normalizeNormals() {
+ const normals = this.attributes.normal;
+ for (let i = 0, il = normals.count; i < il; i++) {
+ _vector$82.fromBufferAttribute(normals, i);
+ _vector$82.normalize();
+ normals.setXYZ(i, _vector$82.x, _vector$82.y, _vector$82.z);
+ }
+ }
+ toNonIndexed() {
+ function convertBufferAttribute(attribute, indices2) {
+ const array2 = attribute.array;
+ const itemSize = attribute.itemSize;
+ const normalized = attribute.normalized;
+ const array22 = new array2.constructor(indices2.length * itemSize);
+ let index2 = 0, index22 = 0;
+ for (let i = 0, l = indices2.length; i < l; i++) {
+ if (attribute.isInterleavedBufferAttribute) {
+ index2 = indices2[i] * attribute.data.stride + attribute.offset;
+ } else {
+ index2 = indices2[i] * itemSize;
+ }
+ for (let j = 0; j < itemSize; j++) {
+ array22[index22++] = array2[index2++];
+ }
+ }
+ return new BufferAttribute2(array22, itemSize, normalized);
+ }
+ if (this.index === null) {
+ console.warn("THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.");
+ return this;
+ }
+ const geometry2 = new BufferGeometry2();
+ const indices = this.index.array;
+ const attributes = this.attributes;
+ for (const name in attributes) {
+ const attribute = attributes[name];
+ const newAttribute = convertBufferAttribute(attribute, indices);
+ geometry2.setAttribute(name, newAttribute);
+ }
+ const morphAttributes = this.morphAttributes;
+ for (const name in morphAttributes) {
+ const morphArray = [];
+ const morphAttribute = morphAttributes[name];
+ for (let i = 0, il = morphAttribute.length; i < il; i++) {
+ const attribute = morphAttribute[i];
+ const newAttribute = convertBufferAttribute(attribute, indices);
+ morphArray.push(newAttribute);
+ }
+ geometry2.morphAttributes[name] = morphArray;
+ }
+ geometry2.morphTargetsRelative = this.morphTargetsRelative;
+ const groups = this.groups;
+ for (let i = 0, l = groups.length; i < l; i++) {
+ const group = groups[i];
+ geometry2.addGroup(group.start, group.count, group.materialIndex);
+ }
+ return geometry2;
+ }
+ toJSON() {
+ const data = {
+ metadata: {
+ version: 4.5,
+ type: "BufferGeometry",
+ generator: "BufferGeometry.toJSON"
+ }
+ };
+ data.uuid = this.uuid;
+ data.type = this.type;
+ if (this.name !== "")
+ data.name = this.name;
+ if (Object.keys(this.userData).length > 0)
+ data.userData = this.userData;
+ if (this.parameters !== void 0) {
+ const parameters = this.parameters;
+ for (const key in parameters) {
+ if (parameters[key] !== void 0)
+ data[key] = parameters[key];
+ }
+ return data;
+ }
+ data.data = {
+ attributes: {}
+ };
+ const index2 = this.index;
+ if (index2 !== null) {
+ data.data.index = {
+ type: index2.array.constructor.name,
+ array: Array.prototype.slice.call(index2.array)
+ };
+ }
+ const attributes = this.attributes;
+ for (const key in attributes) {
+ const attribute = attributes[key];
+ data.data.attributes[key] = attribute.toJSON(data.data);
+ }
+ const morphAttributes = {};
+ let hasMorphAttributes = false;
+ for (const key in this.morphAttributes) {
+ const attributeArray = this.morphAttributes[key];
+ const array2 = [];
+ for (let i = 0, il = attributeArray.length; i < il; i++) {
+ const attribute = attributeArray[i];
+ array2.push(attribute.toJSON(data.data));
+ }
+ if (array2.length > 0) {
+ morphAttributes[key] = array2;
+ hasMorphAttributes = true;
+ }
+ }
+ if (hasMorphAttributes) {
+ data.data.morphAttributes = morphAttributes;
+ data.data.morphTargetsRelative = this.morphTargetsRelative;
+ }
+ const groups = this.groups;
+ if (groups.length > 0) {
+ data.data.groups = JSON.parse(JSON.stringify(groups));
+ }
+ const boundingSphere = this.boundingSphere;
+ if (boundingSphere !== null) {
+ data.data.boundingSphere = {
+ center: boundingSphere.center.toArray(),
+ radius: boundingSphere.radius
+ };
+ }
+ return data;
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ copy(source) {
+ this.index = null;
+ this.attributes = {};
+ this.morphAttributes = {};
+ this.groups = [];
+ this.boundingBox = null;
+ this.boundingSphere = null;
+ const data = {};
+ this.name = source.name;
+ const index2 = source.index;
+ if (index2 !== null) {
+ this.setIndex(index2.clone(data));
+ }
+ const attributes = source.attributes;
+ for (const name in attributes) {
+ const attribute = attributes[name];
+ this.setAttribute(name, attribute.clone(data));
+ }
+ const morphAttributes = source.morphAttributes;
+ for (const name in morphAttributes) {
+ const array2 = [];
+ const morphAttribute = morphAttributes[name];
+ for (let i = 0, l = morphAttribute.length; i < l; i++) {
+ array2.push(morphAttribute[i].clone(data));
+ }
+ this.morphAttributes[name] = array2;
+ }
+ this.morphTargetsRelative = source.morphTargetsRelative;
+ const groups = source.groups;
+ for (let i = 0, l = groups.length; i < l; i++) {
+ const group = groups[i];
+ this.addGroup(group.start, group.count, group.materialIndex);
+ }
+ const boundingBox = source.boundingBox;
+ if (boundingBox !== null) {
+ this.boundingBox = boundingBox.clone();
+ }
+ const boundingSphere = source.boundingSphere;
+ if (boundingSphere !== null) {
+ this.boundingSphere = boundingSphere.clone();
+ }
+ this.drawRange.start = source.drawRange.start;
+ this.drawRange.count = source.drawRange.count;
+ this.userData = source.userData;
+ if (source.parameters !== void 0)
+ this.parameters = Object.assign({}, source.parameters);
+ return this;
+ }
+ dispose() {
+ this.dispatchEvent({
+ type: "dispose"
+ });
+ }
+ };
+ var _inverseMatrix$22 = /* @__PURE__ */ new Matrix42();
+ var _ray$22 = /* @__PURE__ */ new Ray2();
+ var _sphere$32 = /* @__PURE__ */ new Sphere2();
+ var _vA$12 = /* @__PURE__ */ new Vector32();
+ var _vB$12 = /* @__PURE__ */ new Vector32();
+ var _vC$12 = /* @__PURE__ */ new Vector32();
+ var _tempA2 = /* @__PURE__ */ new Vector32();
+ var _tempB2 = /* @__PURE__ */ new Vector32();
+ var _tempC2 = /* @__PURE__ */ new Vector32();
+ var _morphA2 = /* @__PURE__ */ new Vector32();
+ var _morphB2 = /* @__PURE__ */ new Vector32();
+ var _morphC2 = /* @__PURE__ */ new Vector32();
+ var _uvA$12 = /* @__PURE__ */ new Vector22();
+ var _uvB$12 = /* @__PURE__ */ new Vector22();
+ var _uvC$12 = /* @__PURE__ */ new Vector22();
+ var _intersectionPoint2 = /* @__PURE__ */ new Vector32();
+ var _intersectionPointWorld2 = /* @__PURE__ */ new Vector32();
+ var Mesh2 = class extends Object3D2 {
+ constructor(geometry = new BufferGeometry2(), material = new MeshBasicMaterial2()) {
+ super();
+ this.isMesh = true;
+ this.type = "Mesh";
+ this.geometry = geometry;
+ this.material = material;
+ this.updateMorphTargets();
+ }
+ copy(source, recursive) {
+ super.copy(source, recursive);
+ if (source.morphTargetInfluences !== void 0) {
+ this.morphTargetInfluences = source.morphTargetInfluences.slice();
+ }
+ if (source.morphTargetDictionary !== void 0) {
+ this.morphTargetDictionary = Object.assign({}, source.morphTargetDictionary);
+ }
+ this.material = source.material;
+ this.geometry = source.geometry;
+ return this;
+ }
+ updateMorphTargets() {
+ const geometry = this.geometry;
+ const morphAttributes = geometry.morphAttributes;
+ const keys = Object.keys(morphAttributes);
+ if (keys.length > 0) {
+ const morphAttribute = morphAttributes[keys[0]];
+ if (morphAttribute !== void 0) {
+ this.morphTargetInfluences = [];
+ this.morphTargetDictionary = {};
+ for (let m2 = 0, ml = morphAttribute.length; m2 < ml; m2++) {
+ const name = morphAttribute[m2].name || String(m2);
+ this.morphTargetInfluences.push(0);
+ this.morphTargetDictionary[name] = m2;
+ }
+ }
+ }
+ }
+ raycast(raycaster, intersects2) {
+ const geometry = this.geometry;
+ const material = this.material;
+ const matrixWorld = this.matrixWorld;
+ if (material === void 0)
+ return;
+ if (geometry.boundingSphere === null)
+ geometry.computeBoundingSphere();
+ _sphere$32.copy(geometry.boundingSphere);
+ _sphere$32.applyMatrix4(matrixWorld);
+ if (raycaster.ray.intersectsSphere(_sphere$32) === false)
+ return;
+ _inverseMatrix$22.copy(matrixWorld).invert();
+ _ray$22.copy(raycaster.ray).applyMatrix4(_inverseMatrix$22);
+ if (geometry.boundingBox !== null) {
+ if (_ray$22.intersectsBox(geometry.boundingBox) === false)
+ return;
+ }
+ let intersection;
+ const index2 = geometry.index;
+ const position = geometry.attributes.position;
+ const morphPosition = geometry.morphAttributes.position;
+ const morphTargetsRelative = geometry.morphTargetsRelative;
+ const uv = geometry.attributes.uv;
+ const uv2 = geometry.attributes.uv2;
+ const groups = geometry.groups;
+ const drawRange = geometry.drawRange;
+ if (index2 !== null) {
+ if (Array.isArray(material)) {
+ for (let i = 0, il = groups.length; i < il; i++) {
+ const group = groups[i];
+ const groupMaterial = material[group.materialIndex];
+ const start2 = Math.max(group.start, drawRange.start);
+ const end = Math.min(index2.count, Math.min(group.start + group.count, drawRange.start + drawRange.count));
+ for (let j = start2, jl = end; j < jl; j += 3) {
+ const a2 = index2.getX(j);
+ const b = index2.getX(j + 1);
+ const c2 = index2.getX(j + 2);
+ intersection = checkBufferGeometryIntersection2(this, groupMaterial, raycaster, _ray$22, position, morphPosition, morphTargetsRelative, uv, uv2, a2, b, c2);
+ if (intersection) {
+ intersection.faceIndex = Math.floor(j / 3);
+ intersection.face.materialIndex = group.materialIndex;
+ intersects2.push(intersection);
+ }
+ }
+ }
+ } else {
+ const start2 = Math.max(0, drawRange.start);
+ const end = Math.min(index2.count, drawRange.start + drawRange.count);
+ for (let i = start2, il = end; i < il; i += 3) {
+ const a2 = index2.getX(i);
+ const b = index2.getX(i + 1);
+ const c2 = index2.getX(i + 2);
+ intersection = checkBufferGeometryIntersection2(this, material, raycaster, _ray$22, position, morphPosition, morphTargetsRelative, uv, uv2, a2, b, c2);
+ if (intersection) {
+ intersection.faceIndex = Math.floor(i / 3);
+ intersects2.push(intersection);
+ }
+ }
+ }
+ } else if (position !== void 0) {
+ if (Array.isArray(material)) {
+ for (let i = 0, il = groups.length; i < il; i++) {
+ const group = groups[i];
+ const groupMaterial = material[group.materialIndex];
+ const start2 = Math.max(group.start, drawRange.start);
+ const end = Math.min(position.count, Math.min(group.start + group.count, drawRange.start + drawRange.count));
+ for (let j = start2, jl = end; j < jl; j += 3) {
+ const a2 = j;
+ const b = j + 1;
+ const c2 = j + 2;
+ intersection = checkBufferGeometryIntersection2(this, groupMaterial, raycaster, _ray$22, position, morphPosition, morphTargetsRelative, uv, uv2, a2, b, c2);
+ if (intersection) {
+ intersection.faceIndex = Math.floor(j / 3);
+ intersection.face.materialIndex = group.materialIndex;
+ intersects2.push(intersection);
+ }
+ }
+ }
+ } else {
+ const start2 = Math.max(0, drawRange.start);
+ const end = Math.min(position.count, drawRange.start + drawRange.count);
+ for (let i = start2, il = end; i < il; i += 3) {
+ const a2 = i;
+ const b = i + 1;
+ const c2 = i + 2;
+ intersection = checkBufferGeometryIntersection2(this, material, raycaster, _ray$22, position, morphPosition, morphTargetsRelative, uv, uv2, a2, b, c2);
+ if (intersection) {
+ intersection.faceIndex = Math.floor(i / 3);
+ intersects2.push(intersection);
+ }
+ }
+ }
+ }
+ }
+ };
+ function checkIntersection2(object, material, raycaster, ray, pA, pB, pC, point) {
+ let intersect;
+ if (material.side === BackSide2) {
+ intersect = ray.intersectTriangle(pC, pB, pA, true, point);
+ } else {
+ intersect = ray.intersectTriangle(pA, pB, pC, material.side !== DoubleSide2, point);
+ }
+ if (intersect === null)
+ return null;
+ _intersectionPointWorld2.copy(point);
+ _intersectionPointWorld2.applyMatrix4(object.matrixWorld);
+ const distance = raycaster.ray.origin.distanceTo(_intersectionPointWorld2);
+ if (distance < raycaster.near || distance > raycaster.far)
+ return null;
+ return {
+ distance,
+ point: _intersectionPointWorld2.clone(),
+ object
+ };
+ }
+ function checkBufferGeometryIntersection2(object, material, raycaster, ray, position, morphPosition, morphTargetsRelative, uv, uv2, a2, b, c2) {
+ _vA$12.fromBufferAttribute(position, a2);
+ _vB$12.fromBufferAttribute(position, b);
+ _vC$12.fromBufferAttribute(position, c2);
+ const morphInfluences = object.morphTargetInfluences;
+ if (morphPosition && morphInfluences) {
+ _morphA2.set(0, 0, 0);
+ _morphB2.set(0, 0, 0);
+ _morphC2.set(0, 0, 0);
+ for (let i = 0, il = morphPosition.length; i < il; i++) {
+ const influence = morphInfluences[i];
+ const morphAttribute = morphPosition[i];
+ if (influence === 0)
+ continue;
+ _tempA2.fromBufferAttribute(morphAttribute, a2);
+ _tempB2.fromBufferAttribute(morphAttribute, b);
+ _tempC2.fromBufferAttribute(morphAttribute, c2);
+ if (morphTargetsRelative) {
+ _morphA2.addScaledVector(_tempA2, influence);
+ _morphB2.addScaledVector(_tempB2, influence);
+ _morphC2.addScaledVector(_tempC2, influence);
+ } else {
+ _morphA2.addScaledVector(_tempA2.sub(_vA$12), influence);
+ _morphB2.addScaledVector(_tempB2.sub(_vB$12), influence);
+ _morphC2.addScaledVector(_tempC2.sub(_vC$12), influence);
+ }
+ }
+ _vA$12.add(_morphA2);
+ _vB$12.add(_morphB2);
+ _vC$12.add(_morphC2);
+ }
+ if (object.isSkinnedMesh) {
+ object.boneTransform(a2, _vA$12);
+ object.boneTransform(b, _vB$12);
+ object.boneTransform(c2, _vC$12);
+ }
+ const intersection = checkIntersection2(object, material, raycaster, ray, _vA$12, _vB$12, _vC$12, _intersectionPoint2);
+ if (intersection) {
+ if (uv) {
+ _uvA$12.fromBufferAttribute(uv, a2);
+ _uvB$12.fromBufferAttribute(uv, b);
+ _uvC$12.fromBufferAttribute(uv, c2);
+ intersection.uv = Triangle2.getUV(_intersectionPoint2, _vA$12, _vB$12, _vC$12, _uvA$12, _uvB$12, _uvC$12, new Vector22());
+ }
+ if (uv2) {
+ _uvA$12.fromBufferAttribute(uv2, a2);
+ _uvB$12.fromBufferAttribute(uv2, b);
+ _uvC$12.fromBufferAttribute(uv2, c2);
+ intersection.uv2 = Triangle2.getUV(_intersectionPoint2, _vA$12, _vB$12, _vC$12, _uvA$12, _uvB$12, _uvC$12, new Vector22());
+ }
+ const face = {
+ a: a2,
+ b,
+ c: c2,
+ normal: new Vector32(),
+ materialIndex: 0
+ };
+ Triangle2.getNormal(_vA$12, _vB$12, _vC$12, face.normal);
+ intersection.face = face;
+ }
+ return intersection;
+ }
+ var BoxGeometry2 = class extends BufferGeometry2 {
+ constructor(width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1) {
+ super();
+ this.type = "BoxGeometry";
+ this.parameters = {
+ width,
+ height,
+ depth,
+ widthSegments,
+ heightSegments,
+ depthSegments
+ };
+ const scope = this;
+ widthSegments = Math.floor(widthSegments);
+ heightSegments = Math.floor(heightSegments);
+ depthSegments = Math.floor(depthSegments);
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+ let numberOfVertices = 0;
+ let groupStart = 0;
+ buildPlane("z", "y", "x", -1, -1, depth, height, width, depthSegments, heightSegments, 0);
+ buildPlane("z", "y", "x", 1, -1, depth, height, -width, depthSegments, heightSegments, 1);
+ buildPlane("x", "z", "y", 1, 1, width, depth, height, widthSegments, depthSegments, 2);
+ buildPlane("x", "z", "y", 1, -1, width, depth, -height, widthSegments, depthSegments, 3);
+ buildPlane("x", "y", "z", 1, -1, width, height, depth, widthSegments, heightSegments, 4);
+ buildPlane("x", "y", "z", -1, -1, width, height, -depth, widthSegments, heightSegments, 5);
+ this.setIndex(indices);
+ this.setAttribute("position", new Float32BufferAttribute2(vertices, 3));
+ this.setAttribute("normal", new Float32BufferAttribute2(normals, 3));
+ this.setAttribute("uv", new Float32BufferAttribute2(uvs, 2));
+ function buildPlane(u, v, w, udir, vdir, width2, height2, depth2, gridX, gridY, materialIndex) {
+ const segmentWidth = width2 / gridX;
+ const segmentHeight = height2 / gridY;
+ const widthHalf = width2 / 2;
+ const heightHalf = height2 / 2;
+ const depthHalf = depth2 / 2;
+ const gridX1 = gridX + 1;
+ const gridY1 = gridY + 1;
+ let vertexCounter = 0;
+ let groupCount = 0;
+ const vector = new Vector32();
+ for (let iy = 0; iy < gridY1; iy++) {
+ const y2 = iy * segmentHeight - heightHalf;
+ for (let ix = 0; ix < gridX1; ix++) {
+ const x2 = ix * segmentWidth - widthHalf;
+ vector[u] = x2 * udir;
+ vector[v] = y2 * vdir;
+ vector[w] = depthHalf;
+ vertices.push(vector.x, vector.y, vector.z);
+ vector[u] = 0;
+ vector[v] = 0;
+ vector[w] = depth2 > 0 ? 1 : -1;
+ normals.push(vector.x, vector.y, vector.z);
+ uvs.push(ix / gridX);
+ uvs.push(1 - iy / gridY);
+ vertexCounter += 1;
+ }
+ }
+ for (let iy = 0; iy < gridY; iy++) {
+ for (let ix = 0; ix < gridX; ix++) {
+ const a2 = numberOfVertices + ix + gridX1 * iy;
+ const b = numberOfVertices + ix + gridX1 * (iy + 1);
+ const c2 = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
+ const d = numberOfVertices + (ix + 1) + gridX1 * iy;
+ indices.push(a2, b, d);
+ indices.push(b, c2, d);
+ groupCount += 6;
+ }
+ }
+ scope.addGroup(groupStart, groupCount, materialIndex);
+ groupStart += groupCount;
+ numberOfVertices += vertexCounter;
+ }
+ }
+ static fromJSON(data) {
+ return new BoxGeometry2(data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments);
+ }
+ };
+ function cloneUniforms2(src) {
+ const dst = {};
+ for (const u in src) {
+ dst[u] = {};
+ for (const p in src[u]) {
+ const property = src[u][p];
+ if (property && (property.isColor || property.isMatrix3 || property.isMatrix4 || property.isVector2 || property.isVector3 || property.isVector4 || property.isTexture || property.isQuaternion)) {
+ dst[u][p] = property.clone();
+ } else if (Array.isArray(property)) {
+ dst[u][p] = property.slice();
+ } else {
+ dst[u][p] = property;
+ }
+ }
+ }
+ return dst;
+ }
+ function mergeUniforms2(uniforms) {
+ const merged = {};
+ for (let u = 0; u < uniforms.length; u++) {
+ const tmp2 = cloneUniforms2(uniforms[u]);
+ for (const p in tmp2) {
+ merged[p] = tmp2[p];
+ }
+ }
+ return merged;
+ }
+ function cloneUniformsGroups2(src) {
+ const dst = [];
+ for (let u = 0; u < src.length; u++) {
+ dst.push(src[u].clone());
+ }
+ return dst;
+ }
+ var UniformsUtils2 = {
+ clone: cloneUniforms2,
+ merge: mergeUniforms2
+ };
+ var default_vertex2 = "void main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}";
+ var default_fragment2 = "void main() {\n gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}";
+ var ShaderMaterial2 = class extends Material2 {
+ constructor(parameters) {
+ super();
+ this.isShaderMaterial = true;
+ this.type = "ShaderMaterial";
+ this.defines = {};
+ this.uniforms = {};
+ this.uniformsGroups = [];
+ this.vertexShader = default_vertex2;
+ this.fragmentShader = default_fragment2;
+ this.linewidth = 1;
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.fog = false;
+ this.lights = false;
+ this.clipping = false;
+ this.extensions = {
+ derivatives: false,
+ fragDepth: false,
+ drawBuffers: false,
+ shaderTextureLOD: false
+ };
+ this.defaultAttributeValues = {
+ "color": [1, 1, 1],
+ "uv": [0, 0],
+ "uv2": [0, 0]
+ };
+ this.index0AttributeName = void 0;
+ this.uniformsNeedUpdate = false;
+ this.glslVersion = null;
+ if (parameters !== void 0) {
+ if (parameters.attributes !== void 0) {
+ console.error("THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.");
+ }
+ this.setValues(parameters);
+ }
+ }
+ copy(source) {
+ super.copy(source);
+ this.fragmentShader = source.fragmentShader;
+ this.vertexShader = source.vertexShader;
+ this.uniforms = cloneUniforms2(source.uniforms);
+ this.uniformsGroups = cloneUniformsGroups2(source.uniformsGroups);
+ this.defines = Object.assign({}, source.defines);
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+ this.fog = source.fog;
+ this.lights = source.lights;
+ this.clipping = source.clipping;
+ this.extensions = Object.assign({}, source.extensions);
+ this.glslVersion = source.glslVersion;
+ return this;
+ }
+ toJSON(meta) {
+ const data = super.toJSON(meta);
+ data.glslVersion = this.glslVersion;
+ data.uniforms = {};
+ for (const name in this.uniforms) {
+ const uniform = this.uniforms[name];
+ const value = uniform.value;
+ if (value && value.isTexture) {
+ data.uniforms[name] = {
+ type: "t",
+ value: value.toJSON(meta).uuid
+ };
+ } else if (value && value.isColor) {
+ data.uniforms[name] = {
+ type: "c",
+ value: value.getHex()
+ };
+ } else if (value && value.isVector2) {
+ data.uniforms[name] = {
+ type: "v2",
+ value: value.toArray()
+ };
+ } else if (value && value.isVector3) {
+ data.uniforms[name] = {
+ type: "v3",
+ value: value.toArray()
+ };
+ } else if (value && value.isVector4) {
+ data.uniforms[name] = {
+ type: "v4",
+ value: value.toArray()
+ };
+ } else if (value && value.isMatrix3) {
+ data.uniforms[name] = {
+ type: "m3",
+ value: value.toArray()
+ };
+ } else if (value && value.isMatrix4) {
+ data.uniforms[name] = {
+ type: "m4",
+ value: value.toArray()
+ };
+ } else {
+ data.uniforms[name] = {
+ value
+ };
+ }
+ }
+ if (Object.keys(this.defines).length > 0)
+ data.defines = this.defines;
+ data.vertexShader = this.vertexShader;
+ data.fragmentShader = this.fragmentShader;
+ const extensions = {};
+ for (const key in this.extensions) {
+ if (this.extensions[key] === true)
+ extensions[key] = true;
+ }
+ if (Object.keys(extensions).length > 0)
+ data.extensions = extensions;
+ return data;
+ }
+ };
+ var Camera2 = class extends Object3D2 {
+ constructor() {
+ super();
+ this.isCamera = true;
+ this.type = "Camera";
+ this.matrixWorldInverse = new Matrix42();
+ this.projectionMatrix = new Matrix42();
+ this.projectionMatrixInverse = new Matrix42();
+ }
+ copy(source, recursive) {
+ super.copy(source, recursive);
+ this.matrixWorldInverse.copy(source.matrixWorldInverse);
+ this.projectionMatrix.copy(source.projectionMatrix);
+ this.projectionMatrixInverse.copy(source.projectionMatrixInverse);
+ return this;
+ }
+ getWorldDirection(target) {
+ this.updateWorldMatrix(true, false);
+ const e = this.matrixWorld.elements;
+ return target.set(-e[8], -e[9], -e[10]).normalize();
+ }
+ updateMatrixWorld(force) {
+ super.updateMatrixWorld(force);
+ this.matrixWorldInverse.copy(this.matrixWorld).invert();
+ }
+ updateWorldMatrix(updateParents, updateChildren) {
+ super.updateWorldMatrix(updateParents, updateChildren);
+ this.matrixWorldInverse.copy(this.matrixWorld).invert();
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ };
+ var PerspectiveCamera2 = class extends Camera2 {
+ constructor(fov3 = 50, aspect3 = 1, near = 0.1, far = 2e3) {
+ super();
+ this.isPerspectiveCamera = true;
+ this.type = "PerspectiveCamera";
+ this.fov = fov3;
+ this.zoom = 1;
+ this.near = near;
+ this.far = far;
+ this.focus = 10;
+ this.aspect = aspect3;
+ this.view = null;
+ this.filmGauge = 35;
+ this.filmOffset = 0;
+ this.updateProjectionMatrix();
+ }
+ copy(source, recursive) {
+ super.copy(source, recursive);
+ this.fov = source.fov;
+ this.zoom = source.zoom;
+ this.near = source.near;
+ this.far = source.far;
+ this.focus = source.focus;
+ this.aspect = source.aspect;
+ this.view = source.view === null ? null : Object.assign({}, source.view);
+ this.filmGauge = source.filmGauge;
+ this.filmOffset = source.filmOffset;
+ return this;
+ }
+ setFocalLength(focalLength) {
+ const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;
+ this.fov = RAD2DEG2 * 2 * Math.atan(vExtentSlope);
+ this.updateProjectionMatrix();
+ }
+ getFocalLength() {
+ const vExtentSlope = Math.tan(DEG2RAD2 * 0.5 * this.fov);
+ return 0.5 * this.getFilmHeight() / vExtentSlope;
+ }
+ getEffectiveFOV() {
+ return RAD2DEG2 * 2 * Math.atan(Math.tan(DEG2RAD2 * 0.5 * this.fov) / this.zoom);
+ }
+ getFilmWidth() {
+ return this.filmGauge * Math.min(this.aspect, 1);
+ }
+ getFilmHeight() {
+ return this.filmGauge / Math.max(this.aspect, 1);
+ }
+ setViewOffset(fullWidth, fullHeight, x2, y2, width, height) {
+ this.aspect = fullWidth / fullHeight;
+ if (this.view === null) {
+ this.view = {
+ enabled: true,
+ fullWidth: 1,
+ fullHeight: 1,
+ offsetX: 0,
+ offsetY: 0,
+ width: 1,
+ height: 1
+ };
+ }
+ this.view.enabled = true;
+ this.view.fullWidth = fullWidth;
+ this.view.fullHeight = fullHeight;
+ this.view.offsetX = x2;
+ this.view.offsetY = y2;
+ this.view.width = width;
+ this.view.height = height;
+ this.updateProjectionMatrix();
+ }
+ clearViewOffset() {
+ if (this.view !== null) {
+ this.view.enabled = false;
+ }
+ this.updateProjectionMatrix();
+ }
+ updateProjectionMatrix() {
+ const near = this.near;
+ let top = near * Math.tan(DEG2RAD2 * 0.5 * this.fov) / this.zoom;
+ let height = 2 * top;
+ let width = this.aspect * height;
+ let left = -0.5 * width;
+ const view = this.view;
+ if (this.view !== null && this.view.enabled) {
+ const fullWidth = view.fullWidth, fullHeight = view.fullHeight;
+ left += view.offsetX * width / fullWidth;
+ top -= view.offsetY * height / fullHeight;
+ width *= view.width / fullWidth;
+ height *= view.height / fullHeight;
+ }
+ const skew = this.filmOffset;
+ if (skew !== 0)
+ left += near * skew / this.getFilmWidth();
+ this.projectionMatrix.makePerspective(left, left + width, top, top - height, near, this.far);
+ this.projectionMatrixInverse.copy(this.projectionMatrix).invert();
+ }
+ toJSON(meta) {
+ const data = super.toJSON(meta);
+ data.object.fov = this.fov;
+ data.object.zoom = this.zoom;
+ data.object.near = this.near;
+ data.object.far = this.far;
+ data.object.focus = this.focus;
+ data.object.aspect = this.aspect;
+ if (this.view !== null)
+ data.object.view = Object.assign({}, this.view);
+ data.object.filmGauge = this.filmGauge;
+ data.object.filmOffset = this.filmOffset;
+ return data;
+ }
+ };
+ var fov2 = 90;
+ var aspect2 = 1;
+ var CubeCamera2 = class extends Object3D2 {
+ constructor(near, far, renderTarget) {
+ super();
+ this.type = "CubeCamera";
+ if (renderTarget.isWebGLCubeRenderTarget !== true) {
+ console.error("THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.");
+ return;
+ }
+ this.renderTarget = renderTarget;
+ const cameraPX = new PerspectiveCamera2(fov2, aspect2, near, far);
+ cameraPX.layers = this.layers;
+ cameraPX.up.set(0, -1, 0);
+ cameraPX.lookAt(new Vector32(1, 0, 0));
+ this.add(cameraPX);
+ const cameraNX = new PerspectiveCamera2(fov2, aspect2, near, far);
+ cameraNX.layers = this.layers;
+ cameraNX.up.set(0, -1, 0);
+ cameraNX.lookAt(new Vector32(-1, 0, 0));
+ this.add(cameraNX);
+ const cameraPY = new PerspectiveCamera2(fov2, aspect2, near, far);
+ cameraPY.layers = this.layers;
+ cameraPY.up.set(0, 0, 1);
+ cameraPY.lookAt(new Vector32(0, 1, 0));
+ this.add(cameraPY);
+ const cameraNY = new PerspectiveCamera2(fov2, aspect2, near, far);
+ cameraNY.layers = this.layers;
+ cameraNY.up.set(0, 0, -1);
+ cameraNY.lookAt(new Vector32(0, -1, 0));
+ this.add(cameraNY);
+ const cameraPZ = new PerspectiveCamera2(fov2, aspect2, near, far);
+ cameraPZ.layers = this.layers;
+ cameraPZ.up.set(0, -1, 0);
+ cameraPZ.lookAt(new Vector32(0, 0, 1));
+ this.add(cameraPZ);
+ const cameraNZ = new PerspectiveCamera2(fov2, aspect2, near, far);
+ cameraNZ.layers = this.layers;
+ cameraNZ.up.set(0, -1, 0);
+ cameraNZ.lookAt(new Vector32(0, 0, -1));
+ this.add(cameraNZ);
+ }
+ update(renderer, scene) {
+ if (this.parent === null)
+ this.updateMatrixWorld();
+ const renderTarget = this.renderTarget;
+ const [cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ] = this.children;
+ const currentRenderTarget = renderer.getRenderTarget();
+ const currentToneMapping = renderer.toneMapping;
+ const currentXrEnabled = renderer.xr.enabled;
+ renderer.toneMapping = NoToneMapping2;
+ renderer.xr.enabled = false;
+ const generateMipmaps = renderTarget.texture.generateMipmaps;
+ renderTarget.texture.generateMipmaps = false;
+ renderer.setRenderTarget(renderTarget, 0);
+ renderer.render(scene, cameraPX);
+ renderer.setRenderTarget(renderTarget, 1);
+ renderer.render(scene, cameraNX);
+ renderer.setRenderTarget(renderTarget, 2);
+ renderer.render(scene, cameraPY);
+ renderer.setRenderTarget(renderTarget, 3);
+ renderer.render(scene, cameraNY);
+ renderer.setRenderTarget(renderTarget, 4);
+ renderer.render(scene, cameraPZ);
+ renderTarget.texture.generateMipmaps = generateMipmaps;
+ renderer.setRenderTarget(renderTarget, 5);
+ renderer.render(scene, cameraNZ);
+ renderer.setRenderTarget(currentRenderTarget);
+ renderer.toneMapping = currentToneMapping;
+ renderer.xr.enabled = currentXrEnabled;
+ renderTarget.texture.needsPMREMUpdate = true;
+ }
+ };
+ var CubeTexture2 = class extends Texture2 {
+ constructor(images, mapping, wrapS, wrapT, magFilter, minFilter, format2, type2, anisotropy, encoding) {
+ images = images !== void 0 ? images : [];
+ mapping = mapping !== void 0 ? mapping : CubeReflectionMapping2;
+ super(images, mapping, wrapS, wrapT, magFilter, minFilter, format2, type2, anisotropy, encoding);
+ this.isCubeTexture = true;
+ this.flipY = false;
+ }
+ get images() {
+ return this.image;
+ }
+ set images(value) {
+ this.image = value;
+ }
+ };
+ var WebGLCubeRenderTarget2 = class extends WebGLRenderTarget2 {
+ constructor(size, options = {}) {
+ super(size, size, options);
+ this.isWebGLCubeRenderTarget = true;
+ const image = {
+ width: size,
+ height: size,
+ depth: 1
+ };
+ const images = [image, image, image, image, image, image];
+ this.texture = new CubeTexture2(images, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding);
+ this.texture.isRenderTargetTexture = true;
+ this.texture.generateMipmaps = options.generateMipmaps !== void 0 ? options.generateMipmaps : false;
+ this.texture.minFilter = options.minFilter !== void 0 ? options.minFilter : LinearFilter2;
+ }
+ fromEquirectangularTexture(renderer, texture) {
+ this.texture.type = texture.type;
+ this.texture.encoding = texture.encoding;
+ this.texture.generateMipmaps = texture.generateMipmaps;
+ this.texture.minFilter = texture.minFilter;
+ this.texture.magFilter = texture.magFilter;
+ const shader = {
+ uniforms: {
+ tEquirect: {
+ value: null
+ }
+ },
+ vertexShader: `
+
+ varying vec3 vWorldDirection;
+
+ vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
+
+ return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
+
+ }
+
+ void main() {
+
+ vWorldDirection = transformDirection( position, modelMatrix );
+
+ #include
+ #include
+
+ }
+ `,
+ fragmentShader: `
+
+ uniform sampler2D tEquirect;
+
+ varying vec3 vWorldDirection;
+
+ #include
+
+ void main() {
+
+ vec3 direction = normalize( vWorldDirection );
+
+ vec2 sampleUV = equirectUv( direction );
+
+ gl_FragColor = texture2D( tEquirect, sampleUV );
+
+ }
+ `
+ };
+ const geometry = new BoxGeometry2(5, 5, 5);
+ const material = new ShaderMaterial2({
+ name: "CubemapFromEquirect",
+ uniforms: cloneUniforms2(shader.uniforms),
+ vertexShader: shader.vertexShader,
+ fragmentShader: shader.fragmentShader,
+ side: BackSide2,
+ blending: NoBlending2
+ });
+ material.uniforms.tEquirect.value = texture;
+ const mesh = new Mesh2(geometry, material);
+ const currentMinFilter = texture.minFilter;
+ if (texture.minFilter === LinearMipmapLinearFilter2)
+ texture.minFilter = LinearFilter2;
+ const camera = new CubeCamera2(1, 10, this);
+ camera.update(renderer, mesh);
+ texture.minFilter = currentMinFilter;
+ mesh.geometry.dispose();
+ mesh.material.dispose();
+ return this;
+ }
+ clear(renderer, color2, depth, stencil) {
+ const currentRenderTarget = renderer.getRenderTarget();
+ for (let i = 0; i < 6; i++) {
+ renderer.setRenderTarget(this, i);
+ renderer.clear(color2, depth, stencil);
+ }
+ renderer.setRenderTarget(currentRenderTarget);
+ }
+ };
+ var _vector12 = /* @__PURE__ */ new Vector32();
+ var _vector22 = /* @__PURE__ */ new Vector32();
+ var _normalMatrix2 = /* @__PURE__ */ new Matrix32();
+ var Plane2 = class {
+ constructor(normal = new Vector32(1, 0, 0), constant = 0) {
+ this.isPlane = true;
+ this.normal = normal;
+ this.constant = constant;
+ }
+ set(normal, constant) {
+ this.normal.copy(normal);
+ this.constant = constant;
+ return this;
+ }
+ setComponents(x2, y2, z, w) {
+ this.normal.set(x2, y2, z);
+ this.constant = w;
+ return this;
+ }
+ setFromNormalAndCoplanarPoint(normal, point) {
+ this.normal.copy(normal);
+ this.constant = -point.dot(this.normal);
+ return this;
+ }
+ setFromCoplanarPoints(a2, b, c2) {
+ const normal = _vector12.subVectors(c2, b).cross(_vector22.subVectors(a2, b)).normalize();
+ this.setFromNormalAndCoplanarPoint(normal, a2);
+ return this;
+ }
+ copy(plane) {
+ this.normal.copy(plane.normal);
+ this.constant = plane.constant;
+ return this;
+ }
+ normalize() {
+ const inverseNormalLength = 1 / this.normal.length();
+ this.normal.multiplyScalar(inverseNormalLength);
+ this.constant *= inverseNormalLength;
+ return this;
+ }
+ negate() {
+ this.constant *= -1;
+ this.normal.negate();
+ return this;
+ }
+ distanceToPoint(point) {
+ return this.normal.dot(point) + this.constant;
+ }
+ distanceToSphere(sphere) {
+ return this.distanceToPoint(sphere.center) - sphere.radius;
+ }
+ projectPoint(point, target) {
+ return target.copy(this.normal).multiplyScalar(-this.distanceToPoint(point)).add(point);
+ }
+ intersectLine(line, target) {
+ const direction = line.delta(_vector12);
+ const denominator = this.normal.dot(direction);
+ if (denominator === 0) {
+ if (this.distanceToPoint(line.start) === 0) {
+ return target.copy(line.start);
+ }
+ return null;
+ }
+ const t = -(line.start.dot(this.normal) + this.constant) / denominator;
+ if (t < 0 || t > 1) {
+ return null;
+ }
+ return target.copy(direction).multiplyScalar(t).add(line.start);
+ }
+ intersectsLine(line) {
+ const startSign = this.distanceToPoint(line.start);
+ const endSign = this.distanceToPoint(line.end);
+ return startSign < 0 && endSign > 0 || endSign < 0 && startSign > 0;
+ }
+ intersectsBox(box) {
+ return box.intersectsPlane(this);
+ }
+ intersectsSphere(sphere) {
+ return sphere.intersectsPlane(this);
+ }
+ coplanarPoint(target) {
+ return target.copy(this.normal).multiplyScalar(-this.constant);
+ }
+ applyMatrix4(matrix, optionalNormalMatrix) {
+ const normalMatrix = optionalNormalMatrix || _normalMatrix2.getNormalMatrix(matrix);
+ const referencePoint = this.coplanarPoint(_vector12).applyMatrix4(matrix);
+ const normal = this.normal.applyMatrix3(normalMatrix).normalize();
+ this.constant = -referencePoint.dot(normal);
+ return this;
+ }
+ translate(offset) {
+ this.constant -= offset.dot(this.normal);
+ return this;
+ }
+ equals(plane) {
+ return plane.normal.equals(this.normal) && plane.constant === this.constant;
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ };
+ var _sphere$22 = /* @__PURE__ */ new Sphere2();
+ var _vector$72 = /* @__PURE__ */ new Vector32();
+ var Frustum2 = class {
+ constructor(p0 = new Plane2(), p1 = new Plane2(), p2 = new Plane2(), p3 = new Plane2(), p4 = new Plane2(), p5 = new Plane2()) {
+ this.planes = [p0, p1, p2, p3, p4, p5];
+ }
+ set(p0, p1, p2, p3, p4, p5) {
+ const planes = this.planes;
+ planes[0].copy(p0);
+ planes[1].copy(p1);
+ planes[2].copy(p2);
+ planes[3].copy(p3);
+ planes[4].copy(p4);
+ planes[5].copy(p5);
+ return this;
+ }
+ copy(frustum) {
+ const planes = this.planes;
+ for (let i = 0; i < 6; i++) {
+ planes[i].copy(frustum.planes[i]);
+ }
+ return this;
+ }
+ setFromProjectionMatrix(m2) {
+ const planes = this.planes;
+ const me = m2.elements;
+ const me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3];
+ const me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7];
+ const me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11];
+ const me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15];
+ planes[0].setComponents(me3 - me0, me7 - me4, me11 - me8, me15 - me12).normalize();
+ planes[1].setComponents(me3 + me0, me7 + me4, me11 + me8, me15 + me12).normalize();
+ planes[2].setComponents(me3 + me1, me7 + me5, me11 + me9, me15 + me13).normalize();
+ planes[3].setComponents(me3 - me1, me7 - me5, me11 - me9, me15 - me13).normalize();
+ planes[4].setComponents(me3 - me2, me7 - me6, me11 - me10, me15 - me14).normalize();
+ planes[5].setComponents(me3 + me2, me7 + me6, me11 + me10, me15 + me14).normalize();
+ return this;
+ }
+ intersectsObject(object) {
+ const geometry = object.geometry;
+ if (geometry.boundingSphere === null)
+ geometry.computeBoundingSphere();
+ _sphere$22.copy(geometry.boundingSphere).applyMatrix4(object.matrixWorld);
+ return this.intersectsSphere(_sphere$22);
+ }
+ intersectsSprite(sprite) {
+ _sphere$22.center.set(0, 0, 0);
+ _sphere$22.radius = 0.7071067811865476;
+ _sphere$22.applyMatrix4(sprite.matrixWorld);
+ return this.intersectsSphere(_sphere$22);
+ }
+ intersectsSphere(sphere) {
+ const planes = this.planes;
+ const center = sphere.center;
+ const negRadius = -sphere.radius;
+ for (let i = 0; i < 6; i++) {
+ const distance = planes[i].distanceToPoint(center);
+ if (distance < negRadius) {
+ return false;
+ }
+ }
+ return true;
+ }
+ intersectsBox(box) {
+ const planes = this.planes;
+ for (let i = 0; i < 6; i++) {
+ const plane = planes[i];
+ _vector$72.x = plane.normal.x > 0 ? box.max.x : box.min.x;
+ _vector$72.y = plane.normal.y > 0 ? box.max.y : box.min.y;
+ _vector$72.z = plane.normal.z > 0 ? box.max.z : box.min.z;
+ if (plane.distanceToPoint(_vector$72) < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ containsPoint(point) {
+ const planes = this.planes;
+ for (let i = 0; i < 6; i++) {
+ if (planes[i].distanceToPoint(point) < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ clone() {
+ return new this.constructor().copy(this);
+ }
+ };
+ function WebGLAnimation2() {
+ let context = null;
+ let isAnimating = false;
+ let animationLoop = null;
+ let requestId = null;
+ function onAnimationFrame(time, frame2) {
+ animationLoop(time, frame2);
+ requestId = context.requestAnimationFrame(onAnimationFrame);
+ }
+ return {
+ start: function() {
+ if (isAnimating === true)
+ return;
+ if (animationLoop === null)
+ return;
+ requestId = context.requestAnimationFrame(onAnimationFrame);
+ isAnimating = true;
+ },
+ stop: function() {
+ context.cancelAnimationFrame(requestId);
+ isAnimating = false;
+ },
+ setAnimationLoop: function(callback) {
+ animationLoop = callback;
+ },
+ setContext: function(value) {
+ context = value;
+ }
+ };
+ }
+ function WebGLAttributes2(gl, capabilities) {
+ const isWebGL2 = capabilities.isWebGL2;
+ const buffers = /* @__PURE__ */ new WeakMap();
+ function createBuffer(attribute, bufferType) {
+ const array2 = attribute.array;
+ const usage = attribute.usage;
+ const buffer = gl.createBuffer();
+ gl.bindBuffer(bufferType, buffer);
+ gl.bufferData(bufferType, array2, usage);
+ attribute.onUploadCallback();
+ let type2;
+ if (array2 instanceof Float32Array) {
+ type2 = gl.FLOAT;
+ } else if (array2 instanceof Uint16Array) {
+ if (attribute.isFloat16BufferAttribute) {
+ if (isWebGL2) {
+ type2 = gl.HALF_FLOAT;
+ } else {
+ throw new Error("THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.");
+ }
+ } else {
+ type2 = gl.UNSIGNED_SHORT;
+ }
+ } else if (array2 instanceof Int16Array) {
+ type2 = gl.SHORT;
+ } else if (array2 instanceof Uint32Array) {
+ type2 = gl.UNSIGNED_INT;
+ } else if (array2 instanceof Int32Array) {
+ type2 = gl.INT;
+ } else if (array2 instanceof Int8Array) {
+ type2 = gl.BYTE;
+ } else if (array2 instanceof Uint8Array) {
+ type2 = gl.UNSIGNED_BYTE;
+ } else if (array2 instanceof Uint8ClampedArray) {
+ type2 = gl.UNSIGNED_BYTE;
+ } else {
+ throw new Error("THREE.WebGLAttributes: Unsupported buffer data format: " + array2);
+ }
+ return {
+ buffer,
+ type: type2,
+ bytesPerElement: array2.BYTES_PER_ELEMENT,
+ version: attribute.version
+ };
+ }
+ function updateBuffer(buffer, attribute, bufferType) {
+ const array2 = attribute.array;
+ const updateRange = attribute.updateRange;
+ gl.bindBuffer(bufferType, buffer);
+ if (updateRange.count === -1) {
+ gl.bufferSubData(bufferType, 0, array2);
+ } else {
+ if (isWebGL2) {
+ gl.bufferSubData(bufferType, updateRange.offset * array2.BYTES_PER_ELEMENT, array2, updateRange.offset, updateRange.count);
+ } else {
+ gl.bufferSubData(bufferType, updateRange.offset * array2.BYTES_PER_ELEMENT, array2.subarray(updateRange.offset, updateRange.offset + updateRange.count));
+ }
+ updateRange.count = -1;
+ }
+ }
+ function get3(attribute) {
+ if (attribute.isInterleavedBufferAttribute)
+ attribute = attribute.data;
+ return buffers.get(attribute);
+ }
+ function remove2(attribute) {
+ if (attribute.isInterleavedBufferAttribute)
+ attribute = attribute.data;
+ const data = buffers.get(attribute);
+ if (data) {
+ gl.deleteBuffer(data.buffer);
+ buffers.delete(attribute);
+ }
+ }
+ function update(attribute, bufferType) {
+ if (attribute.isGLBufferAttribute) {
+ const cached = buffers.get(attribute);
+ if (!cached || cached.version < attribute.version) {
+ buffers.set(attribute, {
+ buffer: attribute.buffer,
+ type: attribute.type,
+ bytesPerElement: attribute.elementSize,
+ version: attribute.version
+ });
+ }
+ return;
+ }
+ if (attribute.isInterleavedBufferAttribute)
+ attribute = attribute.data;
+ const data = buffers.get(attribute);
+ if (data === void 0) {
+ buffers.set(attribute, createBuffer(attribute, bufferType));
+ } else if (data.version < attribute.version) {
+ updateBuffer(data.buffer, attribute, bufferType);
+ data.version = attribute.version;
+ }
+ }
+ return {
+ get: get3,
+ remove: remove2,
+ update
+ };
+ }
+ var PlaneGeometry2 = class extends BufferGeometry2 {
+ constructor(width = 1, height = 1, widthSegments = 1, heightSegments = 1) {
+ super();
+ this.type = "PlaneGeometry";
+ this.parameters = {
+ width,
+ height,
+ widthSegments,
+ heightSegments
+ };
+ const width_half = width / 2;
+ const height_half = height / 2;
+ const gridX = Math.floor(widthSegments);
+ const gridY = Math.floor(heightSegments);
+ const gridX1 = gridX + 1;
+ const gridY1 = gridY + 1;
+ const segment_width = width / gridX;
+ const segment_height = height / gridY;
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+ for (let iy = 0; iy < gridY1; iy++) {
+ const y2 = iy * segment_height - height_half;
+ for (let ix = 0; ix < gridX1; ix++) {
+ const x2 = ix * segment_width - width_half;
+ vertices.push(x2, -y2, 0);
+ normals.push(0, 0, 1);
+ uvs.push(ix / gridX);
+ uvs.push(1 - iy / gridY);
+ }
+ }
+ for (let iy = 0; iy < gridY; iy++) {
+ for (let ix = 0; ix < gridX; ix++) {
+ const a2 = ix + gridX1 * iy;
+ const b = ix + gridX1 * (iy + 1);
+ const c2 = ix + 1 + gridX1 * (iy + 1);
+ const d = ix + 1 + gridX1 * iy;
+ indices.push(a2, b, d);
+ indices.push(b, c2, d);
+ }
+ }
+ this.setIndex(indices);
+ this.setAttribute("position", new Float32BufferAttribute2(vertices, 3));
+ this.setAttribute("normal", new Float32BufferAttribute2(normals, 3));
+ this.setAttribute("uv", new Float32BufferAttribute2(uvs, 2));
+ }
+ static fromJSON(data) {
+ return new PlaneGeometry2(data.width, data.height, data.widthSegments, data.heightSegments);
+ }
+ };
+ var alphamap_fragment2 = "#ifdef USE_ALPHAMAP\n diffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif";
+ var alphamap_pars_fragment2 = "#ifdef USE_ALPHAMAP\n uniform sampler2D alphaMap;\n#endif";
+ var alphatest_fragment2 = "#ifdef USE_ALPHATEST\n if ( diffuseColor.a < alphaTest ) discard;\n#endif";
+ var alphatest_pars_fragment2 = "#ifdef USE_ALPHATEST\n uniform float alphaTest;\n#endif";
+ var aomap_fragment2 = "#ifdef USE_AOMAP\n float ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n reflectedLight.indirectDiffuse *= ambientOcclusion;\n #if defined( USE_ENVMAP ) && defined( STANDARD )\n float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n reflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n #endif\n#endif";
+ var aomap_pars_fragment2 = "#ifdef USE_AOMAP\n uniform sampler2D aoMap;\n uniform float aoMapIntensity;\n#endif";
+ var begin_vertex2 = "vec3 transformed = vec3( position );";
+ var beginnormal_vertex2 = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n vec3 objectTangent = vec3( tangent.xyz );\n#endif";
+ var bsdfs2 = "vec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n return RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n float a2 = pow2( alpha );\n float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n return 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n float a2 = pow2( alpha );\n float denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n return RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in float f90, const in float roughness ) {\n float alpha = pow2( roughness );\n vec3 halfDir = normalize( lightDir + viewDir );\n float dotNL = saturate( dot( normal, lightDir ) );\n float dotNV = saturate( dot( normal, viewDir ) );\n float dotNH = saturate( dot( normal, halfDir ) );\n float dotVH = saturate( dot( viewDir, halfDir ) );\n vec3 F = F_Schlick( f0, f90, dotVH );\n float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n float D = D_GGX( alpha, dotNH );\n return F * ( V * D );\n}\n#ifdef USE_IRIDESCENCE\n vec3 BRDF_GGX_Iridescence( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in float f90, const in float iridescence, const in vec3 iridescenceFresnel, const in float roughness ) {\n float alpha = pow2( roughness );\n vec3 halfDir = normalize( lightDir + viewDir );\n float dotNL = saturate( dot( normal, lightDir ) );\n float dotNV = saturate( dot( normal, viewDir ) );\n float dotNH = saturate( dot( normal, halfDir ) );\n float dotVH = saturate( dot( viewDir, halfDir ) );\n vec3 F = mix( F_Schlick( f0, f90, dotVH ), iridescenceFresnel, iridescence );\n float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n float D = D_GGX( alpha, dotNH );\n return F * ( V * D );\n }\n#endif\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n const float LUT_SIZE = 64.0;\n const float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n const float LUT_BIAS = 0.5 / LUT_SIZE;\n float dotNV = saturate( dot( N, V ) );\n vec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n uv = uv * LUT_SCALE + LUT_BIAS;\n return uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n float l = length( f );\n return max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n float x = dot( v1, v2 );\n float y = abs( x );\n float a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n float b = 3.4175940 + ( 4.1616724 + y ) * y;\n float v = a / b;\n float theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n return cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n vec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n vec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n vec3 lightNormal = cross( v1, v2 );\n if( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n vec3 T1, T2;\n T1 = normalize( V - N * dot( V, N ) );\n T2 = - cross( N, T1 );\n mat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n vec3 coords[ 4 ];\n coords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n coords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n coords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n coords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n coords[ 0 ] = normalize( coords[ 0 ] );\n coords[ 1 ] = normalize( coords[ 1 ] );\n coords[ 2 ] = normalize( coords[ 2 ] );\n coords[ 3 ] = normalize( coords[ 3 ] );\n vec3 vectorFormFactor = vec3( 0.0 );\n vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n float result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n return vec3( result );\n}\nfloat G_BlinnPhong_Implicit( ) {\n return 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n vec3 halfDir = normalize( lightDir + viewDir );\n float dotNH = saturate( dot( normal, halfDir ) );\n float dotVH = saturate( dot( viewDir, halfDir ) );\n vec3 F = F_Schlick( specularColor, 1.0, dotVH );\n float G = G_BlinnPhong_Implicit( );\n float D = D_BlinnPhong( shininess, dotNH );\n return F * ( G * D );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n float alpha = pow2( roughness );\n float invAlpha = 1.0 / alpha;\n float cos2h = dotNH * dotNH;\n float sin2h = max( 1.0 - cos2h, 0.0078125 );\n return ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n return saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n vec3 halfDir = normalize( lightDir + viewDir );\n float dotNL = saturate( dot( normal, lightDir ) );\n float dotNV = saturate( dot( normal, viewDir ) );\n float dotNH = saturate( dot( normal, halfDir ) );\n float D = D_Charlie( sheenRoughness, dotNH );\n float V = V_Neubelt( dotNV, dotNL );\n return sheenColor * ( D * V );\n}\n#endif";
+ var iridescence_fragment2 = "#ifdef USE_IRIDESCENCE\n const mat3 XYZ_TO_REC709 = mat3(\n 3.2404542, -0.9692660, 0.0556434,\n -1.5371385, 1.8760108, -0.2040259,\n -0.4985314, 0.0415560, 1.0572252\n );\n vec3 Fresnel0ToIor( vec3 fresnel0 ) {\n vec3 sqrtF0 = sqrt( fresnel0 );\n return ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n }\n vec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n return pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n }\n float IorToFresnel0( float transmittedIor, float incidentIor ) {\n return pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n }\n vec3 evalSensitivity( float OPD, vec3 shift ) {\n float phase = 2.0 * PI * OPD * 1.0e-9;\n vec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n vec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n vec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n vec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n xyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n xyz /= 1.0685e-7;\n vec3 rgb = XYZ_TO_REC709 * xyz;\n return rgb;\n }\n vec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n vec3 I;\n float iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n float sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n float cosTheta2Sq = 1.0 - sinTheta2Sq;\n if ( cosTheta2Sq < 0.0 ) {\n return vec3( 1.0 );\n }\n float cosTheta2 = sqrt( cosTheta2Sq );\n float R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n float R12 = F_Schlick( R0, 1.0, cosTheta1 );\n float R21 = R12;\n float T121 = 1.0 - R12;\n float phi12 = 0.0;\n if ( iridescenceIOR < outsideIOR ) phi12 = PI;\n float phi21 = PI - phi12;\n vec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) ); vec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n vec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n vec3 phi23 = vec3( 0.0 );\n if ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n if ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n if ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n float OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n vec3 phi = vec3( phi21 ) + phi23;\n vec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n vec3 r123 = sqrt( R123 );\n vec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n vec3 C0 = R12 + Rs;\n I = C0;\n vec3 Cm = Rs - T121;\n for ( int m = 1; m <= 2; ++ m ) {\n Cm *= r123;\n vec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n I += Cm * Sm;\n }\n return max( I, vec3( 0.0 ) );\n }\n#endif";
+ var bumpmap_pars_fragment2 = "#ifdef USE_BUMPMAP\n uniform sampler2D bumpMap;\n uniform float bumpScale;\n vec2 dHdxy_fwd() {\n vec2 dSTdx = dFdx( vUv );\n vec2 dSTdy = dFdy( vUv );\n float Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n return vec2( dBx, dBy );\n }\n vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n vec3 vSigmaX = dFdx( surf_pos.xyz );\n vec3 vSigmaY = dFdy( surf_pos.xyz );\n vec3 vN = surf_norm;\n vec3 R1 = cross( vSigmaY, vN );\n vec3 R2 = cross( vN, vSigmaX );\n float fDet = dot( vSigmaX, R1 ) * faceDirection;\n vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n return normalize( abs( fDet ) * surf_norm - vGrad );\n }\n#endif";
+ var clipping_planes_fragment2 = "#if NUM_CLIPPING_PLANES > 0\n vec4 plane;\n #pragma unroll_loop_start\n for ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n plane = clippingPlanes[ i ];\n if ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n }\n #pragma unroll_loop_end\n #if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n bool clipped = true;\n #pragma unroll_loop_start\n for ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n plane = clippingPlanes[ i ];\n clipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n }\n #pragma unroll_loop_end\n if ( clipped ) discard;\n #endif\n#endif";
+ var clipping_planes_pars_fragment2 = "#if NUM_CLIPPING_PLANES > 0\n varying vec3 vClipPosition;\n uniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif";
+ var clipping_planes_pars_vertex2 = "#if NUM_CLIPPING_PLANES > 0\n varying vec3 vClipPosition;\n#endif";
+ var clipping_planes_vertex2 = "#if NUM_CLIPPING_PLANES > 0\n vClipPosition = - mvPosition.xyz;\n#endif";
+ var color_fragment2 = "#if defined( USE_COLOR_ALPHA )\n diffuseColor *= vColor;\n#elif defined( USE_COLOR )\n diffuseColor.rgb *= vColor;\n#endif";
+ var color_pars_fragment2 = "#if defined( USE_COLOR_ALPHA )\n varying vec4 vColor;\n#elif defined( USE_COLOR )\n varying vec3 vColor;\n#endif";
+ var color_pars_vertex2 = "#if defined( USE_COLOR_ALPHA )\n varying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n varying vec3 vColor;\n#endif";
+ var color_vertex2 = "#if defined( USE_COLOR_ALPHA )\n vColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n vColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n vColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n vColor.xyz *= instanceColor.xyz;\n#endif";
+ var common2 = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp 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#ifdef HIGH_PRECISION\n float precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n float precisionSafeLength( vec3 v ) {\n float maxComponent = max3( abs( v ) );\n return length( v / maxComponent ) * maxComponent;\n }\n#endif\nstruct IncidentLight {\n vec3 color;\n vec3 direction;\n bool visible;\n};\nstruct ReflectedLight {\n vec3 directDiffuse;\n vec3 directSpecular;\n vec3 indirectDiffuse;\n vec3 indirectSpecular;\n};\nstruct GeometricContext {\n vec3 position;\n vec3 normal;\n vec3 viewDir;\n#ifdef USE_CLEARCOAT\n vec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n mat3 tmp;\n tmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n tmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n tmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n return tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n const vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n return dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n return m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n float u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n float v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n return vec2( u, v );\n}";
+ var cube_uv_reflection_fragment2 = "#ifdef ENVMAP_TYPE_CUBE_UV\n #define cubeUV_minMipLevel 4.0\n #define cubeUV_minTileSize 16.0\n float getFace( vec3 direction ) {\n vec3 absDirection = abs( direction );\n float face = - 1.0;\n if ( absDirection.x > absDirection.z ) {\n if ( absDirection.x > absDirection.y )\n face = direction.x > 0.0 ? 0.0 : 3.0;\n else\n face = direction.y > 0.0 ? 1.0 : 4.0;\n } else {\n if ( absDirection.z > absDirection.y )\n face = direction.z > 0.0 ? 2.0 : 5.0;\n else\n face = direction.y > 0.0 ? 1.0 : 4.0;\n }\n return face;\n }\n vec2 getUV( vec3 direction, float face ) {\n vec2 uv;\n if ( face == 0.0 ) {\n uv = vec2( direction.z, direction.y ) / abs( direction.x );\n } else if ( face == 1.0 ) {\n uv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n } else if ( face == 2.0 ) {\n uv = vec2( - direction.x, direction.y ) / abs( direction.z );\n } else if ( face == 3.0 ) {\n uv = vec2( - direction.z, direction.y ) / abs( direction.x );\n } else if ( face == 4.0 ) {\n uv = vec2( - direction.x, direction.z ) / abs( direction.y );\n } else {\n uv = vec2( direction.x, direction.y ) / abs( direction.z );\n }\n return 0.5 * ( uv + 1.0 );\n }\n vec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n float face = getFace( direction );\n float filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n mipInt = max( mipInt, cubeUV_minMipLevel );\n float faceSize = exp2( mipInt );\n vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n if ( face > 2.0 ) {\n uv.y += faceSize;\n face -= 3.0;\n }\n uv.x += face * faceSize;\n uv.x += filterInt * 3.0 * cubeUV_minTileSize;\n uv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n uv.x *= CUBEUV_TEXEL_WIDTH;\n uv.y *= CUBEUV_TEXEL_HEIGHT;\n #ifdef texture2DGradEXT\n return texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n #else\n return texture2D( envMap, uv ).rgb;\n #endif\n }\n #define r0 1.0\n #define v0 0.339\n #define m0 - 2.0\n #define r1 0.8\n #define v1 0.276\n #define m1 - 1.0\n #define r4 0.4\n #define v4 0.046\n #define m4 2.0\n #define r5 0.305\n #define v5 0.016\n #define m5 3.0\n #define r6 0.21\n #define v6 0.0038\n #define m6 4.0\n float roughnessToMip( float roughness ) {\n float mip = 0.0;\n if ( roughness >= r1 ) {\n mip = ( r0 - roughness ) * ( m1 - m0 ) / ( r0 - r1 ) + m0;\n } else if ( roughness >= r4 ) {\n mip = ( r1 - roughness ) * ( m4 - m1 ) / ( r1 - r4 ) + m1;\n } else if ( roughness >= r5 ) {\n mip = ( r4 - roughness ) * ( m5 - m4 ) / ( r4 - r5 ) + m4;\n } else if ( roughness >= r6 ) {\n mip = ( r5 - roughness ) * ( m6 - m5 ) / ( r5 - r6 ) + m5;\n } else {\n mip = - 2.0 * log2( 1.16 * roughness ); }\n return mip;\n }\n vec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n float mip = clamp( roughnessToMip( roughness ), m0, CUBEUV_MAX_MIP );\n float mipF = fract( mip );\n float mipInt = floor( mip );\n vec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n if ( mipF == 0.0 ) {\n return vec4( color0, 1.0 );\n } else {\n vec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n return vec4( mix( color0, color1, mipF ), 1.0 );\n }\n }\n#endif";
+ var defaultnormal_vertex2 = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n mat3 m = mat3( instanceMatrix );\n transformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n transformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n transformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n vec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n #ifdef FLIP_SIDED\n transformedTangent = - transformedTangent;\n #endif\n#endif";
+ var displacementmap_pars_vertex2 = "#ifdef USE_DISPLACEMENTMAP\n uniform sampler2D displacementMap;\n uniform float displacementScale;\n uniform float displacementBias;\n#endif";
+ var displacementmap_vertex2 = "#ifdef USE_DISPLACEMENTMAP\n transformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif";
+ var emissivemap_fragment2 = "#ifdef USE_EMISSIVEMAP\n vec4 emissiveColor = texture2D( emissiveMap, vUv );\n totalEmissiveRadiance *= emissiveColor.rgb;\n#endif";
+ var emissivemap_pars_fragment2 = "#ifdef USE_EMISSIVEMAP\n uniform sampler2D emissiveMap;\n#endif";
+ var encodings_fragment2 = "gl_FragColor = linearToOutputTexel( gl_FragColor );";
+ var encodings_pars_fragment2 = "vec4 LinearToLinear( in vec4 value ) {\n return value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}";
+ var envmap_fragment2 = "#ifdef USE_ENVMAP\n #ifdef ENV_WORLDPOS\n vec3 cameraToFrag;\n if ( isOrthographic ) {\n cameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n } else {\n cameraToFrag = normalize( vWorldPosition - cameraPosition );\n }\n vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n #ifdef ENVMAP_MODE_REFLECTION\n vec3 reflectVec = reflect( cameraToFrag, worldNormal );\n #else\n vec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n #endif\n #else\n vec3 reflectVec = vReflect;\n #endif\n #ifdef ENVMAP_TYPE_CUBE\n vec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n #elif defined( ENVMAP_TYPE_CUBE_UV )\n vec4 envColor = textureCubeUV( envMap, reflectVec, 0.0 );\n #else\n vec4 envColor = vec4( 0.0 );\n #endif\n #ifdef ENVMAP_BLENDING_MULTIPLY\n outgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n #elif defined( ENVMAP_BLENDING_MIX )\n outgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n #elif defined( ENVMAP_BLENDING_ADD )\n outgoingLight += envColor.xyz * specularStrength * reflectivity;\n #endif\n#endif";
+ var envmap_common_pars_fragment2 = "#ifdef USE_ENVMAP\n uniform float envMapIntensity;\n uniform float flipEnvMap;\n #ifdef ENVMAP_TYPE_CUBE\n uniform samplerCube envMap;\n #else\n uniform sampler2D envMap;\n #endif\n \n#endif";
+ var envmap_pars_fragment2 = "#ifdef USE_ENVMAP\n uniform float reflectivity;\n #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n #define ENV_WORLDPOS\n #endif\n #ifdef ENV_WORLDPOS\n varying vec3 vWorldPosition;\n uniform float refractionRatio;\n #else\n varying vec3 vReflect;\n #endif\n#endif";
+ var envmap_pars_vertex2 = "#ifdef USE_ENVMAP\n #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n #define ENV_WORLDPOS\n #endif\n #ifdef ENV_WORLDPOS\n \n varying vec3 vWorldPosition;\n #else\n varying vec3 vReflect;\n uniform float refractionRatio;\n #endif\n#endif";
+ var envmap_vertex2 = "#ifdef USE_ENVMAP\n #ifdef ENV_WORLDPOS\n vWorldPosition = worldPosition.xyz;\n #else\n vec3 cameraToVertex;\n if ( isOrthographic ) {\n cameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n } else {\n cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n }\n vec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n #ifdef ENVMAP_MODE_REFLECTION\n vReflect = reflect( cameraToVertex, worldNormal );\n #else\n vReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n #endif\n #endif\n#endif";
+ var fog_vertex2 = "#ifdef USE_FOG\n vFogDepth = - mvPosition.z;\n#endif";
+ var fog_pars_vertex2 = "#ifdef USE_FOG\n varying float vFogDepth;\n#endif";
+ var fog_fragment2 = "#ifdef USE_FOG\n #ifdef FOG_EXP2\n float fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n #else\n float fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n #endif\n gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif";
+ var fog_pars_fragment2 = "#ifdef USE_FOG\n uniform vec3 fogColor;\n varying float vFogDepth;\n #ifdef FOG_EXP2\n uniform float fogDensity;\n #else\n uniform float fogNear;\n uniform float fogFar;\n #endif\n#endif";
+ var gradientmap_pars_fragment2 = "#ifdef USE_GRADIENTMAP\n uniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n float dotNL = dot( normal, lightDirection );\n vec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n #ifdef USE_GRADIENTMAP\n return vec3( texture2D( gradientMap, coord ).r );\n #else\n return ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n #endif\n}";
+ var lightmap_fragment2 = "#ifdef USE_LIGHTMAP\n vec4 lightMapTexel = texture2D( lightMap, vUv2 );\n vec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n reflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif";
+ var lightmap_pars_fragment2 = "#ifdef USE_LIGHTMAP\n uniform sampler2D lightMap;\n uniform float lightMapIntensity;\n#endif";
+ var lights_lambert_vertex2 = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n vLightBack = vec3( 0.0 );\n vIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\nvIndirectFront += getAmbientLightIrradiance( ambientLightColor );\nvIndirectFront += getLightProbeIrradiance( lightProbe, geometry.normal );\n#ifdef DOUBLE_SIDED\n vIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n vIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry.normal );\n#endif\n#if NUM_POINT_LIGHTS > 0\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n getPointLightInfo( pointLights[ i ], geometry, directLight );\n dotNL = dot( geometry.normal, directLight.direction );\n directLightColor_Diffuse = directLight.color;\n vLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n #ifdef DOUBLE_SIDED\n vLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n #endif\n }\n #pragma unroll_loop_end\n#endif\n#if NUM_SPOT_LIGHTS > 0\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n getSpotLightInfo( spotLights[ i ], geometry, directLight );\n dotNL = dot( geometry.normal, directLight.direction );\n directLightColor_Diffuse = directLight.color;\n vLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n #ifdef DOUBLE_SIDED\n vLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n #endif\n }\n #pragma unroll_loop_end\n#endif\n#if NUM_DIR_LIGHTS > 0\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n getDirectionalLightInfo( directionalLights[ i ], geometry, directLight );\n dotNL = dot( geometry.normal, directLight.direction );\n directLightColor_Diffuse = directLight.color;\n vLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n #ifdef DOUBLE_SIDED\n vLightBack += saturate( - dotNL ) * directLightColor_Diffuse;\n #endif\n }\n #pragma unroll_loop_end\n#endif\n#if NUM_HEMI_LIGHTS > 0\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n vIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n #ifdef DOUBLE_SIDED\n vIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry.normal );\n #endif\n }\n #pragma unroll_loop_end\n#endif";
+ var lights_pars_begin2 = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n float x = normal.x, y = normal.y, z = normal.z;\n vec3 result = shCoefficients[ 0 ] * 0.886227;\n result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n return result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n vec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n return irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n vec3 irradiance = ambientLightColor;\n return irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n #if defined ( PHYSICALLY_CORRECT_LIGHTS )\n float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n if ( cutoffDistance > 0.0 ) {\n distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n }\n return distanceFalloff;\n #else\n if ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n return pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n }\n return 1.0;\n #endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n return smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n struct DirectionalLight {\n vec3 direction;\n vec3 color;\n };\n uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n void getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n light.color = directionalLight.color;\n light.direction = directionalLight.direction;\n light.visible = true;\n }\n#endif\n#if NUM_POINT_LIGHTS > 0\n struct PointLight {\n vec3 position;\n vec3 color;\n float distance;\n float decay;\n };\n uniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n void getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n vec3 lVector = pointLight.position - geometry.position;\n light.direction = normalize( lVector );\n float lightDistance = length( lVector );\n light.color = pointLight.color;\n light.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n light.visible = ( light.color != vec3( 0.0 ) );\n }\n#endif\n#if NUM_SPOT_LIGHTS > 0\n struct SpotLight {\n vec3 position;\n vec3 direction;\n vec3 color;\n float distance;\n float decay;\n float coneCos;\n float penumbraCos;\n };\n uniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n void getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n vec3 lVector = spotLight.position - geometry.position;\n light.direction = normalize( lVector );\n float angleCos = dot( light.direction, spotLight.direction );\n float spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n if ( spotAttenuation > 0.0 ) {\n float lightDistance = length( lVector );\n light.color = spotLight.color * spotAttenuation;\n light.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n light.visible = ( light.color != vec3( 0.0 ) );\n } else {\n light.color = vec3( 0.0 );\n light.visible = false;\n }\n }\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n struct RectAreaLight {\n vec3 color;\n vec3 position;\n vec3 halfWidth;\n vec3 halfHeight;\n };\n uniform sampler2D ltc_1; uniform sampler2D ltc_2;\n uniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n struct HemisphereLight {\n vec3 direction;\n vec3 skyColor;\n vec3 groundColor;\n };\n uniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n vec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n float dotNL = dot( normal, hemiLight.direction );\n float hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n vec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n return irradiance;\n }\n#endif";
+ var envmap_physical_pars_fragment2 = "#if defined( USE_ENVMAP )\n vec3 getIBLIrradiance( const in vec3 normal ) {\n #if defined( ENVMAP_TYPE_CUBE_UV )\n vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n vec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n return PI * envMapColor.rgb * envMapIntensity;\n #else\n return vec3( 0.0 );\n #endif\n }\n vec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n #if defined( ENVMAP_TYPE_CUBE_UV )\n vec3 reflectVec = reflect( - viewDir, normal );\n reflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n reflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n vec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n return envMapColor.rgb * envMapIntensity;\n #else\n return vec3( 0.0 );\n #endif\n }\n#endif";
+ var lights_toon_fragment2 = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;";
+ var lights_toon_pars_fragment2 = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n vec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n vec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct RE_Direct_Toon\n#define RE_IndirectDiffuse RE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material ) (0)";
+ var lights_phong_fragment2 = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";
+ var lights_phong_pars_fragment2 = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n vec3 diffuseColor;\n vec3 specularColor;\n float specularShininess;\n float specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n float dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n vec3 irradiance = dotNL * directLight.color;\n reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n reflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct RE_Direct_BlinnPhong\n#define RE_IndirectDiffuse RE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material ) (0)";
+ var lights_physical_fragment2 = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n #ifdef SPECULAR\n float specularIntensityFactor = specularIntensity;\n vec3 specularColorFactor = specularColor;\n #ifdef USE_SPECULARINTENSITYMAP\n specularIntensityFactor *= texture2D( specularIntensityMap, vUv ).a;\n #endif\n #ifdef USE_SPECULARCOLORMAP\n specularColorFactor *= texture2D( specularColorMap, vUv ).rgb;\n #endif\n material.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n #else\n float specularIntensityFactor = 1.0;\n vec3 specularColorFactor = vec3( 1.0 );\n material.specularF90 = 1.0;\n #endif\n material.specularColor = mix( min( pow2( ( ior - 1.0 ) / ( ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n material.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n material.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n material.clearcoat = clearcoat;\n material.clearcoatRoughness = clearcoatRoughness;\n material.clearcoatF0 = vec3( 0.04 );\n material.clearcoatF90 = 1.0;\n #ifdef USE_CLEARCOATMAP\n material.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n #endif\n #ifdef USE_CLEARCOAT_ROUGHNESSMAP\n material.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n #endif\n material.clearcoat = saturate( material.clearcoat ); material.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n material.clearcoatRoughness += geometryRoughness;\n material.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n material.iridescence = iridescence;\n material.iridescenceIOR = iridescenceIOR;\n #ifdef USE_IRIDESCENCEMAP\n material.iridescence *= texture2D( iridescenceMap, vUv ).r;\n #endif\n #ifdef USE_IRIDESCENCE_THICKNESSMAP\n material.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vUv ).g + iridescenceThicknessMinimum;\n #else\n material.iridescenceThickness = iridescenceThicknessMaximum;\n #endif\n#endif\n#ifdef USE_SHEEN\n material.sheenColor = sheenColor;\n #ifdef USE_SHEENCOLORMAP\n material.sheenColor *= texture2D( sheenColorMap, vUv ).rgb;\n #endif\n material.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n #ifdef USE_SHEENROUGHNESSMAP\n material.sheenRoughness *= texture2D( sheenRoughnessMap, vUv ).a;\n #endif\n#endif";
+ var lights_physical_pars_fragment2 = "struct PhysicalMaterial {\n vec3 diffuseColor;\n float roughness;\n vec3 specularColor;\n float specularF90;\n #ifdef USE_CLEARCOAT\n float clearcoat;\n float clearcoatRoughness;\n vec3 clearcoatF0;\n float clearcoatF90;\n #endif\n #ifdef USE_IRIDESCENCE\n float iridescence;\n float iridescenceIOR;\n float iridescenceThickness;\n vec3 iridescenceFresnel;\n vec3 iridescenceF0;\n #endif\n #ifdef USE_SHEEN\n vec3 sheenColor;\n float sheenRoughness;\n #endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness) {\n float dotNV = saturate( dot( normal, viewDir ) );\n float r2 = roughness * roughness;\n float a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n float b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n float DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n return saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n float dotNV = saturate( dot( normal, viewDir ) );\n const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n vec4 r = roughness * c0 + c1;\n float a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n vec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n return fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n vec2 fab = DFGApprox( normal, viewDir, roughness );\n return specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n vec2 fab = DFGApprox( normal, viewDir, roughness );\n #ifdef USE_IRIDESCENCE\n vec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n #else\n vec3 Fr = specularColor;\n #endif\n vec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n float Ess = fab.x + fab.y;\n float Ems = 1.0 - Ess;\n vec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619; vec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n singleScatter += FssEss;\n multiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n void RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n vec3 normal = geometry.normal;\n vec3 viewDir = geometry.viewDir;\n vec3 position = geometry.position;\n vec3 lightPos = rectAreaLight.position;\n vec3 halfWidth = rectAreaLight.halfWidth;\n vec3 halfHeight = rectAreaLight.halfHeight;\n vec3 lightColor = rectAreaLight.color;\n float roughness = material.roughness;\n vec3 rectCoords[ 4 ];\n rectCoords[ 0 ] = lightPos + halfWidth - halfHeight; rectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n rectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n rectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n vec2 uv = LTC_Uv( normal, viewDir, roughness );\n vec4 t1 = texture2D( ltc_1, uv );\n vec4 t2 = texture2D( ltc_2, uv );\n mat3 mInv = mat3(\n vec3( t1.x, 0, t1.y ),\n vec3( 0, 1, 0 ),\n vec3( t1.z, 0, t1.w )\n );\n vec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n reflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n reflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n }\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n float dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n vec3 irradiance = dotNL * directLight.color;\n #ifdef USE_CLEARCOAT\n float dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n vec3 ccIrradiance = dotNLcc * directLight.color;\n clearcoatSpecular += ccIrradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n #endif\n #ifdef USE_SHEEN\n sheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n #endif\n #ifdef USE_IRIDESCENCE\n reflectedLight.directSpecular += irradiance * BRDF_GGX_Iridescence( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness );\n #else\n reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );\n #endif\n reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n #ifdef USE_CLEARCOAT\n clearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n #endif\n #ifdef USE_SHEEN\n sheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n #endif\n vec3 singleScattering = vec3( 0.0 );\n vec3 multiScattering = vec3( 0.0 );\n vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n #ifdef USE_IRIDESCENCE\n computeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n #else\n computeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n #endif\n vec3 totalScattering = singleScattering + multiScattering;\n vec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n reflectedLight.indirectSpecular += radiance * singleScattering;\n reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct RE_Direct_Physical\n#define RE_Direct_RectArea RE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse RE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular RE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n return saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}";
+ var lights_fragment_begin2 = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n geometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n float dotNVi = saturate( dot( normal, geometry.viewDir ) );\n if ( material.iridescenceThickness == 0.0 ) {\n material.iridescence = 0.0;\n } else {\n material.iridescence = saturate( material.iridescence );\n }\n if ( material.iridescence > 0.0 ) {\n material.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n material.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n }\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n PointLight pointLight;\n #if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n PointLightShadow pointLightShadow;\n #endif\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n pointLight = pointLights[ i ];\n getPointLightInfo( pointLight, geometry, directLight );\n #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n pointLightShadow = pointLightShadows[ i ];\n directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n #endif\n RE_Direct( directLight, geometry, material, reflectedLight );\n }\n #pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n SpotLight spotLight;\n #if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n SpotLightShadow spotLightShadow;\n #endif\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n spotLight = spotLights[ i ];\n getSpotLightInfo( spotLight, geometry, directLight );\n #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n spotLightShadow = spotLightShadows[ i ];\n directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n #endif\n RE_Direct( directLight, geometry, material, reflectedLight );\n }\n #pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n DirectionalLight directionalLight;\n #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n DirectionalLightShadow directionalLightShadow;\n #endif\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n directionalLight = directionalLights[ i ];\n getDirectionalLightInfo( directionalLight, geometry, directLight );\n #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n directionalLightShadow = directionalLightShadows[ i ];\n directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n #endif\n RE_Direct( directLight, geometry, material, reflectedLight );\n }\n #pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n RectAreaLight rectAreaLight;\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n rectAreaLight = rectAreaLights[ i ];\n RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n }\n #pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n vec3 iblIrradiance = vec3( 0.0 );\n vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n irradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n #if ( NUM_HEMI_LIGHTS > 0 )\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n }\n #pragma unroll_loop_end\n #endif\n#endif\n#if defined( RE_IndirectSpecular )\n vec3 radiance = vec3( 0.0 );\n vec3 clearcoatRadiance = vec3( 0.0 );\n#endif";
+ var lights_fragment_maps2 = "#if defined( RE_IndirectDiffuse )\n #ifdef USE_LIGHTMAP\n vec4 lightMapTexel = texture2D( lightMap, vUv2 );\n vec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n irradiance += lightMapIrradiance;\n #endif\n #if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n iblIrradiance += getIBLIrradiance( geometry.normal );\n #endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n radiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n #ifdef USE_CLEARCOAT\n clearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n #endif\n#endif";
+ var lights_fragment_end2 = "#if defined( RE_IndirectDiffuse )\n RE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n RE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif";
+ var logdepthbuf_fragment2 = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n gl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif";
+ var logdepthbuf_pars_fragment2 = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n uniform float logDepthBufFC;\n varying float vFragDepth;\n varying float vIsPerspective;\n#endif";
+ var logdepthbuf_pars_vertex2 = "#ifdef USE_LOGDEPTHBUF\n #ifdef USE_LOGDEPTHBUF_EXT\n varying float vFragDepth;\n varying float vIsPerspective;\n #else\n uniform float logDepthBufFC;\n #endif\n#endif";
+ var logdepthbuf_vertex2 = "#ifdef USE_LOGDEPTHBUF\n #ifdef USE_LOGDEPTHBUF_EXT\n vFragDepth = 1.0 + gl_Position.w;\n vIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n #else\n if ( isPerspectiveMatrix( projectionMatrix ) ) {\n gl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n gl_Position.z *= gl_Position.w;\n }\n #endif\n#endif";
+ var map_fragment2 = "#ifdef USE_MAP\n vec4 sampledDiffuseColor = texture2D( map, vUv );\n #ifdef DECODE_VIDEO_TEXTURE\n sampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n #endif\n diffuseColor *= sampledDiffuseColor;\n#endif";
+ var map_pars_fragment2 = "#ifdef USE_MAP\n uniform sampler2D map;\n#endif";
+ var map_particle_fragment2 = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n vec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n diffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n diffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif";
+ var map_particle_pars_fragment2 = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n uniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n uniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n uniform sampler2D alphaMap;\n#endif";
+ var metalnessmap_fragment2 = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n vec4 texelMetalness = texture2D( metalnessMap, vUv );\n metalnessFactor *= texelMetalness.b;\n#endif";
+ var metalnessmap_pars_fragment2 = "#ifdef USE_METALNESSMAP\n uniform sampler2D metalnessMap;\n#endif";
+ var morphcolor_vertex2 = "#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n vColor *= morphTargetBaseInfluence;\n for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n #if defined( USE_COLOR_ALPHA )\n if ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n #elif defined( USE_COLOR )\n if ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n #endif\n }\n#endif";
+ var morphnormal_vertex2 = "#ifdef USE_MORPHNORMALS\n objectNormal *= morphTargetBaseInfluence;\n #ifdef MORPHTARGETS_TEXTURE\n for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n if ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n }\n #else\n objectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n objectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n objectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n objectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n #endif\n#endif";
+ var morphtarget_pars_vertex2 = "#ifdef USE_MORPHTARGETS\n uniform float morphTargetBaseInfluence;\n #ifdef MORPHTARGETS_TEXTURE\n uniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n uniform sampler2DArray morphTargetsTexture;\n uniform ivec2 morphTargetsTextureSize;\n vec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n int texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n int y = texelIndex / morphTargetsTextureSize.x;\n int x = texelIndex - y * morphTargetsTextureSize.x;\n ivec3 morphUV = ivec3( x, y, morphTargetIndex );\n return texelFetch( morphTargetsTexture, morphUV, 0 );\n }\n #else\n #ifndef USE_MORPHNORMALS\n uniform float morphTargetInfluences[ 8 ];\n #else\n uniform float morphTargetInfluences[ 4 ];\n #endif\n #endif\n#endif";
+ var morphtarget_vertex2 = "#ifdef USE_MORPHTARGETS\n transformed *= morphTargetBaseInfluence;\n #ifdef MORPHTARGETS_TEXTURE\n for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n if ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n }\n #else\n transformed += morphTarget0 * morphTargetInfluences[ 0 ];\n transformed += morphTarget1 * morphTargetInfluences[ 1 ];\n transformed += morphTarget2 * morphTargetInfluences[ 2 ];\n transformed += morphTarget3 * morphTargetInfluences[ 3 ];\n #ifndef USE_MORPHNORMALS\n transformed += morphTarget4 * morphTargetInfluences[ 4 ];\n transformed += morphTarget5 * morphTargetInfluences[ 5 ];\n transformed += morphTarget6 * morphTargetInfluences[ 6 ];\n transformed += morphTarget7 * morphTargetInfluences[ 7 ];\n #endif\n #endif\n#endif";
+ var normal_fragment_begin2 = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n vec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n vec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n vec3 normal = normalize( cross( fdx, fdy ) );\n#else\n vec3 normal = normalize( vNormal );\n #ifdef DOUBLE_SIDED\n normal = normal * faceDirection;\n #endif\n #ifdef USE_TANGENT\n vec3 tangent = normalize( vTangent );\n vec3 bitangent = normalize( vBitangent );\n #ifdef DOUBLE_SIDED\n tangent = tangent * faceDirection;\n bitangent = bitangent * faceDirection;\n #endif\n #if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n mat3 vTBN = mat3( tangent, bitangent, normal );\n #endif\n #endif\n#endif\nvec3 geometryNormal = normal;";
+ var normal_fragment_maps2 = "#ifdef OBJECTSPACE_NORMALMAP\n normal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n #ifdef FLIP_SIDED\n normal = - normal;\n #endif\n #ifdef DOUBLE_SIDED\n normal = normal * faceDirection;\n #endif\n normal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n mapN.xy *= normalScale;\n #ifdef USE_TANGENT\n normal = normalize( vTBN * mapN );\n #else\n normal = perturbNormal2Arb( - vViewPosition, normal, mapN, faceDirection );\n #endif\n#elif defined( USE_BUMPMAP )\n normal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif";
+ var normal_pars_fragment2 = "#ifndef FLAT_SHADED\n varying vec3 vNormal;\n #ifdef USE_TANGENT\n varying vec3 vTangent;\n varying vec3 vBitangent;\n #endif\n#endif";
+ var normal_pars_vertex2 = "#ifndef FLAT_SHADED\n varying vec3 vNormal;\n #ifdef USE_TANGENT\n varying vec3 vTangent;\n varying vec3 vBitangent;\n #endif\n#endif";
+ var normal_vertex2 = "#ifndef FLAT_SHADED\n vNormal = normalize( transformedNormal );\n #ifdef USE_TANGENT\n vTangent = normalize( transformedTangent );\n vBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n #endif\n#endif";
+ var normalmap_pars_fragment2 = "#ifdef USE_NORMALMAP\n uniform sampler2D normalMap;\n uniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n uniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection ) {\n vec3 q0 = dFdx( eye_pos.xyz );\n vec3 q1 = dFdy( eye_pos.xyz );\n vec2 st0 = dFdx( vUv.st );\n vec2 st1 = dFdy( vUv.st );\n vec3 N = surf_norm;\n vec3 q1perp = cross( q1, N );\n vec3 q0perp = cross( N, q0 );\n vec3 T = q1perp * st0.x + q0perp * st1.x;\n vec3 B = q1perp * st0.y + q0perp * st1.y;\n float det = max( dot( T, T ), dot( B, B ) );\n float scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );\n return normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );\n }\n#endif";
+ var clearcoat_normal_fragment_begin2 = "#ifdef USE_CLEARCOAT\n vec3 clearcoatNormal = geometryNormal;\n#endif";
+ var clearcoat_normal_fragment_maps2 = "#ifdef USE_CLEARCOAT_NORMALMAP\n vec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n clearcoatMapN.xy *= clearcoatNormalScale;\n #ifdef USE_TANGENT\n clearcoatNormal = normalize( vTBN * clearcoatMapN );\n #else\n clearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN, faceDirection );\n #endif\n#endif";
+ var clearcoat_pars_fragment2 = "#ifdef USE_CLEARCOATMAP\n uniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n uniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n uniform sampler2D clearcoatNormalMap;\n uniform vec2 clearcoatNormalScale;\n#endif";
+ var iridescence_pars_fragment2 = "#ifdef USE_IRIDESCENCEMAP\n uniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n uniform sampler2D iridescenceThicknessMap;\n#endif";
+ var output_fragment2 = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= transmissionAlpha + 0.1;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );";
+ var packing2 = "vec3 packNormalToRGB( const in vec3 normal ) {\n return normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n return 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n vec4 r = vec4( fract( v * PackFactors ), v );\n r.yzw -= r.xyz * ShiftRight8; return r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n return dot( v, UnpackFactors );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n vec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n return vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n return vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n return ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n return linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n return ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n return ( near * far ) / ( ( far - near ) * invClipZ - far );\n}";
+ var premultiplied_alpha_fragment2 = "#ifdef PREMULTIPLIED_ALPHA\n gl_FragColor.rgb *= gl_FragColor.a;\n#endif";
+ var project_vertex2 = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n mvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;";
+ var dithering_fragment2 = "#ifdef DITHERING\n gl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif";
+ var dithering_pars_fragment2 = "#ifdef DITHERING\n vec3 dithering( vec3 color ) {\n float grid_position = rand( gl_FragCoord.xy );\n vec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n dither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n return color + dither_shift_RGB;\n }\n#endif";
+ var roughnessmap_fragment2 = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n vec4 texelRoughness = texture2D( roughnessMap, vUv );\n roughnessFactor *= texelRoughness.g;\n#endif";
+ var roughnessmap_pars_fragment2 = "#ifdef USE_ROUGHNESSMAP\n uniform sampler2D roughnessMap;\n#endif";
+ var shadowmap_pars_fragment2 = "#ifdef USE_SHADOWMAP\n #if NUM_DIR_LIGHT_SHADOWS > 0\n uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n struct DirectionalLightShadow {\n float shadowBias;\n float shadowNormalBias;\n float shadowRadius;\n vec2 shadowMapSize;\n };\n uniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n #endif\n #if NUM_SPOT_LIGHT_SHADOWS > 0\n uniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n varying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n struct SpotLightShadow {\n float shadowBias;\n float shadowNormalBias;\n float shadowRadius;\n vec2 shadowMapSize;\n };\n uniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n #endif\n #if NUM_POINT_LIGHT_SHADOWS > 0\n uniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n varying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n struct PointLightShadow {\n float shadowBias;\n float shadowNormalBias;\n float shadowRadius;\n vec2 shadowMapSize;\n float shadowCameraNear;\n float shadowCameraFar;\n };\n uniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n #endif\n float texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n return step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n }\n vec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n return unpackRGBATo2Half( texture2D( shadow, uv ) );\n }\n float VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n float occlusion = 1.0;\n vec2 distribution = texture2DDistribution( shadow, uv );\n float hard_shadow = step( compare , distribution.x );\n if (hard_shadow != 1.0 ) {\n float distance = compare - distribution.x ;\n float variance = max( 0.00000, distribution.y * distribution.y );\n float softness_probability = variance / (variance + distance * distance ); softness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 ); occlusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n }\n return occlusion;\n }\n float getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n float shadow = 1.0;\n shadowCoord.xyz /= shadowCoord.w;\n shadowCoord.z += shadowBias;\n bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n bool inFrustum = all( inFrustumVec );\n bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n bool frustumTest = all( frustumTestVec );\n if ( frustumTest ) {\n #if defined( SHADOWMAP_TYPE_PCF )\n vec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n float dx0 = - texelSize.x * shadowRadius;\n float dy0 = - texelSize.y * shadowRadius;\n float dx1 = + texelSize.x * shadowRadius;\n float dy1 = + texelSize.y * shadowRadius;\n float dx2 = dx0 / 2.0;\n float dy2 = dy0 / 2.0;\n float dx3 = dx1 / 2.0;\n float dy3 = dy1 / 2.0;\n shadow = (\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n ) * ( 1.0 / 17.0 );\n #elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n vec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n float dx = texelSize.x;\n float dy = texelSize.y;\n vec2 uv = shadowCoord.xy;\n vec2 f = fract( uv * shadowMapSize + 0.5 );\n uv -= f * texelSize;\n shadow = (\n texture2DCompare( shadowMap, uv, shadowCoord.z ) +\n texture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n texture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n texture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n mix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), \n texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n f.x ) +\n mix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), \n texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n f.x ) +\n mix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), \n texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n f.y ) +\n mix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), \n texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n f.y ) +\n mix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), \n texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n f.x ),\n mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), \n texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n f.x ),\n f.y )\n ) * ( 1.0 / 9.0 );\n #elif defined( SHADOWMAP_TYPE_VSM )\n shadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n #else\n shadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n #endif\n }\n return shadow;\n }\n vec2 cubeToUV( vec3 v, float texelSizeY ) {\n vec3 absV = abs( v );\n float scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n absV *= scaleToCube;\n v *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n vec2 planar = v.xy;\n float almostATexel = 1.5 * texelSizeY;\n float almostOne = 1.0 - almostATexel;\n if ( absV.z >= almostOne ) {\n if ( v.z > 0.0 )\n planar.x = 4.0 - v.x;\n } else if ( absV.x >= almostOne ) {\n float signX = sign( v.x );\n planar.x = v.z * signX + 2.0 * signX;\n } else if ( absV.y >= almostOne ) {\n float signY = sign( v.y );\n planar.x = v.x + 2.0 * signY + 2.0;\n planar.y = v.z * signY - 2.0;\n }\n return vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n }\n float getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n vec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n vec3 lightToPosition = shadowCoord.xyz;\n float dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear ); dp += shadowBias;\n vec3 bd3D = normalize( lightToPosition );\n #if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n vec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n return (\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n ) * ( 1.0 / 9.0 );\n #else\n return texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n #endif\n }\n#endif";
+ var shadowmap_pars_vertex2 = "#ifdef USE_SHADOWMAP\n #if NUM_DIR_LIGHT_SHADOWS > 0\n uniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n struct DirectionalLightShadow {\n float shadowBias;\n float shadowNormalBias;\n float shadowRadius;\n vec2 shadowMapSize;\n };\n uniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n #endif\n #if NUM_SPOT_LIGHT_SHADOWS > 0\n uniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n varying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n struct SpotLightShadow {\n float shadowBias;\n float shadowNormalBias;\n float shadowRadius;\n vec2 shadowMapSize;\n };\n uniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n #endif\n #if NUM_POINT_LIGHT_SHADOWS > 0\n uniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n varying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n struct PointLightShadow {\n float shadowBias;\n float shadowNormalBias;\n float shadowRadius;\n vec2 shadowMapSize;\n float shadowCameraNear;\n float shadowCameraFar;\n };\n uniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n #endif\n#endif";
+ var shadowmap_vertex2 = "#ifdef USE_SHADOWMAP\n #if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n vec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n vec4 shadowWorldPosition;\n #endif\n #if NUM_DIR_LIGHT_SHADOWS > 0\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n }\n #pragma unroll_loop_end\n #endif\n #if NUM_SPOT_LIGHT_SHADOWS > 0\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias, 0 );\n vSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * shadowWorldPosition;\n }\n #pragma unroll_loop_end\n #endif\n #if NUM_POINT_LIGHT_SHADOWS > 0\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n vPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n }\n #pragma unroll_loop_end\n #endif\n#endif";
+ var shadowmask_pars_fragment2 = "float getShadowMask() {\n float shadow = 1.0;\n #ifdef USE_SHADOWMAP\n #if NUM_DIR_LIGHT_SHADOWS > 0\n DirectionalLightShadow directionalLight;\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n directionalLight = directionalLightShadows[ i ];\n shadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n }\n #pragma unroll_loop_end\n #endif\n #if NUM_SPOT_LIGHT_SHADOWS > 0\n SpotLightShadow spotLight;\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n spotLight = spotLightShadows[ i ];\n shadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n }\n #pragma unroll_loop_end\n #endif\n #if NUM_POINT_LIGHT_SHADOWS > 0\n PointLightShadow pointLight;\n #pragma unroll_loop_start\n for ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n pointLight = pointLightShadows[ i ];\n shadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n }\n #pragma unroll_loop_end\n #endif\n #endif\n return shadow;\n}";
+ var skinbase_vertex2 = "#ifdef USE_SKINNING\n mat4 boneMatX = getBoneMatrix( skinIndex.x );\n mat4 boneMatY = getBoneMatrix( skinIndex.y );\n mat4 boneMatZ = getBoneMatrix( skinIndex.z );\n mat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif";
+ var skinning_pars_vertex2 = "#ifdef USE_SKINNING\n uniform mat4 bindMatrix;\n uniform mat4 bindMatrixInverse;\n uniform highp sampler2D boneTexture;\n uniform int boneTextureSize;\n mat4 getBoneMatrix( const in float i ) {\n float j = i * 4.0;\n float x = mod( j, float( boneTextureSize ) );\n float y = floor( j / float( boneTextureSize ) );\n float dx = 1.0 / float( boneTextureSize );\n float dy = 1.0 / float( boneTextureSize );\n y = dy * ( y + 0.5 );\n vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n mat4 bone = mat4( v1, v2, v3, v4 );\n return bone;\n }\n#endif";
+ var skinning_vertex2 = "#ifdef USE_SKINNING\n vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n vec4 skinned = vec4( 0.0 );\n skinned += boneMatX * skinVertex * skinWeight.x;\n skinned += boneMatY * skinVertex * skinWeight.y;\n skinned += boneMatZ * skinVertex * skinWeight.z;\n skinned += boneMatW * skinVertex * skinWeight.w;\n transformed = ( bindMatrixInverse * skinned ).xyz;\n#endif";
+ var skinnormal_vertex2 = "#ifdef USE_SKINNING\n mat4 skinMatrix = mat4( 0.0 );\n skinMatrix += skinWeight.x * boneMatX;\n skinMatrix += skinWeight.y * boneMatY;\n skinMatrix += skinWeight.z * boneMatZ;\n skinMatrix += skinWeight.w * boneMatW;\n skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n #ifdef USE_TANGENT\n objectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n #endif\n#endif";
+ var specularmap_fragment2 = "float specularStrength;\n#ifdef USE_SPECULARMAP\n vec4 texelSpecular = texture2D( specularMap, vUv );\n specularStrength = texelSpecular.r;\n#else\n specularStrength = 1.0;\n#endif";
+ var specularmap_pars_fragment2 = "#ifdef USE_SPECULARMAP\n uniform sampler2D specularMap;\n#endif";
+ var tonemapping_fragment2 = "#if defined( TONE_MAPPING )\n gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif";
+ var tonemapping_pars_fragment2 = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n return toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n color *= toneMappingExposure;\n return saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n color *= toneMappingExposure;\n color = max( vec3( 0.0 ), color - 0.004 );\n return pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n vec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n vec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n return a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n const mat3 ACESInputMat = mat3(\n vec3( 0.59719, 0.07600, 0.02840 ), vec3( 0.35458, 0.90834, 0.13383 ),\n vec3( 0.04823, 0.01566, 0.83777 )\n );\n const mat3 ACESOutputMat = mat3(\n vec3( 1.60475, -0.10208, -0.00327 ), vec3( -0.53108, 1.10813, -0.07276 ),\n vec3( -0.07367, -0.00605, 1.07602 )\n );\n color *= toneMappingExposure / 0.6;\n color = ACESInputMat * color;\n color = RRTAndODTFit( color );\n color = ACESOutputMat * color;\n return saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }";
+ var transmission_fragment2 = "#ifdef USE_TRANSMISSION\n float transmissionAlpha = 1.0;\n float transmissionFactor = transmission;\n float thicknessFactor = thickness;\n #ifdef USE_TRANSMISSIONMAP\n transmissionFactor *= texture2D( transmissionMap, vUv ).r;\n #endif\n #ifdef USE_THICKNESSMAP\n thicknessFactor *= texture2D( thicknessMap, vUv ).g;\n #endif\n vec3 pos = vWorldPosition;\n vec3 v = normalize( cameraPosition - pos );\n vec3 n = inverseTransformDirection( normal, viewMatrix );\n vec4 transmission = getIBLVolumeRefraction(\n n, v, roughnessFactor, material.diffuseColor, material.specularColor, material.specularF90,\n pos, modelMatrix, viewMatrix, projectionMatrix, ior, thicknessFactor,\n attenuationColor, attenuationDistance );\n totalDiffuse = mix( totalDiffuse, transmission.rgb, transmissionFactor );\n transmissionAlpha = mix( transmissionAlpha, transmission.a, transmissionFactor );\n#endif";
+ var transmission_pars_fragment2 = "#ifdef USE_TRANSMISSION\n uniform float transmission;\n uniform float thickness;\n uniform float attenuationDistance;\n uniform vec3 attenuationColor;\n #ifdef USE_TRANSMISSIONMAP\n uniform sampler2D transmissionMap;\n #endif\n #ifdef USE_THICKNESSMAP\n uniform sampler2D thicknessMap;\n #endif\n uniform vec2 transmissionSamplerSize;\n uniform sampler2D transmissionSamplerMap;\n uniform mat4 modelMatrix;\n uniform mat4 projectionMatrix;\n varying vec3 vWorldPosition;\n vec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n vec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n vec3 modelScale;\n modelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n modelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n modelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n return normalize( refractionVector ) * thickness * modelScale;\n }\n float applyIorToRoughness( const in float roughness, const in float ior ) {\n return roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n }\n vec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n float framebufferLod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n #ifdef texture2DLodEXT\n return texture2DLodEXT( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n #else\n return texture2D( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n #endif\n }\n vec3 applyVolumeAttenuation( const in vec3 radiance, const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n if ( attenuationDistance == 0.0 ) {\n return radiance;\n } else {\n vec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n vec3 transmittance = exp( - attenuationCoefficient * transmissionDistance ); return transmittance * radiance;\n }\n }\n vec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n const in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n const in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n const in vec3 attenuationColor, const in float attenuationDistance ) {\n vec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n vec3 refractedRayExit = position + transmissionRay;\n vec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n vec2 refractionCoords = ndcPos.xy / ndcPos.w;\n refractionCoords += 1.0;\n refractionCoords /= 2.0;\n vec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n vec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance );\n vec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n return vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a );\n }\n#endif";
+ var uv_pars_fragment2 = "#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n varying vec2 vUv;\n#endif";
+ var uv_pars_vertex2 = "#ifdef USE_UV\n #ifdef UVS_VERTEX_ONLY\n vec2 vUv;\n #else\n varying vec2 vUv;\n #endif\n uniform mat3 uvTransform;\n#endif";
+ var uv_vertex2 = "#ifdef USE_UV\n vUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif";
+ var uv2_pars_fragment2 = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n varying vec2 vUv2;\n#endif";
+ var uv2_pars_vertex2 = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n attribute vec2 uv2;\n varying vec2 vUv2;\n uniform mat3 uv2Transform;\n#endif";
+ var uv2_vertex2 = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n vUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif";
+ var worldpos_vertex2 = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION )\n vec4 worldPosition = vec4( transformed, 1.0 );\n #ifdef USE_INSTANCING\n worldPosition = instanceMatrix * worldPosition;\n #endif\n worldPosition = modelMatrix * worldPosition;\n#endif";
+ var vertex$g2 = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n vUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n gl_Position = vec4( position.xy, 1.0, 1.0 );\n}";
+ var fragment$g2 = "uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n gl_FragColor = texture2D( t2D, vUv );\n #ifdef DECODE_VIDEO_TEXTURE\n gl_FragColor = vec4( mix( pow( gl_FragColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), gl_FragColor.rgb * 0.0773993808, vec3( lessThanEqual( gl_FragColor.rgb, vec3( 0.04045 ) ) ) ), gl_FragColor.w );\n #endif\n #include \n #include \n}";
+ var vertex$f2 = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n vWorldDirection = transformDirection( position, modelMatrix );\n #include \n #include \n gl_Position.z = gl_Position.w;\n}";
+ var fragment$f2 = "#include \nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n vec3 vReflect = vWorldDirection;\n #include \n gl_FragColor = envColor;\n gl_FragColor.a *= opacity;\n #include \n #include \n}";
+ var vertex$e2 = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n #include \n #include \n #ifdef USE_DISPLACEMENTMAP\n #include \n #include \n #include \n #endif\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n vHighPrecisionZW = gl_Position.zw;\n}";
+ var fragment$e2 = "#if DEPTH_PACKING == 3200\n uniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n #include \n vec4 diffuseColor = vec4( 1.0 );\n #if DEPTH_PACKING == 3200\n diffuseColor.a = opacity;\n #endif\n #include \n #include \n #include \n #include \n float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n #if DEPTH_PACKING == 3200\n gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n #elif DEPTH_PACKING == 3201\n gl_FragColor = packDepthToRGBA( fragCoordZ );\n #endif\n}";
+ var vertex$d2 = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n #include \n #include \n #ifdef USE_DISPLACEMENTMAP\n #include \n #include \n #include \n #endif\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n vWorldPosition = worldPosition.xyz;\n}";
+ var fragment$d2 = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n #include \n vec4 diffuseColor = vec4( 1.0 );\n #include \n #include \n #include \n float dist = length( vWorldPosition - referencePosition );\n dist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n dist = saturate( dist );\n gl_FragColor = packDepthToRGBA( dist );\n}";
+ var vertex$c2 = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n vWorldDirection = transformDirection( position, modelMatrix );\n #include \n #include \n}";
+ var fragment$c2 = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n vec3 direction = normalize( vWorldDirection );\n vec2 sampleUV = equirectUv( direction );\n gl_FragColor = texture2D( tEquirect, sampleUV );\n #include \n #include \n}";
+ var vertex$b2 = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n vLineDistance = scale * lineDistance;\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n}";
+ var fragment$b2 = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n #include \n if ( mod( vLineDistance, totalSize ) > dashSize ) {\n discard;\n }\n vec3 outgoingLight = vec3( 0.0 );\n vec4 diffuseColor = vec4( diffuse, opacity );\n #include \n #include \n outgoingLight = diffuseColor.rgb;\n #include \n #include \n #include \n #include \n #include \n}";
+ var vertex$a2 = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n #include \n #include \n #include \n #include \n #if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n #include \n #include \n #include \n #include \n #include \n #endif\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n}";
+ var fragment$a2 = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n varying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n #include \n vec4 diffuseColor = vec4( diffuse, opacity );\n #include \n #include \n #include \n #include \n #include \n #include \n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n #ifdef USE_LIGHTMAP\n vec4 lightMapTexel = texture2D( lightMap, vUv2 );\n reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n #else\n reflectedLight.indirectDiffuse += vec3( 1.0 );\n #endif\n #include \n reflectedLight.indirectDiffuse *= diffuseColor.rgb;\n vec3 outgoingLight = reflectedLight.indirectDiffuse;\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n}";
+ var vertex$92 = "#define LAMBERT\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n varying vec3 vLightBack;\n varying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #include \n}";
+ var fragment$92 = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n varying vec3 vLightBack;\n varying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n #include \n vec4 diffuseColor = vec4( diffuse, opacity );\n ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n vec3 totalEmissiveRadiance = emissive;\n #include \n #include \n #include \n #include \n #include \n #include \n #include \n #ifdef DOUBLE_SIDED\n reflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;\n #else\n reflectedLight.indirectDiffuse += vIndirectFront;\n #endif\n #include \n reflectedLight.indirectDiffuse *= BRDF_Lambert( diffuseColor.rgb );\n #ifdef DOUBLE_SIDED\n reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n #else\n reflectedLight.directDiffuse = vLightFront;\n #endif\n reflectedLight.directDiffuse *= BRDF_Lambert( diffuseColor.rgb ) * getShadowMask();\n #include \n vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n #include \n #include \n #include