k-l-lambda commited on
Commit
2b7aae2
·
1 Parent(s): a523941

feat: add Python ML services (CPU mode) with model download

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +32 -4
  2. backend/libs/PyProcessor.ts +58 -0
  3. backend/libs/ZeroClient.ts +85 -0
  4. backend/libs/adminSolutionStore.ts +95 -0
  5. backend/libs/async-queue.ts +72 -0
  6. backend/libs/beadPicker.ts +22 -0
  7. backend/libs/browserComponents.ts +2 -0
  8. backend/libs/gauge-renderer-three.ts +161 -0
  9. backend/libs/predictJianpuPages.ts +443 -0
  10. backend/libs/predictPages.ts +887 -0
  11. backend/libs/predictors.ts +139 -0
  12. backend/libs/regulation.ts +350 -0
  13. backend/libs/regulationBead.ts +372 -0
  14. backend/libs/store.ts +42 -0
  15. backend/libs/three/Three.Legacy.d.ts +1 -0
  16. backend/libs/three/Three.Legacy.js +1444 -0
  17. backend/libs/three/Three.d.ts +234 -0
  18. backend/libs/three/Three.js +172 -0
  19. backend/libs/three/animation/AnimationAction.d.ts +86 -0
  20. backend/libs/three/animation/AnimationAction.js +553 -0
  21. backend/libs/three/animation/AnimationClip.d.ts +42 -0
  22. backend/libs/three/animation/AnimationClip.js +350 -0
  23. backend/libs/three/animation/AnimationMixer.d.ts +30 -0
  24. backend/libs/three/animation/AnimationMixer.js +590 -0
  25. backend/libs/three/animation/AnimationObjectGroup.d.ts +17 -0
  26. backend/libs/three/animation/AnimationObjectGroup.js +327 -0
  27. backend/libs/three/animation/AnimationUtils.d.ts +27 -0
  28. backend/libs/three/animation/AnimationUtils.js +267 -0
  29. backend/libs/three/animation/KeyframeTrack.d.ts +45 -0
  30. backend/libs/three/animation/KeyframeTrack.js +337 -0
  31. backend/libs/three/animation/PropertyBinding.d.ts +43 -0
  32. backend/libs/three/animation/PropertyBinding.js +534 -0
  33. backend/libs/three/animation/PropertyMixer.d.ts +17 -0
  34. backend/libs/three/animation/PropertyMixer.js +248 -0
  35. backend/libs/three/animation/tracks/BooleanKeyframeTrack.d.ts +10 -0
  36. backend/libs/three/animation/tracks/BooleanKeyframeTrack.js +19 -0
  37. backend/libs/three/animation/tracks/ColorKeyframeTrack.d.ts +11 -0
  38. backend/libs/three/animation/tracks/ColorKeyframeTrack.js +15 -0
  39. backend/libs/three/animation/tracks/NumberKeyframeTrack.d.ts +11 -0
  40. backend/libs/three/animation/tracks/NumberKeyframeTrack.js +12 -0
  41. backend/libs/three/animation/tracks/QuaternionKeyframeTrack.d.ts +11 -0
  42. backend/libs/three/animation/tracks/QuaternionKeyframeTrack.js +19 -0
  43. backend/libs/three/animation/tracks/StringKeyframeTrack.d.ts +11 -0
  44. backend/libs/three/animation/tracks/StringKeyframeTrack.js +15 -0
  45. backend/libs/three/animation/tracks/VectorKeyframeTrack.d.ts +11 -0
  46. backend/libs/three/animation/tracks/VectorKeyframeTrack.js +12 -0
  47. backend/libs/three/audio/Audio.d.ts +106 -0
  48. backend/libs/three/audio/Audio.js +297 -0
  49. backend/libs/three/audio/AudioAnalyser.d.ts +20 -0
  50. backend/libs/three/audio/AudioAnalyser.js +29 -0
Dockerfile CHANGED
@@ -1,7 +1,8 @@
1
  ###############################################################################
2
- # STARRY HuggingFace Space — Lightweight Deployment
3
  # nginx (reverse proxy) + omr-service (Fastify) + cluster-server (NestJS)
4
- # No ML predictors prediction features are disabled
 
5
  ###############################################################################
6
 
7
  FROM node:20-slim
@@ -13,13 +14,33 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
13
  postgresql postgresql-client \
14
  nginx \
15
  tini \
16
- python3 make g++ pkg-config \
17
  libzmq3-dev libfontconfig1 curl \
 
18
  && rm -rf /var/lib/apt/lists/*
19
 
20
  # Install tsx globally
21
  RUN npm install -g tsx
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  # --- node user already has UID 1000 in node:20-slim ---
24
  # Ensure home directory exists
25
  RUN mkdir -p /home/node && chown node:node /home/node
@@ -88,13 +109,20 @@ RUN ln -sf /home/node/app/backend/omr-service/node_modules /home/node/app/backen
88
  # --- Root tsconfig ---
89
  COPY --chown=node tsconfig.json ./
90
 
 
 
 
 
 
 
91
  # --- Config files ---
92
  COPY --chown=node docker-entrypoint.sh ./docker-entrypoint.sh
93
  COPY --chown=node nginx.conf /etc/nginx/nginx.conf
94
  RUN chmod +x docker-entrypoint.sh
95
 
96
  # Directories
97
- RUN mkdir -p /tmp/starry-uploads && chown node:node /tmp/starry-uploads
 
98
 
99
  USER node
100
 
 
1
  ###############################################################################
2
+ # STARRY HuggingFace Space — Full Deployment with ML Predictors (CPU mode)
3
  # nginx (reverse proxy) + omr-service (Fastify) + cluster-server (NestJS)
4
+ # + 7 Python ML predictors via supervisord (layout, gauge, mask, semantic,
5
+ # loc, ocr, brackets)
6
  ###############################################################################
7
 
8
  FROM node:20-slim
 
14
  postgresql postgresql-client \
15
  nginx \
16
  tini \
17
+ python3 python3-pip make g++ pkg-config \
18
  libzmq3-dev libfontconfig1 curl \
19
+ supervisor \
20
  && rm -rf /var/lib/apt/lists/*
21
 
22
  # Install tsx globally
23
  RUN npm install -g tsx
24
 
25
+ # --- Python ML deps (CPU-only PyTorch + TensorFlow) ---
26
+ RUN pip install --no-cache-dir --break-system-packages \
27
+ "numpy>=1.26.0,<2.0.0" \
28
+ torch torchvision \
29
+ --index-url https://download.pytorch.org/whl/cpu
30
+
31
+ RUN pip install --no-cache-dir --break-system-packages \
32
+ "numpy>=1.26.0,<2.0.0" \
33
+ tensorflow-cpu \
34
+ tf_keras \
35
+ "opencv-python-headless<4.11" \
36
+ Pillow>=8.0.0 \
37
+ PyYAML>=5.4.0 \
38
+ pyzmq>=22.0.0 \
39
+ msgpack>=1.0.0
40
+
41
+ ENV TF_USE_LEGACY_KERAS=1
42
+ ENV PYTHONUNBUFFERED=1
43
+
44
  # --- node user already has UID 1000 in node:20-slim ---
45
  # Ensure home directory exists
46
  RUN mkdir -p /home/node && chown node:node /home/node
 
109
  # --- Root tsconfig ---
110
  COPY --chown=node tsconfig.json ./
111
 
112
+ # --- Python ML services ---
113
+ COPY --chown=node backend/python-services/ ./backend/python-services/
114
+
115
+ # --- Supervisord config ---
116
+ COPY --chown=node supervisord.conf ./supervisord.conf
117
+
118
  # --- Config files ---
119
  COPY --chown=node docker-entrypoint.sh ./docker-entrypoint.sh
120
  COPY --chown=node nginx.conf /etc/nginx/nginx.conf
121
  RUN chmod +x docker-entrypoint.sh
122
 
123
  # Directories
124
+ RUN mkdir -p /tmp/starry-uploads /home/node/log/supervisor /home/node/run /home/node/app/models \
125
+ && chown -R node:node /tmp/starry-uploads /home/node/log /home/node/run /home/node/app/models
126
 
127
  USER node
128
 
backend/libs/PyProcessor.ts ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getPortPromise } from 'portfinder';
2
+ import { Options, PythonShell } from 'python-shell';
3
+ import { defaultsDeep } from 'lodash';
4
+ import ZeroClient, { Logger } from './ZeroClient';
5
+
6
+ export default class PyProcessor extends ZeroClient {
7
+ private readonly scriptPath: string;
8
+ private readonly options: Options;
9
+ private pyShell: PythonShell;
10
+
11
+ private retryCount: number = 0;
12
+ private retryDelay: number = 3000;
13
+
14
+ constructor(scriptPath: string, options: Options = {}, logger: Logger = console) {
15
+ super(logger);
16
+ this.scriptPath = scriptPath;
17
+ this.options = options;
18
+ }
19
+
20
+ async bind(port?: string | number) {
21
+ const freePort =
22
+ port ||
23
+ (await getPortPromise({
24
+ port: 12022,
25
+ stopPort: 12122,
26
+ }));
27
+
28
+ // "./streamPredictor.py", "--inspect"
29
+ const options = defaultsDeep(
30
+ {
31
+ args: [...(this.options.args || []), '-p', `${freePort}`],
32
+ },
33
+ this.options
34
+ );
35
+
36
+ this.logger.info(`[python-shell]: starting python shell. path: ${this.scriptPath}`);
37
+
38
+ this.pyShell = new PythonShell(this.scriptPath, options);
39
+
40
+ this.pyShell.stdout.on('data', (data) => this.logger.info(data));
41
+
42
+ this.pyShell.on('pythonError', (err) => this.logger.error(`[python-shell]: ${this.scriptPath} pythonError:`, err));
43
+ this.pyShell.on('stderr', (err) => this.logger.error(`[python-shell]: ${this.scriptPath} stderr:`, err));
44
+ this.pyShell.on('error', (err) => this.logger.error(`[python-shell]: ${this.scriptPath} error:`, err));
45
+ this.pyShell.on('close', () => {
46
+ // python子进程关闭事件
47
+ if (this.retryCount < 5) {
48
+ this.retryCount++;
49
+ this.logger.info(`[python-shell]: ${this.scriptPath} will retry ${this.retryCount}th time after 3 seconds`);
50
+ setTimeout(() => {
51
+ this.bind();
52
+ }, this.retryDelay);
53
+ }
54
+ });
55
+
56
+ super.bind(`tcp://127.0.0.1:${freePort}`);
57
+ }
58
+ }
backend/libs/ZeroClient.ts ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { pack, unpack } from 'msgpackr';
2
+ import { Request } from 'zeromq';
3
+ import { AsyncQueue } from './async-queue';
4
+
5
+ interface Response {
6
+ code: number;
7
+ msg: string;
8
+ data?: any;
9
+ }
10
+
11
+ export interface Logger {
12
+ info: (...data: any[]) => void;
13
+ error: (...data: any[]) => void;
14
+ }
15
+
16
+ type PyArgs = any[];
17
+ type PyKwargs = Record<string, any>;
18
+
19
+ export default class ZeroClient {
20
+ logger: Logger;
21
+ private socket: Request;
22
+ private queue: AsyncQueue = new AsyncQueue();
23
+
24
+ private url: string;
25
+
26
+ constructor(logger: Logger = console) {
27
+ this.logger = logger;
28
+ }
29
+
30
+ bind(url?: string) {
31
+ url && (this.url = url);
32
+ this.socket = new Request({
33
+ sendTimeout: 15e3,
34
+ receiveTimeout: 300e3,
35
+ });
36
+
37
+ this.socket.connect(this.url);
38
+ }
39
+
40
+ private __request(payload) {
41
+ let retryTimes = 0;
42
+
43
+ const req = async (data) => {
44
+ try {
45
+ if (this.socket.closed) this.bind();
46
+ return await this.socket.send(pack(data)).then(() => this.socket.receive());
47
+ } catch (err) {
48
+ if (retryTimes < 2) {
49
+ retryTimes++;
50
+ console.log(`请求失败,${err.stack}`);
51
+ console.error(`3s后重试第${retryTimes}次`);
52
+ this.socket.close();
53
+ await new Promise((resolve) => setTimeout(resolve, 3000));
54
+ return req(data);
55
+ } else {
56
+ throw err;
57
+ }
58
+ }
59
+ };
60
+
61
+ return req(payload);
62
+ }
63
+
64
+ async request(method: string, args: PyArgs | PyKwargs = null, kwargs: PyKwargs = null): Promise<any> {
65
+ const [args_, kwargs_] = Array.isArray(args) ? [args, kwargs] : [undefined, args];
66
+ const msg: any = { method };
67
+ if (args_) msg.args = args_;
68
+ if (kwargs_) msg.kwargs = kwargs_;
69
+
70
+ return this.queue.addTask([
71
+ async (opt) => {
72
+ const [result] = await this.__request(opt);
73
+
74
+ const obj = unpack(result) as Response;
75
+
76
+ if (obj.code === 0) {
77
+ return obj.data;
78
+ } else {
79
+ return Promise.reject(obj.msg);
80
+ }
81
+ },
82
+ msg,
83
+ ]);
84
+ }
85
+ }
backend/libs/adminSolutionStore.ts ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { SolutionStore } from './store';
2
+ import { RegulationSolution } from '../../src/starry';
3
+ import getSign from '../../src/utils/request/createSign';
4
+ import * as asyncAtom from '../../src/utils/asyncAtom';
5
+
6
+ const getHeaders = (method: string = 'get', data: any = {}): Record<string, string> => {
7
+ const headers = {
8
+ stmp: Date.now(),
9
+ sess: '654a591de5d3a4b0',
10
+ ver: 512,
11
+ orn: '(X11;$Linux x86_64$android$1.0.0$torchTrain',
12
+ sign: '',
13
+ size: 0,
14
+ dst: '',
15
+ type: 1,
16
+ lang: 'zh_CN',
17
+ seq: 0,
18
+ code: 0,
19
+ ext: 'CN',
20
+ desc: '',
21
+ } as any;
22
+ headers.sign = getSign({ method, data, requestHeader: headers });
23
+
24
+ return headers;
25
+ };
26
+
27
+ export default class AdminSolutionStore implements SolutionStore {
28
+ env: string;
29
+ host: string;
30
+ fetch: typeof fetch;
31
+
32
+ constructor(options: { env: string; host: string; fetch: typeof fetch }) {
33
+ Object.assign(this, options);
34
+ }
35
+
36
+ post(path, { data }: any): Promise<any> {
37
+ const headers = getHeaders('post', data);
38
+ headers['content-type'] = 'application/json;charset=UTF-8';
39
+
40
+ return this.fetch(this.host + path, {
41
+ method: 'post',
42
+ headers,
43
+ body: JSON.stringify(data),
44
+ }).then((res) => res.json());
45
+ }
46
+
47
+ async batchGet(keys: string[]): Promise<RegulationSolution[]> {
48
+ const solutions = (
49
+ await this.post('/torch/musicSet/manage/cache/batchGet', {
50
+ data: {
51
+ env: this.env,
52
+ nameList: keys,
53
+ },
54
+ })
55
+ ).data as RegulationSolution[];
56
+ //console.log('AdminSolutionStore.batchGet:', solutions);
57
+
58
+ return Promise.all(solutions);
59
+ }
60
+
61
+ async get(key: string): Promise<RegulationSolution> {
62
+ const solution = (await this.post('/torch/musicSet/manage/cache/batchGet', {
63
+ data: {
64
+ env: this.env,
65
+ nameList: [key],
66
+ },
67
+ }).then((res) => res.data[0])) as RegulationSolution;
68
+ //console.log('AdminSolutionStore.get:', key, solution);
69
+
70
+ return solution;
71
+ }
72
+
73
+ async set(key: string, value: RegulationSolution) {
74
+ const request = () =>
75
+ this.post('/torch/musicSet/manage/cache/set', {
76
+ data: {
77
+ env: this.env,
78
+ name: key,
79
+ value: value,
80
+ },
81
+ }).catch((err) => console.warn('failed to set solution:', key, err));
82
+ asyncAtom.queue(this, request);
83
+
84
+ //console.log('AdminSolutionStore.set:', key, res.ok);
85
+ }
86
+
87
+ /*remove(key: string) {
88
+ return request.post('/torch/musicSet/manage/cache/delete', {
89
+ data: {
90
+ env: this.env,
91
+ name: key,
92
+ },
93
+ });
94
+ }*/
95
+ }
backend/libs/async-queue.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { EventEmitter } from 'events';
2
+
3
+ interface DSPromiseOption {
4
+ timeout?: number;
5
+ }
6
+
7
+ export function destructPromise<T>(
8
+ options: DSPromiseOption = {}
9
+ ): [promise: Promise<T>, resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void] {
10
+ const { timeout } = options;
11
+ let rs: (value: T | PromiseLike<T>) => void;
12
+ let rj: (reason: any) => void;
13
+
14
+ return [
15
+ new Promise((resolve, reject) => {
16
+ rs = resolve;
17
+ rj = reject;
18
+
19
+ if (timeout >= 0) setTimeout(rj, timeout, 'timeout');
20
+ }),
21
+ rs,
22
+ rj,
23
+ ];
24
+ }
25
+
26
+ type AsyncTask = [fn: (data: any) => Promise<any>, payload: any, resolve: (data: any) => void, reject: (reason: any) => void];
27
+
28
+ export class AsyncQueue extends EventEmitter {
29
+ private working = false;
30
+
31
+ tasks: AsyncTask[];
32
+
33
+ constructor() {
34
+ super();
35
+ this.working = false;
36
+ this.tasks = [];
37
+ process.nextTick(() => {
38
+ this.emit('idle');
39
+ });
40
+ }
41
+
42
+ private async _digest(item: AsyncTask) {
43
+ this.working = true;
44
+
45
+ const [taskFn, payload, resolve, reject] = item;
46
+ await taskFn(payload).then(resolve, reject);
47
+
48
+ if (this.tasks.length > 0) {
49
+ await this._digest(this.tasks.shift());
50
+ } else {
51
+ this.working = false;
52
+ this.emit('idle');
53
+ }
54
+ }
55
+
56
+ /**
57
+ * 添加队列任务
58
+ * @param task
59
+ * @param options
60
+ */
61
+ addTask(task: [AsyncTask[0], AsyncTask[1]], { timeout = 600000 }: { timeout?: number } = {}): Promise<any> {
62
+ const [promise, resolve, reject] = destructPromise({ timeout });
63
+
64
+ if (this.working) {
65
+ this.tasks.push([...task, resolve, reject]);
66
+ } else {
67
+ this._digest([...task, resolve, reject]);
68
+ }
69
+
70
+ return promise;
71
+ }
72
+ }
backend/libs/beadPicker.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as starry from '../../src/starry';
2
+ import OnnxBeadPicker from '../../src/utils/onnxBeadPicker';
3
+
4
+ const loadBeadPickers = (path: string, seqs: number[] = [32, 64, 128, 512]): Promise<starry.BeadPicker[]> => {
5
+ return Promise.all(
6
+ seqs.map(async (n_seq) => {
7
+ let loading: any;
8
+
9
+ const url = path.replace(/seq\d+/, `seq${n_seq}`);
10
+ const picker = new OnnxBeadPicker(url, {
11
+ n_seq,
12
+ usePivotX: true,
13
+ onLoad: (p) => (loading = p),
14
+ });
15
+ await loading;
16
+
17
+ return picker;
18
+ })
19
+ );
20
+ };
21
+
22
+ export { loadBeadPickers };
backend/libs/browserComponents.ts ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ globalThis.btoa = (str) => Buffer.from(str, 'binary').toString('base64');
2
+ globalThis.atob = (str) => Buffer.from(str, 'base64').toString('binary');
backend/libs/gauge-renderer-three.ts ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Canvas, loadImage } from 'skia-canvas';
2
+ import * as THREE from './three/Three';
3
+ import gl from 'gl';
4
+
5
+ // threejs内部使用了OffscreenCanvas
6
+ (globalThis as any).OffscreenCanvas = (globalThis as any).OffscreenCanvas || Canvas;
7
+
8
+ const cc = <T>(a: T[][]): T[] => {
9
+ const result: T[] = [];
10
+ for (const x of a) {
11
+ for (const e of x) result.push(e);
12
+ }
13
+
14
+ return result;
15
+ };
16
+
17
+ class GLCanvas {
18
+ ctx: ReturnType<typeof gl>;
19
+ width: number = 256;
20
+ height: number = 192;
21
+
22
+ resizeBuffer: number[];
23
+
24
+ constructor(width: number, height: number) {
25
+ this.width = width;
26
+ this.height = height;
27
+ }
28
+
29
+ getContext(type, options) {
30
+ if (type === 'webgl') {
31
+ this.ctx = this.ctx || gl(this.width, this.height, options);
32
+
33
+ return this.ctx;
34
+ }
35
+
36
+ return null as WebGLRenderingContext;
37
+ }
38
+
39
+ addEventListener(evt: 'webglcontextlost') {}
40
+
41
+ removeEventListener(evt: any) {}
42
+
43
+ async toBuffer() {
44
+ const pixels = new Uint8Array(this.width * this.height * 4);
45
+ this.ctx.readPixels(0, 0, this.width, this.height, this.ctx.RGBA, this.ctx.UNSIGNED_BYTE, pixels);
46
+
47
+ const canvas = new Canvas(this.width, this.height);
48
+ const ctx = canvas.getContext('2d');
49
+ ctx.putImageData({ data: new Uint8ClampedArray(pixels), width: this.width, height: this.height }, 0, 0);
50
+
51
+ return canvas.toBuffer('png');
52
+ }
53
+ }
54
+
55
+ export const renderGaugeImage = async (sourceURL: string | Buffer, gaugeURL: string | Buffer, baseY: number) => {
56
+ const source = await loadImage(sourceURL);
57
+ const gauge = await loadImage(gaugeURL);
58
+ const { width: gaugeWidth, height: gaugeHeight } = gauge;
59
+ const { width: srcWidth, height: srcHeight } = source;
60
+
61
+ let width: number = 256;
62
+ let height: number = 192;
63
+
64
+ if (width !== srcWidth || height !== srcHeight) {
65
+ if (Number.isFinite(gaugeWidth)) {
66
+ width = gaugeWidth;
67
+ } else {
68
+ width = Math.round((height * srcWidth) / srcHeight);
69
+ }
70
+ }
71
+
72
+ const camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0.1, 10);
73
+ camera.position.set(0, 0, 1);
74
+ camera.up.set(0, 1, 0);
75
+ camera.lookAt(0, 0, 0);
76
+
77
+ const ctx = new Canvas(gaugeWidth, gaugeHeight).getContext('2d');
78
+ ctx.drawImage(gauge, 0, 0);
79
+ const { data: buff } = ctx.getImageData(0, 0, gaugeWidth, gaugeHeight);
80
+
81
+ const xFactor = width / gaugeWidth;
82
+
83
+ baseY = Math.round(Number.isFinite(baseY) ? baseY : gaugeHeight / 2);
84
+ baseY = Math.max(0, Math.min(gaugeHeight - 1, baseY));
85
+
86
+ const propertyArray = Array(gaugeHeight)
87
+ .fill(null)
88
+ .map((_, y) =>
89
+ Array(gaugeWidth)
90
+ .fill(null)
91
+ .map((_, x) => ({
92
+ uv: [(x + 0.5) / gaugeWidth, 1 - (y + 0.5) / gaugeHeight],
93
+ position: [(x - gaugeWidth / 2) * xFactor, (buff[(y * gaugeWidth + x) * 4] + buff[(y * gaugeWidth + x) * 4 + 2] / 256 - 128) / xFactor, 0],
94
+ }))
95
+ );
96
+
97
+ // integral X by K
98
+ for (let y = baseY; y > 0; --y) {
99
+ for (let x = 0; x < gaugeWidth; ++x)
100
+ propertyArray[y - 1][x].position[0] = propertyArray[y][x].position[0] - ((buff[(y * gaugeWidth + x) * 4 + 1] - 128) * xFactor) / 127;
101
+ }
102
+ for (let y = baseY + 1; y < gaugeHeight; ++y) {
103
+ for (let x = 0; x < gaugeWidth; ++x)
104
+ propertyArray[y][x].position[0] = propertyArray[y - 1][x].position[0] + ((buff[((y - 1) * gaugeWidth + x) * 4 + 1] - 128) * xFactor) / 127;
105
+ }
106
+
107
+ const uvs = cc(cc(propertyArray).map((p) => p.uv));
108
+ const positions = cc(cc(propertyArray).map((p) => p.position));
109
+
110
+ const geometry = new THREE.BufferGeometry();
111
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
112
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
113
+
114
+ const faces = Array(gaugeHeight - 1)
115
+ .fill(null)
116
+ .map((_, y) =>
117
+ Array(gaugeWidth - 1)
118
+ .fill(null)
119
+ .map((_, x) => [
120
+ y * gaugeWidth + x,
121
+ y * gaugeWidth + x + 1,
122
+ (y + 1) * gaugeWidth + x,
123
+ (y + 1) * gaugeWidth + x,
124
+ (y + 1) * gaugeWidth + x + 1,
125
+ y * gaugeWidth + x + 1,
126
+ ])
127
+ );
128
+ const indices = cc(cc(faces));
129
+ geometry.setIndex(indices);
130
+
131
+ const material = new THREE.MeshBasicMaterial({
132
+ side: THREE.DoubleSide,
133
+ map: new THREE.Texture(source),
134
+ });
135
+ material.map.needsUpdate = true;
136
+ material.needsUpdate = true;
137
+
138
+ const scene = new THREE.Scene();
139
+ const plane = new THREE.Mesh(geometry, material);
140
+ scene.add(plane);
141
+
142
+ const glCanvas = new GLCanvas(width, height);
143
+
144
+ const renderer = new THREE.WebGLRenderer({
145
+ antialias: true,
146
+ canvas: glCanvas as any,
147
+ alpha: false,
148
+ });
149
+
150
+ renderer.render(scene, camera);
151
+
152
+ renderer.dispose();
153
+
154
+ return {
155
+ buffer: await glCanvas.toBuffer(),
156
+ size: {
157
+ width,
158
+ height,
159
+ },
160
+ };
161
+ };
backend/libs/predictJianpuPages.ts ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Canvas, Image, loadImage } from 'skia-canvas';
2
+ import sha1 from 'sha1';
3
+ import { WeakLRUCache } from 'weak-lru-cache';
4
+ import * as starry from '../../src/starry';
5
+ import { LayoutResult, PyClients } from './predictors';
6
+ import { constructSystem, convertImage } from './util';
7
+ import { SemanticGraph } from '../../src/starry';
8
+
9
+ globalThis.OffscreenCanvas = (globalThis as any).OffscreenCanvas || Canvas;
10
+ globalThis.Image = globalThis.Image || Image;
11
+ globalThis.btoa = globalThis.btoa || ((str: string) => Buffer.from(str, 'binary').toString('base64'));
12
+
13
+ const STAFF_PADDING_LEFT = 32;
14
+
15
+ const MAX_PAGE_WIDTH = 1200;
16
+
17
+ const GAUGE_VISION_SPEC = {
18
+ viewportHeight: 256,
19
+ viewportUnit: 8,
20
+ };
21
+
22
+ const MASK_VISION_SPEC = {
23
+ viewportHeight: 192,
24
+ viewportUnit: 8,
25
+ };
26
+
27
+ const SEMANTIC_VISION_SPEC = {
28
+ viewportHeight: 192,
29
+ viewportUnit: 8,
30
+ };
31
+
32
+ interface OMRStat {
33
+ cost: number; // in milliseconds
34
+ pagesCost: number; // in milliseconds
35
+ pages: number;
36
+ }
37
+
38
+ interface OMRSummary {
39
+ costTotal: number; // in milliseconds
40
+ costPerPage: number; // in milliseconds
41
+ pagesTotal: number;
42
+ scoreN: number;
43
+ }
44
+
45
+ /**
46
+ * 为布局识别的图片标准化处理
47
+ * @param image
48
+ * @param width
49
+ */
50
+ function scaleForLayout(image: Image, width: number): Canvas {
51
+ let height = (image.height / image.width) * width;
52
+
53
+ const canvas = new Canvas(width, height);
54
+ const ctx = canvas.getContext('2d');
55
+
56
+ ctx.drawImage(image, 0, 0, width, (width * image.height) / image.width);
57
+
58
+ return canvas;
59
+ }
60
+
61
+ /**
62
+ * 根据所有图像的检测结果设置合适的全局页面尺寸
63
+ * @param score
64
+ * @param detections
65
+ * @param outputWidth
66
+ */
67
+ function setGlobalPageSize(score: starry.Score, detections: LayoutResult[], outputWidth: number) {
68
+ const sizeRatios = detections
69
+ .filter((s) => s && s.detection)
70
+ .map((v, k) => {
71
+ const staffInterval = Math.min(...v.detection.areas.filter((area) => area.staves?.middleRhos?.length).map((x) => x.staves.interval));
72
+
73
+ const sourceSize = v.sourceSize;
74
+ return {
75
+ ...v,
76
+ index: k,
77
+ vw: sourceSize.width / staffInterval, // 页面宽度(逻辑单位)
78
+ hwr: sourceSize.height / sourceSize.width, // 页面高宽比
79
+ };
80
+ });
81
+
82
+ if (!sizeRatios.length) {
83
+ throw new Error('empty result');
84
+ }
85
+
86
+ const maxVW = sizeRatios.sort((a, b) => b.vw - a.vw)[0];
87
+ const maxAspect = Math.max(...sizeRatios.map((r) => r.hwr));
88
+
89
+ score.unitSize = outputWidth / maxVW.vw;
90
+
91
+ // 页面显示尺寸
92
+ score.pageSize = {
93
+ width: outputWidth,
94
+ height: outputWidth * maxAspect,
95
+ };
96
+ }
97
+
98
+ const batchTask = (fn: () => Promise<any>) => fn();
99
+ const concurrencyTask = (fns: (() => Promise<any>)[]) => Promise.all(fns.map((fn) => fn()));
100
+
101
+ const shootStaffImage = async (
102
+ system: starry.System,
103
+ staffIndex: number,
104
+ { paddingLeft = 0, scaling = 1, spec }: { paddingLeft?: number; scaling?: number; spec: { viewportHeight: number; viewportUnit: number } }
105
+ ): Promise<Canvas> => {
106
+ if (!system || !system.backgroundImage) return null;
107
+
108
+ const staff = system.staves[staffIndex];
109
+ if (!staff) return null;
110
+
111
+ const middleUnits = spec.viewportHeight / spec.viewportUnit / 2;
112
+
113
+ const width = system.imagePosition.width * spec.viewportUnit;
114
+ const height = system.imagePosition.height * spec.viewportUnit;
115
+ const x = system.imagePosition.x * spec.viewportUnit + paddingLeft;
116
+ const y = (system.imagePosition.y - (staff.top + staff.staffY - middleUnits)) * spec.viewportUnit;
117
+
118
+ const canvas = new Canvas(Math.round(width + x) * scaling, spec.viewportHeight * scaling);
119
+ const context = canvas.getContext('2d');
120
+ context.fillStyle = 'white';
121
+ context.fillRect(0, 0, canvas.width, canvas.height);
122
+ context.drawImage(await loadImage(system.backgroundImage), x * scaling, y * scaling, width * scaling, height * scaling);
123
+
124
+ return canvas;
125
+ // .substr(22); // remove the prefix of 'data:image/png;base64,'
126
+ };
127
+
128
+ /**
129
+ * 根据布局检测结果进行截图
130
+ * @param score
131
+ * @param pageCanvas
132
+ * @param page
133
+ * @param detection
134
+ */
135
+ async function shootImageByDetection({
136
+ page,
137
+ score,
138
+ pageCanvas,
139
+ }: {
140
+ score: starry.Score;
141
+ page: starry.Page;
142
+ pageCanvas: Canvas; // 原始图片绘制好的canvas
143
+ }) {
144
+ if (!page?.layout?.areas?.length) {
145
+ return null;
146
+ }
147
+
148
+ page.width = score.pageSize.width / score.unitSize;
149
+ page.height = score.pageSize.height / score.unitSize;
150
+
151
+ const correctCanvas = new Canvas(pageCanvas.width, pageCanvas.height);
152
+ const ctx = correctCanvas.getContext('2d');
153
+
154
+ ctx.save();
155
+
156
+ const { width, height } = correctCanvas;
157
+ const [a, b, c, d] = page.source.matrix;
158
+
159
+ ctx.setTransform(a, b, c, d, (-1 / 2) * width + (1 / 2) * a * width + (1 / 2) * b * height, (-1 / 2) * height + (1 / 2) * c * width + (1 / 2) * d * height);
160
+
161
+ ctx.drawImage(pageCanvas, 0, 0);
162
+
163
+ ctx.restore();
164
+
165
+ const interval = page.source.interval;
166
+
167
+ page.layout.areas.map((area, systemIndex) => {
168
+ console.assert(area.staves?.middleRhos?.length, '[shootImageByDetection] empty area:', area);
169
+
170
+ const data = ctx.getImageData(area.x, area.y, area.width, area.height);
171
+
172
+ const canvas = new Canvas(area.width, area.height);
173
+
174
+ const context = canvas.getContext('2d');
175
+ // context.rotate(-area.staves.theta);
176
+ context.putImageData(data, 0, 0);
177
+
178
+ const detection = area.staves;
179
+ const size = { width: area.width, height: area.height };
180
+
181
+ const sourceCenter = {
182
+ x: pageCanvas.width / 2 / interval,
183
+ y: pageCanvas.height / 2 / interval,
184
+ };
185
+
186
+ const position = {
187
+ x: (area.x + area.staves.phi1) / interval - sourceCenter.x + page.width / 2,
188
+ y: area.y / interval - sourceCenter.y + page.height / 2,
189
+ };
190
+
191
+ page.systems[systemIndex] = constructSystem({
192
+ page,
193
+ backgroundImage: canvas.toBufferSync('png'),
194
+ detection,
195
+ imageSize: size,
196
+ position,
197
+ });
198
+ });
199
+
200
+ return correctCanvas;
201
+ }
202
+
203
+ async function shootStaffBackgroundImage({ system, staff, staffIndex }: { system: starry.System; staff: starry.Staff; staffIndex: number }) {
204
+ const sourceCanvas = await shootStaffImage(system, staffIndex, {
205
+ paddingLeft: STAFF_PADDING_LEFT,
206
+ spec: SEMANTIC_VISION_SPEC,
207
+ });
208
+
209
+ staff.backgroundImage = sourceCanvas.toBufferSync('png');
210
+
211
+ staff.imagePosition = {
212
+ x: -STAFF_PADDING_LEFT / SEMANTIC_VISION_SPEC.viewportUnit,
213
+ y: staff.staffY - SEMANTIC_VISION_SPEC.viewportHeight / 2 / SEMANTIC_VISION_SPEC.viewportUnit,
214
+ width: sourceCanvas.width / SEMANTIC_VISION_SPEC.viewportUnit,
215
+ height: sourceCanvas.height / SEMANTIC_VISION_SPEC.viewportUnit,
216
+ };
217
+ }
218
+
219
+ /**
220
+ * 单个staff的变形矫正
221
+ * @param system
222
+ * @param staff
223
+ * @param staffIndex
224
+ * @param gaugeImage
225
+ * @param pyClients
226
+ */
227
+ async function gaugeStaff({
228
+ system,
229
+ staff,
230
+ staffIndex,
231
+ gaugeImage,
232
+ pyClients,
233
+ }: {
234
+ system: starry.System;
235
+ staff: starry.Staff;
236
+ staffIndex: number;
237
+ gaugeImage: Buffer;
238
+ pyClients: PyClients;
239
+ }) {
240
+ const sourceCanvas = await shootStaffImage(system, staffIndex, {
241
+ paddingLeft: STAFF_PADDING_LEFT,
242
+ spec: GAUGE_VISION_SPEC,
243
+ scaling: 2,
244
+ });
245
+
246
+ const sourceBuffer = sourceCanvas.toBufferSync('png');
247
+
248
+ const baseY = (system.middleY - (staff.top + staff.staffY)) * GAUGE_VISION_SPEC.viewportUnit + GAUGE_VISION_SPEC.viewportHeight / 2;
249
+
250
+ const { buffer, size } = await pyClients.predictScoreImages('gaugeRenderer', [sourceBuffer, gaugeImage, baseY]);
251
+
252
+ staff.backgroundImage = buffer;
253
+
254
+ staff.imagePosition = {
255
+ x: -STAFF_PADDING_LEFT / GAUGE_VISION_SPEC.viewportUnit,
256
+ y: staff.staffY - GAUGE_VISION_SPEC.viewportHeight / 2 / GAUGE_VISION_SPEC.viewportUnit,
257
+ width: size.width / GAUGE_VISION_SPEC.viewportUnit,
258
+ height: size.height / GAUGE_VISION_SPEC.viewportUnit,
259
+ };
260
+
261
+ staff.maskImage = null;
262
+ }
263
+
264
+ /**
265
+ * 单个staff的降噪
266
+ * @param staff
267
+ * @param staffIndex
268
+ * @param maskImage
269
+ */
270
+ async function maskStaff({ staff, staffIndex, maskImage }: { staff: starry.Staff; staffIndex: number; maskImage: Buffer }) {
271
+ const img = await loadImage(maskImage);
272
+
273
+ staff.maskImage = maskImage;
274
+ staff.imagePosition = {
275
+ x: -STAFF_PADDING_LEFT / MASK_VISION_SPEC.viewportUnit,
276
+ y: staff.staffY - MASK_VISION_SPEC.viewportHeight / 2 / MASK_VISION_SPEC.viewportUnit,
277
+ width: img.width / MASK_VISION_SPEC.viewportUnit,
278
+ height: img.height / MASK_VISION_SPEC.viewportUnit,
279
+ };
280
+ }
281
+
282
+ /**
283
+ * 单个staff的语义识别
284
+ * @param score
285
+ * @param staffIndex
286
+ * @param system
287
+ * @param staff
288
+ * @param graph
289
+ */
290
+ async function semanticStaff({
291
+ score,
292
+ staffIndex,
293
+ system,
294
+ staff,
295
+ graph,
296
+ }: {
297
+ score: starry.Score;
298
+ staffIndex: number;
299
+ system: starry.System;
300
+ staff: starry.Staff;
301
+ graph: SemanticGraph;
302
+ }) {
303
+ graph.offset(-STAFF_PADDING_LEFT / SEMANTIC_VISION_SPEC.viewportUnit, 0);
304
+
305
+ system.assignSemantics(staffIndex, graph);
306
+
307
+ staff.assignSemantics(graph);
308
+ staff.clearPredictedTokens();
309
+
310
+ score.assembleSystem(system, score.settings?.semanticConfidenceThreshold || 1);
311
+ }
312
+
313
+ function replacePageImages(page: starry.Page, onReplaceImageKey: (src: string) => any) {
314
+ const tasks = [
315
+ [page.source, 'url'],
316
+ ...page.systems
317
+ .map((system) => {
318
+ return [
319
+ [system, 'backgroundImage'],
320
+ ...system.staves
321
+ .map((staff) => [
322
+ [staff, 'backgroundImage'],
323
+ [staff, 'maskImage'],
324
+ ])
325
+ .flat(),
326
+ ];
327
+ })
328
+ .flat(),
329
+ ];
330
+
331
+ tasks.map(([target, key]: [any, string]) => {
332
+ target[key] = onReplaceImageKey(target[key]);
333
+ });
334
+ }
335
+
336
+ export type TaskProgress = { total?: number; finished?: number };
337
+
338
+ export interface OMRPage {
339
+ url: string | Buffer;
340
+ key?: string;
341
+ layout?: LayoutResult;
342
+ renew?: boolean;
343
+ enableGauge?: boolean;
344
+ }
345
+
346
+ export interface ProgressState {
347
+ layout?: TaskProgress;
348
+ text?: TaskProgress;
349
+ gauge?: TaskProgress;
350
+ mask?: TaskProgress;
351
+ semantic?: TaskProgress;
352
+ regulate?: TaskProgress;
353
+ brackets?: TaskProgress;
354
+ }
355
+
356
+ class OMRProgress {
357
+ state: ProgressState = {};
358
+
359
+ onChange: (evt: ProgressState) => void;
360
+
361
+ constructor(onChange: (evt: ProgressState) => void) {
362
+ this.onChange = onChange;
363
+ }
364
+
365
+ setTotal(stage: keyof ProgressState, total: number) {
366
+ this.state[stage] = this.state[stage] || {
367
+ total,
368
+ finished: 0,
369
+ };
370
+ }
371
+
372
+ increase(stage: keyof ProgressState, step = 1) {
373
+ const info: TaskProgress = this.state[stage] || {
374
+ finished: 0,
375
+ };
376
+ info.finished += step;
377
+
378
+ this.onChange(this.state);
379
+ }
380
+ }
381
+
382
+ type SourceImage = string | Buffer;
383
+
384
+ export interface OMROption {
385
+ outputWidth?: number;
386
+ title?: string; // 曲谱标题
387
+ pageStore?: {
388
+ has?: (key: string) => Promise<Boolean>;
389
+ get: (key: string) => Promise<string>;
390
+ set: (key: string, val: string) => Promise<void>;
391
+ };
392
+ renew?: boolean;
393
+ processes?: (keyof ProgressState)[]; // 选择流程
394
+ onProgress?: (progress: ProgressState) => void;
395
+ onReplaceImage?: (src: SourceImage) => Promise<string>; // 替换所有图片地址,用于上传或者格式转换
396
+ }
397
+
398
+ const lruCache = new WeakLRUCache();
399
+
400
+ // 默认store
401
+ const pageStore = {
402
+ async get(key: string) {
403
+ return lruCache.getValue(key) as string;
404
+ },
405
+ async set(key: string, val: string) {
406
+ lruCache.setValue(key, val);
407
+ },
408
+ };
409
+
410
+ /**
411
+ * 默认将图片转换为webp格式的base64字符串
412
+ * @param src
413
+ */
414
+ const onReplaceImage = async (src: SourceImage) => {
415
+ if (src instanceof Buffer || (typeof src === 'string' && (/^https?:\/\//.test(src) || /^data:image\//.test(src)))) {
416
+ const webpBuffer = (await convertImage(src)).buffer;
417
+ return `data:image/webp;base64,${webpBuffer.toString('base64')}`;
418
+ }
419
+
420
+ return src;
421
+ };
422
+
423
+ /**
424
+ * 识别所有图片
425
+ * @param pyClients
426
+ * @param images
427
+ * @param option
428
+ */
429
+ export const predictPages = async (
430
+ pyClients: PyClients,
431
+ images: OMRPage[],
432
+ option: OMROption = { outputWidth: 1200, pageStore, onReplaceImage }
433
+ ): Promise<{ score: starry.Score; omitPages: number[]; stat: OMRStat }> => {
434
+ // return {
435
+ // score,
436
+ // omitPages,
437
+ // stat: {
438
+ // cost: t3 - t0,
439
+ // pagesCost: t2 - t1,
440
+ // pages: n_page,
441
+ // },
442
+ // };
443
+ };
backend/libs/predictPages.ts ADDED
@@ -0,0 +1,887 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sha1 from 'sha1';
2
+ import { Canvas, Image, loadImage } from 'skia-canvas';
3
+ import { WeakLRUCache } from 'weak-lru-cache';
4
+ import * as starry from '../../src/starry';
5
+ import { SemanticGraph } from '../../src/starry';
6
+ import { LayoutResult, PyClients } from './predictors';
7
+ import { constructSystem, convertImage } from './util';
8
+
9
+ globalThis.OffscreenCanvas = (globalThis as any).OffscreenCanvas || Canvas;
10
+ (globalThis as any).Image = (globalThis as any).Image || Image;
11
+ globalThis.btoa = globalThis.btoa || ((str: string) => Buffer.from(str, 'binary').toString('base64'));
12
+
13
+ const STAFF_PADDING_LEFT = 32;
14
+
15
+ const MAX_PAGE_WIDTH = 1200;
16
+
17
+ const GAUGE_VISION_SPEC = {
18
+ viewportHeight: 256,
19
+ viewportUnit: 8,
20
+ };
21
+
22
+ const MASK_VISION_SPEC = {
23
+ viewportHeight: 192,
24
+ viewportUnit: 8,
25
+ };
26
+
27
+ const SEMANTIC_VISION_SPEC = {
28
+ viewportHeight: 192,
29
+ viewportUnit: 8,
30
+ };
31
+
32
+ interface OMRStat {
33
+ cost: number; // in milliseconds
34
+ pagesCost: number; // in milliseconds
35
+ pages: number;
36
+ }
37
+
38
+ interface OMRSummary {
39
+ costTotal: number; // in milliseconds
40
+ costPerPage: number; // in milliseconds
41
+ pagesTotal: number;
42
+ scoreN: number;
43
+ }
44
+
45
+ /**
46
+ * 为布局识别的图片标准化处理
47
+ * @param image
48
+ * @param width
49
+ */
50
+ function scaleForLayout(image: Image, width: number): Canvas {
51
+ let height = (image.height / image.width) * width;
52
+
53
+ const canvas = new Canvas(width, height);
54
+ const ctx = canvas.getContext('2d');
55
+
56
+ ctx.drawImage(image, 0, 0, width, (width * image.height) / image.width);
57
+
58
+ return canvas;
59
+ }
60
+
61
+ /**
62
+ * 根据所有图像的检测结果设置合适的全局页面尺寸
63
+ * @param score
64
+ * @param detections
65
+ * @param outputWidth
66
+ */
67
+ function setGlobalPageSize(score: starry.Score, detections: LayoutResult[], outputWidth: number) {
68
+ const sizeRatios = detections
69
+ .filter((s) => s && s.detection && s.detection.areas?.length)
70
+ .map((v, k) => {
71
+ const staffInterval = Math.min(...v.detection.areas.filter((area) => area.staves?.middleRhos?.length).map((x) => x.staves.interval));
72
+
73
+ const sourceSize = v.sourceSize;
74
+ return {
75
+ ...v,
76
+ index: k,
77
+ vw: sourceSize.width / staffInterval, // 页面宽度(逻辑单位)
78
+ hwr: sourceSize.height / sourceSize.width, // 页面高宽比
79
+ };
80
+ });
81
+
82
+ if (!sizeRatios.length) {
83
+ throw new Error('empty result');
84
+ }
85
+
86
+ const maxVW = sizeRatios.sort((a, b) => b.vw - a.vw)[0];
87
+ const maxAspect = Math.max(...sizeRatios.map((r) => r.hwr));
88
+
89
+ score.unitSize = outputWidth / maxVW.vw;
90
+
91
+ // 页面显示尺寸
92
+ score.pageSize = {
93
+ width: outputWidth,
94
+ height: outputWidth * maxAspect,
95
+ };
96
+ }
97
+
98
+ const batchTask = (fn: () => Promise<any>) => fn();
99
+ const concurrencyTask = (fns: (() => Promise<any>)[]) => Promise.all(fns.map((fn) => fn()));
100
+
101
+ const shootStaffImage = async (
102
+ system: starry.System,
103
+ staffIndex: number,
104
+ { paddingLeft = 0, scaling = 1, spec }: { paddingLeft?: number; scaling?: number; spec: { viewportHeight: number; viewportUnit: number } }
105
+ ): Promise<Canvas> => {
106
+ if (!system || !system.backgroundImage) return null;
107
+
108
+ const staff = system.staves[staffIndex];
109
+ if (!staff) return null;
110
+
111
+ const middleUnits = spec.viewportHeight / spec.viewportUnit / 2;
112
+
113
+ const width = system.imagePosition.width * spec.viewportUnit;
114
+ const height = system.imagePosition.height * spec.viewportUnit;
115
+ const x = system.imagePosition.x * spec.viewportUnit + paddingLeft;
116
+ const y = (system.imagePosition.y - (staff.top + staff.staffY - middleUnits)) * spec.viewportUnit;
117
+
118
+ const canvas = new Canvas(Math.round(width + x) * scaling, spec.viewportHeight * scaling);
119
+ const context = canvas.getContext('2d');
120
+ context.fillStyle = 'white';
121
+ context.fillRect(0, 0, canvas.width, canvas.height);
122
+ context.drawImage(await loadImage(system.backgroundImage), x * scaling, y * scaling, width * scaling, height * scaling);
123
+
124
+ return canvas;
125
+ // .substr(22); // remove the prefix of 'data:image/png;base64,'
126
+ };
127
+
128
+ /**
129
+ * 根据布局检测结果进行截图
130
+ * @param score
131
+ * @param pageCanvas
132
+ * @param page
133
+ * @param detection
134
+ */
135
+ async function shootImageByDetection({
136
+ page,
137
+ score,
138
+ pageCanvas,
139
+ }: {
140
+ score: starry.Score;
141
+ page: starry.Page;
142
+ pageCanvas: Canvas; // 原始图片绘制好的canvas
143
+ }) {
144
+ if (!page?.layout?.areas?.length) {
145
+ return null;
146
+ }
147
+
148
+ page.width = score.pageSize.width / score.unitSize;
149
+ page.height = score.pageSize.height / score.unitSize;
150
+
151
+ const correctCanvas = new Canvas(pageCanvas.width, pageCanvas.height);
152
+ const ctx = correctCanvas.getContext('2d');
153
+
154
+ ctx.save();
155
+
156
+ const { width, height } = correctCanvas;
157
+ const [a, b, c, d] = page.source.matrix;
158
+
159
+ ctx.setTransform(a, b, c, d, (-1 / 2) * width + (1 / 2) * a * width + (1 / 2) * b * height, (-1 / 2) * height + (1 / 2) * c * width + (1 / 2) * d * height);
160
+
161
+ ctx.drawImage(pageCanvas, 0, 0);
162
+
163
+ ctx.restore();
164
+
165
+ const interval = page.source.interval;
166
+
167
+ page.layout.areas.map((area, systemIndex) => {
168
+ console.assert(area.staves?.middleRhos?.length, '[shootImageByDetection] empty area:', area);
169
+
170
+ const data = ctx.getImageData(area.x, area.y, area.width, area.height);
171
+
172
+ const canvas = new Canvas(area.width, area.height);
173
+
174
+ const context = canvas.getContext('2d');
175
+ // context.rotate(-area.staves.theta);
176
+ context.putImageData(data, 0, 0);
177
+
178
+ const detection = area.staves;
179
+ const size = { width: area.width, height: area.height };
180
+
181
+ const sourceCenter = {
182
+ x: pageCanvas.width / 2 / interval,
183
+ y: pageCanvas.height / 2 / interval,
184
+ };
185
+
186
+ const position = {
187
+ x: (area.x + area.staves.phi1) / interval - sourceCenter.x + page.width / 2,
188
+ y: area.y / interval - sourceCenter.y + page.height / 2,
189
+ };
190
+
191
+ page.systems[systemIndex] = constructSystem({
192
+ page,
193
+ backgroundImage: canvas.toBufferSync('png'),
194
+ detection,
195
+ imageSize: size,
196
+ position,
197
+ });
198
+ });
199
+
200
+ return correctCanvas;
201
+ }
202
+
203
+ async function shootStaffBackgroundImage({ system, staff, staffIndex }: { system: starry.System; staff: starry.Staff; staffIndex: number }) {
204
+ const sourceCanvas = await shootStaffImage(system, staffIndex, {
205
+ paddingLeft: STAFF_PADDING_LEFT,
206
+ spec: SEMANTIC_VISION_SPEC,
207
+ });
208
+
209
+ staff.backgroundImage = sourceCanvas.toBufferSync('png');
210
+
211
+ staff.imagePosition = {
212
+ x: -STAFF_PADDING_LEFT / SEMANTIC_VISION_SPEC.viewportUnit,
213
+ y: staff.staffY - SEMANTIC_VISION_SPEC.viewportHeight / 2 / SEMANTIC_VISION_SPEC.viewportUnit,
214
+ width: sourceCanvas.width / SEMANTIC_VISION_SPEC.viewportUnit,
215
+ height: sourceCanvas.height / SEMANTIC_VISION_SPEC.viewportUnit,
216
+ };
217
+ }
218
+
219
+ /**
220
+ * 单个staff的变形矫正
221
+ * @param system
222
+ * @param staff
223
+ * @param staffIndex
224
+ * @param gaugeImage
225
+ * @param pyClients
226
+ */
227
+ async function gaugeStaff({
228
+ system,
229
+ staff,
230
+ staffIndex,
231
+ gaugeImage,
232
+ pyClients,
233
+ }: {
234
+ system: starry.System;
235
+ staff: starry.Staff;
236
+ staffIndex: number;
237
+ gaugeImage: Buffer;
238
+ pyClients: PyClients;
239
+ }) {
240
+ const sourceCanvas = await shootStaffImage(system, staffIndex, {
241
+ paddingLeft: STAFF_PADDING_LEFT,
242
+ spec: GAUGE_VISION_SPEC,
243
+ scaling: 2,
244
+ });
245
+
246
+ const sourceBuffer = sourceCanvas.toBufferSync('png');
247
+
248
+ const baseY = (system.middleY - (staff.top + staff.staffY)) * GAUGE_VISION_SPEC.viewportUnit + GAUGE_VISION_SPEC.viewportHeight / 2;
249
+
250
+ const { buffer, size } = await pyClients.predictScoreImages('gaugeRenderer', [sourceBuffer, gaugeImage, baseY]);
251
+
252
+ staff.backgroundImage = buffer;
253
+
254
+ staff.imagePosition = {
255
+ x: -STAFF_PADDING_LEFT / GAUGE_VISION_SPEC.viewportUnit,
256
+ y: staff.staffY - size.height / 2 / GAUGE_VISION_SPEC.viewportUnit,
257
+ width: size.width / GAUGE_VISION_SPEC.viewportUnit,
258
+ height: size.height / GAUGE_VISION_SPEC.viewportUnit,
259
+ };
260
+
261
+ staff.maskImage = null;
262
+ }
263
+
264
+ /**
265
+ * 单个staff的降噪
266
+ * @param staff
267
+ * @param staffIndex
268
+ * @param maskImage
269
+ */
270
+ async function maskStaff({ staff, staffIndex, maskImage }: { staff: starry.Staff; staffIndex: number; maskImage: Buffer }) {
271
+ const img = await loadImage(maskImage);
272
+
273
+ staff.maskImage = maskImage;
274
+ staff.imagePosition = {
275
+ x: -STAFF_PADDING_LEFT / MASK_VISION_SPEC.viewportUnit,
276
+ y: staff.staffY - MASK_VISION_SPEC.viewportHeight / 2 / MASK_VISION_SPEC.viewportUnit,
277
+ width: img.width / MASK_VISION_SPEC.viewportUnit,
278
+ height: img.height / MASK_VISION_SPEC.viewportUnit,
279
+ };
280
+ }
281
+
282
+ /**
283
+ * 单个staff的语义识别
284
+ * @param score
285
+ * @param staffIndex
286
+ * @param system
287
+ * @param staff
288
+ * @param graph
289
+ */
290
+ async function semanticStaff({
291
+ score,
292
+ staffIndex,
293
+ system,
294
+ staff,
295
+ graph,
296
+ }: {
297
+ score: starry.Score;
298
+ staffIndex: number;
299
+ system: starry.System;
300
+ staff: starry.Staff;
301
+ graph: SemanticGraph;
302
+ }) {
303
+ graph.offset(-STAFF_PADDING_LEFT / SEMANTIC_VISION_SPEC.viewportUnit, 0);
304
+
305
+ system.assignSemantics(staffIndex, graph);
306
+
307
+ staff.assignSemantics(graph);
308
+ staff.clearPredictedTokens();
309
+
310
+ score.assembleSystem(system, score.settings?.semanticConfidenceThreshold || 1);
311
+ }
312
+
313
+ function replacePageImages(page: starry.Page, onReplaceImageKey: (src: string) => any) {
314
+ const tasks = [
315
+ [page.source, 'url'],
316
+ ...page.systems
317
+ .map((system) => {
318
+ return [
319
+ [system, 'backgroundImage'],
320
+ ...system.staves
321
+ .map((staff) => [
322
+ [staff, 'backgroundImage'],
323
+ [staff, 'maskImage'],
324
+ ])
325
+ .flat(),
326
+ ];
327
+ })
328
+ .flat(),
329
+ ];
330
+
331
+ tasks.map(([target, key]: [any, string]) => {
332
+ target[key] = onReplaceImageKey(target[key]);
333
+ });
334
+ }
335
+
336
+ export type TaskProgress = { total?: number; finished?: number };
337
+
338
+ export interface OMRPage {
339
+ url: string | Buffer;
340
+ key?: string;
341
+ layout?: LayoutResult;
342
+ renew?: boolean;
343
+ enableGauge?: boolean;
344
+ }
345
+
346
+ export interface ProgressState {
347
+ layout?: TaskProgress;
348
+ text?: TaskProgress;
349
+ gauge?: TaskProgress;
350
+ mask?: TaskProgress;
351
+ semantic?: TaskProgress;
352
+ regulate?: TaskProgress;
353
+ brackets?: TaskProgress;
354
+ }
355
+
356
+ class OMRProgress {
357
+ state: ProgressState = {};
358
+
359
+ onChange: (evt: ProgressState) => void;
360
+
361
+ constructor(onChange: (evt: ProgressState) => void) {
362
+ this.onChange = onChange;
363
+ }
364
+
365
+ setTotal(stage: keyof ProgressState, total: number) {
366
+ this.state[stage] = this.state[stage] || {
367
+ total,
368
+ finished: 0,
369
+ };
370
+ }
371
+
372
+ increase(stage: keyof ProgressState, step = 1) {
373
+ const info: TaskProgress = this.state[stage] || {
374
+ finished: 0,
375
+ };
376
+ info.finished += step;
377
+
378
+ this.onChange(this.state);
379
+ }
380
+ }
381
+
382
+ type SourceImage = string | Buffer;
383
+
384
+ export interface OMROption {
385
+ outputWidth?: number;
386
+ title?: string; // 曲谱标题
387
+ pageStore?: {
388
+ has?: (key: string) => Promise<Boolean>;
389
+ get: (key: string) => Promise<string>;
390
+ set: (key: string, val: string) => Promise<void>;
391
+ };
392
+ renew?: boolean;
393
+ processes?: (keyof ProgressState)[]; // 选择流程
394
+ onProgress?: (progress: ProgressState) => void;
395
+ onReplaceImage?: (src: SourceImage) => Promise<string>; // 替换所有图片地址,用于上传或者格式转换
396
+ }
397
+
398
+ const lruCache = new WeakLRUCache();
399
+
400
+ // 默认store
401
+ const pageStore = {
402
+ async get(key: string) {
403
+ return lruCache.getValue(key) as string;
404
+ },
405
+ async set(key: string, val: string) {
406
+ lruCache.setValue(key, val);
407
+ },
408
+ };
409
+
410
+ /**
411
+ * 默认将图片转换为webp格式的base64字符串
412
+ * @param src
413
+ */
414
+ const onReplaceImage = async (src: SourceImage) => {
415
+ if (src instanceof Buffer || (typeof src === 'string' && (/^https?:\/\//.test(src) || /^data:image\//.test(src)))) {
416
+ const webpBuffer = (await convertImage(src)).buffer;
417
+ return `data:image/webp;base64,${webpBuffer.toString('base64')}`;
418
+ }
419
+
420
+ return src;
421
+ };
422
+
423
+ /**
424
+ * 识别所有图片
425
+ * @param pyClients
426
+ * @param images
427
+ * @param option
428
+ */
429
+ export const predictPages = async (
430
+ pyClients: PyClients,
431
+ images: OMRPage[],
432
+ option: OMROption = { outputWidth: 1200, pageStore, onReplaceImage }
433
+ ): Promise<{ score: starry.Score; omitPages: number[]; stat: OMRStat }> => {
434
+ const logger = pyClients.logger;
435
+
436
+ option.outputWidth = option.outputWidth || 1200;
437
+ option.pageStore = option.pageStore || pageStore;
438
+ option.onReplaceImage = option.onReplaceImage || onReplaceImage;
439
+
440
+ option.processes =
441
+ Array.isArray(option.processes) && option.processes.length > 0 ? option.processes : ['layout', 'text', 'gauge', 'mask', 'semantic', 'brackets'];
442
+ const progress: OMRProgress = new OMRProgress(option.onProgress);
443
+
444
+ const t0 = Date.now();
445
+
446
+ // 预处理删除不合法区域
447
+ images.forEach((image) => {
448
+ if (image.layout?.detection) {
449
+ image.layout.detection.areas = image.layout.detection?.areas?.filter((a) => a?.staves?.middleRhos?.length > 0);
450
+ } else {
451
+ delete image.layout;
452
+ }
453
+ });
454
+
455
+ const score = new starry.Score({
456
+ title: option?.title,
457
+ stavesCount: 2,
458
+ paperOptions: {
459
+ raggedLast: true,
460
+ raggedLastBottom: true,
461
+ },
462
+ headers: {},
463
+ instrumentDict: {},
464
+ settings: {
465
+ enabledGauge: option.processes.includes('gauge'),
466
+ semanticConfidenceThreshold: 1,
467
+ },
468
+ });
469
+
470
+ logger.info(`[predictor]: download_source_images-${images.length}`);
471
+
472
+ // 原始拍摄图
473
+ const originalImages: Image[] = await Promise.all(images.map((img) => loadImage(img.url as any)));
474
+
475
+ logger.info(`[predictor]: source_images_downloaded-${images.length}`);
476
+
477
+ //const INPUT_IMAGE_WIDTH = images.filter((x) => x?.layout?.interval)?.[0]?.layout?.sourceSize?.width;
478
+
479
+ /******************************* 布局识别 start *************************/
480
+ // 输入给布局检测的图
481
+ const pageCanvasList: Canvas[] = originalImages.map((img, index) => scaleForLayout(img, images[index]!.layout?.sourceSize?.width ?? img.width));
482
+
483
+ progress.setTotal('layout', originalImages.length);
484
+ progress.setTotal('text', originalImages.length);
485
+
486
+ const detections = await Promise.all(
487
+ pageCanvasList.map(async (cvs, key) => {
488
+ if (!images[key].layout) return (await pyClients.predictScoreImages('layout', [cvs.toBufferSync('png')]))?.[0];
489
+
490
+ // reinforce layout from front-end if no gauge
491
+ if (!images[key].enableGauge && images[key]?.layout?.detection?.areas?.length)
492
+ return (await pyClients.predictScoreImages('layout$reinforce', [cvs.toBufferSync('png')], [images[key].layout]))?.[0];
493
+
494
+ return images[key].layout;
495
+ })
496
+ );
497
+
498
+ detections.forEach((page) => {
499
+ page.detection.areas = page.detection?.areas?.filter((a) => a?.staves?.middleRhos?.length > 0);
500
+ });
501
+
502
+ const imageURLMap = new Map<SourceImage, string>();
503
+ const collectImage = async (source: SourceImage): Promise<void> => {
504
+ const url = await option.onReplaceImage(source);
505
+ imageURLMap.set(source, url);
506
+ };
507
+
508
+ // 根据所有页面的宽高比决定全局显示尺寸
509
+ setGlobalPageSize(score, detections, option.outputWidth);
510
+
511
+ async function createPage(detect, pageIndex) {
512
+ const { url, key, layout, enableGauge } = images[pageIndex];
513
+
514
+ const pageKey = sha1(JSON.stringify({ key: key || url, layout, enableGauge }));
515
+
516
+ const cachedPageJson = await option.pageStore.get(pageKey);
517
+
518
+ const omit = !option.renew && ((cachedPageJson && !images[pageIndex].renew) || !detect.detection.areas?.length);
519
+
520
+ const page = (score.pages[pageIndex] =
521
+ omit && cachedPageJson
522
+ ? starry.recoverJSON<starry.Page>(cachedPageJson, starry)
523
+ : new starry.Page({
524
+ source: {
525
+ name: key || (typeof url === 'string' && /https?:\/\//.test(url) ? url : null),
526
+ size: 0,
527
+ url,
528
+ crop: {
529
+ unit: '%',
530
+ x: 0,
531
+ y: 0,
532
+ width: 100,
533
+ height: 100,
534
+ },
535
+ dimensions: detect.sourceSize,
536
+ matrix: [Math.cos(detect.theta), -Math.sin(detect.theta), Math.sin(detect.theta), Math.cos(detect.theta), 0, 0],
537
+ interval: detect.interval,
538
+ needGauge: images[pageIndex].enableGauge,
539
+ },
540
+ layout: detect.detection,
541
+ }));
542
+
543
+ const correctCanvas = omit
544
+ ? null
545
+ : await shootImageByDetection({
546
+ score,
547
+ page,
548
+ pageCanvas: pageCanvasList[pageIndex],
549
+ });
550
+
551
+ progress.increase('layout');
552
+
553
+ return {
554
+ page,
555
+ omit,
556
+ hash: pageKey,
557
+ correctCanvas,
558
+ };
559
+ }
560
+
561
+ const systemsCount = detections.reduce((acc, x) => acc + (x.detection.areas?.length ?? 0), 0);
562
+ const stavesCount = detections.reduce((acc, x) => acc + (x.detection.areas?.reduce?.((a, y) => a + (y.staves?.middleRhos?.length ?? 0), 0) ?? 0), 0);
563
+
564
+ progress.setTotal('gauge', stavesCount);
565
+ progress.setTotal('mask', stavesCount);
566
+ progress.setTotal('semantic', stavesCount);
567
+ progress.setTotal('brackets', systemsCount);
568
+
569
+ const allTasks = [];
570
+
571
+ const omitPages = [];
572
+
573
+ const t1 = Date.now();
574
+
575
+ let n_page = 0;
576
+
577
+ for (const pageIndex of detections.keys()) {
578
+ const pageTasks = [];
579
+
580
+ const { page, correctCanvas, omit, hash } = await createPage(detections[pageIndex], pageIndex);
581
+
582
+ pageTasks.push(collectImage(page.source.url));
583
+ pageTasks.push(...page.systems.map((system) => collectImage(system.backgroundImage)));
584
+
585
+ logger.info(`[predictor]: check_cache_pageIndex-${pageIndex} omit: ${omit}`);
586
+ if (omit) {
587
+ omitPages.push(pageIndex);
588
+ } else {
589
+ const staves = page.systems
590
+ .map((system, systemIndex) => system.staves.map((staff, staffIndex) => ({ pageIndex, systemIndex, staffIndex, page, system, staff })))
591
+ .flat(1);
592
+
593
+ await concurrencyTask([
594
+ /******************************* 括号检测 start *************************/
595
+ async () => {
596
+ if (!option.processes.includes('brackets')) return;
597
+
598
+ const detection = page.layout;
599
+ const interval = page.source.interval;
600
+
601
+ const startTime = Date.now();
602
+
603
+ const bracketImages = page.systems.map((system, systemIndex) => {
604
+ const {
605
+ x,
606
+ y,
607
+ staves: { middleRhos, phi1 },
608
+ } = detection.areas[systemIndex];
609
+
610
+ const topMid = middleRhos[0];
611
+ const bottomMid = middleRhos[middleRhos.length - 1];
612
+
613
+ const sourceRect = {
614
+ x: x + phi1 - 4 * interval,
615
+ y: y + topMid - 4 * interval,
616
+ width: 8 * interval,
617
+ height: bottomMid - topMid + 8 * interval,
618
+ };
619
+
620
+ const OUTPUT_INTERVAL = 8;
621
+
622
+ const canvas = new Canvas(OUTPUT_INTERVAL * 8, (sourceRect.height / interval) * OUTPUT_INTERVAL);
623
+
624
+ const context = canvas.getContext('2d');
625
+ context.drawImage(correctCanvas, sourceRect.x, sourceRect.y, sourceRect.width, sourceRect.height, 0, 0, canvas.width, canvas.height);
626
+
627
+ // console.log(pageIndex, systemIndex, JSON.stringify(sourceRect), correctCanvas.width, correctCanvas.height)
628
+ // const pctx = canvas.getContext('2d')
629
+ // pctx.strokeStyle = 'red'
630
+ // pctx.fillStyle = 'rgba(255, 0, 0, 0.2)'
631
+ // pctx.fillRect(sourceRect.x, sourceRect.y, sourceRect.width, sourceRect.height)
632
+ // const area = detections[pageIndex].detection.areas[systemIndex]
633
+ // pctx.strokeStyle = 'green'
634
+ // pctx.fillStyle = 'rgba(0, 255, 0, 0.1)'
635
+ // pctx.fillRect(area.x, area.y, area.width, area.height)
636
+ // pctx.fillRect(area.x, area.y, area.width, area.height)
637
+ // require('fs').writeFile(`test--system-${systemIndex}.png`, canvas.toBufferSync('png'), () => {})
638
+ // require('fs-extra').writeFile(`test--brackets-${pageIndex}-${systemIndex}.png`, canvas.toBufferSync('png'))
639
+
640
+ return {
641
+ system,
642
+ buffer: canvas.toBufferSync('png'),
643
+ };
644
+ });
645
+
646
+ logger.info(`[predictor]: brackets js [pageIndex-${pageIndex}] duration: ${Date.now() - startTime}`);
647
+
648
+ const bracketsRes = await pyClients.predictScoreImages('brackets', { buffers: bracketImages.map((x) => x.buffer) });
649
+ progress.increase('brackets', bracketImages.length);
650
+
651
+ bracketImages.forEach(({ system }, index) => {
652
+ if (bracketsRes[index]) {
653
+ system.bracketsAppearance = bracketsRes[index];
654
+ }
655
+ });
656
+ },
657
+ /******************************* 括号检测 end *************************/
658
+
659
+ /******************************* 文本识别 start *************************/
660
+ async () => {
661
+ if (!option.processes.includes('text')) return;
662
+
663
+ try {
664
+ const startTime = Date.now();
665
+
666
+ // await require('fs-extra').writeFile(`test--text-location-${pageIndex}.png`, correctCanvas.toBufferSync('png'))
667
+ const bufferForText = correctCanvas.toBufferSync('png');
668
+
669
+ const resultLoc = await pyClients.predictScoreImages('textLoc', [bufferForText]);
670
+
671
+ const location = resultLoc[0].filter((box) => box.score > 0);
672
+
673
+ if (location.length > 0) {
674
+ const [resultOCR] = await pyClients.predictScoreImages('textOcr', {
675
+ buffers: [bufferForText],
676
+ location,
677
+ });
678
+
679
+ page.assignTexts(resultOCR.areas, resultOCR.imageSize);
680
+ page.assemble();
681
+ }
682
+
683
+ logger.info(`[predictor]: text js [pageIndex-${pageIndex}] duration: ${Date.now() - startTime}`);
684
+
685
+ progress.increase('text');
686
+
687
+ if (!option.title) {
688
+ const coverTexts: {
689
+ confidence: number;
690
+ fontSize: number;
691
+ id: string;
692
+ text: string;
693
+ textType: 'Title' | 'Author';
694
+ type: starry.TokenType;
695
+ width_: number;
696
+ x: number;
697
+ y: number;
698
+ }[] = score.pages[0].tokens as any;
699
+
700
+ if (Array.isArray(coverTexts) && coverTexts.length > 0) {
701
+ const [titleToken] = coverTexts
702
+ .filter((x) => x.type === starry.TokenType.Text && x.textType === 'Title')
703
+ .sort((a, b) => b.fontSize - a.fontSize);
704
+
705
+ if (titleToken) {
706
+ score.title = titleToken.text;
707
+ }
708
+ }
709
+ }
710
+ } catch (err) {
711
+ logger.error(`[predictor]: text js [pageIndex-${pageIndex}] ${JSON.stringify(err)}`);
712
+ }
713
+ },
714
+ /******************************* 文本识别 end *************************/
715
+ async () => {
716
+ /******************************* 变形矫正 start *************************/
717
+ await batchTask(async () => {
718
+ const disableGauge = !option.processes.includes('gauge') || images[pageIndex].enableGauge === false;
719
+
720
+ if (!disableGauge) {
721
+ const gaugeRes = await pyClients.predictScoreImages(
722
+ 'gauge',
723
+ await Promise.all(
724
+ staves.map(async ({ staffIndex, system }) => {
725
+ const startTime = Date.now();
726
+ const sourceCanvas = await shootStaffImage(system, staffIndex, {
727
+ paddingLeft: STAFF_PADDING_LEFT,
728
+ spec: GAUGE_VISION_SPEC,
729
+ });
730
+
731
+ logger.info(`[predictor]: gauge js shoot [page-${pageIndex}, staff-${staffIndex}] duration: ${Date.now() - startTime}`);
732
+
733
+ return sourceCanvas.toBufferSync('png');
734
+ })
735
+ )
736
+ );
737
+
738
+ for (const [index, { system, staff, pageIndex, staffIndex }] of staves.entries()) {
739
+ const startTime = Date.now();
740
+
741
+ logger.info(`[predictor]: gauge js [page-${pageIndex}, staff-${staffIndex}] start..`);
742
+ await gaugeStaff({
743
+ pyClients,
744
+ system,
745
+ staff,
746
+ staffIndex,
747
+ gaugeImage: gaugeRes[index].image,
748
+ });
749
+ logger.info(`[predictor]: gauge js [page-${pageIndex}, staff-${staffIndex}] duration: ${Date.now() - startTime}`);
750
+
751
+ progress.increase('gauge');
752
+
753
+ pageTasks.push(collectImage(staff.backgroundImage));
754
+ }
755
+ } else {
756
+ for (const [_, { system, staff, staffIndex }] of staves.entries()) {
757
+ await shootStaffBackgroundImage({
758
+ system,
759
+ staff,
760
+ staffIndex,
761
+ });
762
+ pageTasks.push(collectImage(staff.backgroundImage));
763
+ }
764
+ }
765
+ });
766
+ /******************************* 变形矫正 end *************************/
767
+
768
+ await concurrencyTask([
769
+ /******************************* 降噪 start *************************/
770
+ async () => {
771
+ if (!option.processes.includes('mask')) return;
772
+
773
+ const maskRes = await pyClients.predictScoreImages(
774
+ 'mask',
775
+ staves.map(({ staff }) => staff.backgroundImage as Buffer)
776
+ );
777
+
778
+ for (const [index, { staff, staffIndex }] of staves.entries()) {
779
+ const startTime = Date.now();
780
+
781
+ await maskStaff({
782
+ staff,
783
+ staffIndex,
784
+ maskImage: maskRes[index].image,
785
+ });
786
+
787
+ logger.info(`[predictor]: mask js [page-${pageIndex}, ${index}, staff-${staffIndex}] duration: ${Date.now() - startTime}`);
788
+ progress.increase('mask');
789
+
790
+ pageTasks.push(collectImage(staff.maskImage));
791
+ }
792
+ },
793
+ /******************************* 降噪 end *************************/
794
+
795
+ /******************************* 语义识别 start *************************/
796
+ async () => {
797
+ if (!option.processes.includes('semantic')) return;
798
+
799
+ const semanticRes = starry.recoverJSON<starry.SemanticGraph[]>(
800
+ await pyClients.predictScoreImages(
801
+ 'semantic',
802
+ staves.map(({ staff }) => staff.backgroundImage as Buffer)
803
+ ),
804
+ starry
805
+ );
806
+
807
+ staves.forEach(({ system }) => system.clearTokens());
808
+
809
+ for (const [index, { staffIndex, system, staff }] of staves.entries()) {
810
+ const startTime = Date.now();
811
+
812
+ await semanticStaff({
813
+ score,
814
+ system,
815
+ staff,
816
+ staffIndex,
817
+ graph: semanticRes[index],
818
+ });
819
+
820
+ logger.info(
821
+ `[predictor]: semantic js [page-${pageIndex}, system-${system.index}, staff-${staff.index}] duration: ${
822
+ Date.now() - startTime
823
+ }`
824
+ );
825
+ progress.increase('semantic');
826
+ }
827
+ },
828
+ /******************************* 语义识别 end *************************/
829
+ ]);
830
+ },
831
+ ]);
832
+
833
+ ++n_page;
834
+ }
835
+
836
+ allTasks.push(
837
+ Promise.all(pageTasks).then(() => {
838
+ replacePageImages(page, (src) => imageURLMap.get(src));
839
+ logger.info(`[predictor]: pageStore set: [${pageIndex}]`);
840
+ return option.pageStore.set(hash, JSON.stringify(page));
841
+ })
842
+ );
843
+ }
844
+
845
+ const t2 = Date.now();
846
+
847
+ await Promise.all(allTasks);
848
+
849
+ logger.info(`[predictor]: inferenceStaffLayout: ${score.title}, [${score.systems.length}]`);
850
+
851
+ score.inferenceStaffLayout();
852
+
853
+ logger.info(`[predictor]: done: ${score.title}`);
854
+
855
+ // correct semantic ids
856
+ score.assemble();
857
+
858
+ const t3 = Date.now();
859
+
860
+ return {
861
+ score,
862
+ omitPages,
863
+ stat: {
864
+ cost: t3 - t0,
865
+ pagesCost: t2 - t1,
866
+ pages: n_page,
867
+ },
868
+ };
869
+ };
870
+
871
+ export const abstractOMRStats = (stats: OMRStat[]): OMRSummary => {
872
+ const { costTotal, pagesCostTotal, pagesTotal } = stats.reduce(
873
+ (sum, stat) => ({
874
+ costTotal: sum.costTotal + stat.cost,
875
+ pagesCostTotal: sum.pagesCostTotal + stat.pagesCost,
876
+ pagesTotal: sum.pagesTotal + stat.pages,
877
+ }),
878
+ { costTotal: 0, pagesCostTotal: 0, pagesTotal: 0 }
879
+ );
880
+
881
+ return {
882
+ costTotal,
883
+ costPerPage: pagesTotal ? costTotal / pagesTotal : null,
884
+ pagesTotal,
885
+ scoreN: stats.length,
886
+ };
887
+ };
backend/libs/predictors.ts ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ZeroClient, { Logger } from './ZeroClient';
2
+ import * as starry from '../../src/starry';
3
+ import PyProcessor from './PyProcessor';
4
+ import { destructPromise } from './async-queue';
5
+ import { getPort } from 'portfinder';
6
+ import util from 'util';
7
+ import { Options } from 'python-shell';
8
+
9
+ const getPortPromise = util.promisify(getPort);
10
+
11
+ export interface LayoutResult {
12
+ detection: starry.PageLayout;
13
+ theta: number;
14
+ interval: number;
15
+ sourceSize?: {
16
+ width: number;
17
+ height: number;
18
+ };
19
+ }
20
+
21
+ export interface PredictorInterface {
22
+ layout: (streams: Buffer[]) => LayoutResult[];
23
+ layout$reinforce: (streams: Buffer[], baseLayouts: LayoutResult[]) => LayoutResult[];
24
+ gauge: (streams: Buffer[]) => {
25
+ image: Buffer;
26
+ }[];
27
+ mask: (streams: Buffer[]) => {
28
+ image: Buffer;
29
+ }[];
30
+ semantic: (streams: Buffer[]) => any[];
31
+ textLoc: (streams: Buffer[]) => any[];
32
+ textOcr: (params: { buffers: Buffer[]; location: any[] }) => any[];
33
+ brackets: (params: { buffers: Buffer[] }) => any[];
34
+ topo: (params: { clusters: starry.EventCluster[] }) => any[];
35
+ gaugeRenderer: (params: [Buffer, Buffer, number]) => { buffer: Buffer; size: { width: number; height: number } };
36
+ jianpu: (params: { buffers: Buffer[] }) => any[];
37
+ // [source: Buffer, gauge: Buffer, baseY: number]
38
+ }
39
+
40
+ type PredictorType = keyof PredictorInterface;
41
+
42
+ export type PyClientsConstructOptions = Partial<Record<PredictorType, Options | string>>;
43
+
44
+ export class PyClients {
45
+ clients = new Map<string, Promise<ZeroClient>>();
46
+
47
+ constructor(public readonly options: PyClientsConstructOptions, public readonly logger: Logger = console) {}
48
+
49
+ async getClient(type: PredictorType) {
50
+ if (this.clients.has(type)) {
51
+ return this.clients.get(type);
52
+ }
53
+
54
+ const [promise, resolve, reject] = destructPromise<ZeroClient>();
55
+
56
+ const opt = this.options[type];
57
+
58
+ if (!opt) {
59
+ throw new Error(`no config for client \`${type}\` found`);
60
+ }
61
+
62
+ try {
63
+ if (typeof opt === 'string') {
64
+ const client = new ZeroClient();
65
+ client.bind(opt);
66
+ resolve(client);
67
+ } else {
68
+ const { scriptPath, ...option } = opt;
69
+ const client = new PyProcessor(scriptPath, option, this.logger);
70
+ await client.bind(`${await getPortPromise()}`);
71
+ resolve(client);
72
+ }
73
+
74
+ this.logger.info(`PyClients: ${type} started`);
75
+ } catch (err) {
76
+ this.logger.error(`PyClients: ${type} start fail: ${JSON.stringify(err)}`);
77
+ reject(err);
78
+ }
79
+
80
+ this.clients.set(type, promise);
81
+
82
+ return promise;
83
+ }
84
+
85
+ async checkHost(type: PredictorType): Promise<string> {
86
+ const client = await this.getClient(type);
87
+
88
+ return client.request('checkHost');
89
+ }
90
+
91
+ async warmup() {
92
+ const opts = Object.keys(this.options) as PredictorType[];
93
+ await Promise.all(opts.map((type) => this.getClient(type)));
94
+ }
95
+
96
+ /**
97
+ * 模型预测
98
+ * @param type layout | mask | gauge | semantic
99
+ * @param args
100
+ */
101
+ async predictScoreImages<T extends PredictorType>(type: T, ...args: Parameters<PredictorInterface[T]>): Promise<ReturnType<PredictorInterface[T]>> {
102
+ const clientType = type.split('$')[0] as PredictorType;
103
+ const client = await this.getClient(clientType);
104
+ let res = null;
105
+
106
+ this.logger.info(`[predictor]: ${type} py start..`);
107
+ const start = Date.now();
108
+
109
+ switch (type) {
110
+ case 'layout':
111
+ res = await client.request('predictDetection', args);
112
+ break;
113
+ case 'layout$reinforce':
114
+ res = await client.request('predictReinforce', args);
115
+ break;
116
+ case 'gauge':
117
+ case 'mask':
118
+ res = await client.request('predict', args, { by_buffer: true });
119
+ break;
120
+ case 'semantic':
121
+ case 'textLoc':
122
+ res = await client.request('predict', args);
123
+ break;
124
+ case 'textOcr':
125
+ case 'brackets':
126
+ case 'topo':
127
+ case 'gaugeRenderer':
128
+ case 'jianpu':
129
+ res = await client.request('predict', ...args);
130
+ break;
131
+ default:
132
+ this.logger.error(`[predictor]: no predictor ${type}`);
133
+ }
134
+
135
+ this.logger.info(`[predictor]: ${type} py duration: ${Date.now() - start}ms`);
136
+
137
+ return res;
138
+ }
139
+ }
backend/libs/regulation.ts ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as starry from '../../src/starry';
2
+ import { PyClients } from './predictors';
3
+ import { Logger } from './ZeroClient';
4
+ import { SpartitoMeasure, EditableMeasure, evaluateMeasure } from '../../src/starry';
5
+ import { EquationPolicy } from '../../src/starry/spartitoMeasure';
6
+ import { genMeasureRectifications } from '../../src/starry/measureRectification';
7
+ import { SolutionStore, DefaultSolutionStore, SaveIssueMeasure } from './store';
8
+ export * from './regulationBead';
9
+
10
+ globalThis.btoa = globalThis.btoa || ((str) => Buffer.from(str, 'binary').toString('base64'));
11
+
12
+ const RECTIFICATION_SEARCH_ITERATIONS = parseInt(process.env.RECTIFICATION_SEARCH_ITERATIONS || '30');
13
+ const BASE_QUOTA_FACTOR = parseInt(process.env.BASE_QUOTA_FACTOR || '40');
14
+ const RECTIFICATION_QUOTA_FACTOR = parseInt(process.env.RECTIFICATION_QUOTA_FACTOR || '80');
15
+
16
+ const MATRIXH_INTERPOLATION_K = 0.9;
17
+
18
+ interface SolveMeasureOptions {
19
+ solver?: (...args: any[]) => any;
20
+ quotaMax?: number;
21
+ quotaFactor?: number;
22
+ solutionStore?: SolutionStore;
23
+ ignoreCache?: boolean;
24
+ logger?: Logger;
25
+ }
26
+
27
+ const computeQuota = (n: number, factor: number, limit: number) =>
28
+ Math.min(Math.ceil((n + 1) * factor * Math.log(n + 2)), Math.ceil(limit * Math.min(1, (24 / (n + 1)) ** 2)));
29
+
30
+ interface BaseRegulationStat {
31
+ cached: number;
32
+ computed: number;
33
+ solved: number;
34
+ }
35
+
36
+ async function solveMeasures(
37
+ measures: SpartitoMeasure[],
38
+ { solver, quotaMax = 1000, quotaFactor = BASE_QUOTA_FACTOR, solutionStore = DefaultSolutionStore, ignoreCache = false, logger }: SolveMeasureOptions = {}
39
+ ): Promise<BaseRegulationStat> {
40
+ let cached = 0;
41
+ let solved = 0;
42
+
43
+ logger?.info(`[solveMeasures] begin, measure total: ${measures.length}.`);
44
+
45
+ await Promise.all(
46
+ measures.map(async (measure) => {
47
+ if (!ignoreCache) {
48
+ const solution = await solutionStore.get(measure.regulationHash);
49
+ if (solution) {
50
+ measure.applySolution(solution);
51
+ ++cached;
52
+ return;
53
+ }
54
+ }
55
+
56
+ const quota = computeQuota(measure.events.length, quotaFactor, quotaMax);
57
+
58
+ await measure.regulate({
59
+ policy: 'equations',
60
+ quota,
61
+ solver,
62
+ });
63
+
64
+ const stat = evaluateMeasure(measure);
65
+ if (!stat.error) solutionStore.set(measure.regulationHash0, { ...measure.asSolution(), priority: -measure?.solutionStat?.loss! });
66
+ if (stat.perfect) ++solved;
67
+
68
+ logger?.info(
69
+ `[solveMeasures] measure[${measure.measureIndex}/${measures.length}] regulated: ${stat.perfect ? 'solved' : stat.error ? 'error' : 'issue'}, ${
70
+ measure.regulationHash
71
+ }`
72
+ );
73
+ })
74
+ );
75
+
76
+ logger?.info(`[solveMeasures] ${cached}/${measures.length} cache hit, ${solved} solved.`);
77
+
78
+ return {
79
+ cached,
80
+ computed: measures.length - cached,
81
+ solved,
82
+ };
83
+ }
84
+
85
+ const solveMeasuresWithRectifications = async (
86
+ measure: SpartitoMeasure,
87
+ { solver, quotaMax = 4000 }: SolveMeasureOptions
88
+ ): Promise<starry.RegulationSolution> => {
89
+ let best = evaluateMeasure(measure);
90
+ let bestSolution: starry.RegulationSolution = measure.asSolution();
91
+ const quota = computeQuota(measure.events.length, RECTIFICATION_QUOTA_FACTOR, quotaMax);
92
+ let n_rec = 0;
93
+
94
+ // @ts-ignore
95
+ for (const rec of genMeasureRectifications(measure)) {
96
+ const solution = await EquationPolicy.regulateMeasureWithRectification(measure, rec, { solver, quota });
97
+
98
+ const testMeasure = measure.deepCopy() as SpartitoMeasure;
99
+ testMeasure.applySolution(solution);
100
+ const result = evaluateMeasure(testMeasure);
101
+
102
+ if (
103
+ result.perfect > best.perfect ||
104
+ result.error < best.error ||
105
+ (!result.error && result.perfect >= best.perfect && solution.priority! > bestSolution.priority!)
106
+ ) {
107
+ best = result;
108
+ bestSolution = solution;
109
+ }
110
+
111
+ if (result.perfect) break;
112
+
113
+ ++n_rec;
114
+ if (n_rec > RECTIFICATION_SEARCH_ITERATIONS) break;
115
+ }
116
+
117
+ return bestSolution;
118
+ };
119
+
120
+ interface RegulateWithTopoOption {
121
+ solutionStore: SolutionStore;
122
+ pyClients: PyClients;
123
+ solver: (...args: any[]) => any;
124
+ onSaveIssueMeasure?: SaveIssueMeasure;
125
+ }
126
+
127
+ interface RegulateMaybeWithTopoOption {
128
+ solutionStore: SolutionStore;
129
+ pyClients?: PyClients;
130
+ solver: (...args: any[]) => any;
131
+ onSaveIssueMeasure?: SaveIssueMeasure;
132
+ }
133
+
134
+ interface RegulateSimpleOption {
135
+ solutionStore: SolutionStore;
136
+ solver: (...args: any[]) => any;
137
+ logger?: Logger;
138
+ quotaMax?: number;
139
+ quotaFactor?: number;
140
+ }
141
+
142
+ interface TopoRegulationStat {
143
+ solved: number;
144
+ issue: number;
145
+ fatal: number;
146
+ }
147
+
148
+ async function doRegulateWithTopo(
149
+ score: starry.Score,
150
+ { pyClients, solver, solutionStore = DefaultSolutionStore, onSaveIssueMeasure }: RegulateWithTopoOption
151
+ ): Promise<TopoRegulationStat> {
152
+ pyClients.logger.info(`[RegulateWithTopo] regulate score: ${score.title}, measures: ${score.spartito!.measures.length}`);
153
+
154
+ const issueMeasures = score.spartito!.measures.filter((measure) => {
155
+ const stat = evaluateMeasure(measure);
156
+ return !stat.perfect;
157
+ });
158
+ pyClients.logger.info(`[RegulateWithTopo] basic issues: ${issueMeasures.length}`);
159
+
160
+ if (issueMeasures.length === 0) {
161
+ return {
162
+ solved: 0,
163
+ issue: 0,
164
+ fatal: 0,
165
+ };
166
+ }
167
+
168
+ const clusters = ([] as starry.EventCluster[]).concat(...issueMeasures.map((measure) => measure.createClusters()));
169
+ const results = await pyClients.predictScoreImages('topo', { clusters });
170
+ console.assert(results.length === clusters.length, 'prediction number mismatch:', clusters.length, results.length);
171
+
172
+ clusters.forEach((cluster, index) => {
173
+ const result = results[index];
174
+ console.assert(result, 'no result for cluster:', cluster.index);
175
+
176
+ cluster.assignPrediction(result);
177
+ });
178
+
179
+ issueMeasures.forEach((measure) => {
180
+ const cs = clusters.filter((c) => c.index === measure.measureIndex);
181
+ measure.applyClusters(cs);
182
+
183
+ // intepolate matrixH
184
+ const { matrixH } = EquationPolicy.estiamteMeasure(measure);
185
+ matrixH.forEach((row, i) =>
186
+ row.forEach((v, j) => {
187
+ measure.matrixH[i][j] = measure.matrixH[i][j] * MATRIXH_INTERPOLATION_K + v * (1 - MATRIXH_INTERPOLATION_K);
188
+ })
189
+ );
190
+ });
191
+
192
+ const solvedIndices: number[] = [];
193
+ const errorIndices: number[] = [];
194
+
195
+ // rectification search
196
+ await Promise.all(
197
+ issueMeasures.map(async (measure) => {
198
+ const hash = measure.regulationHash0;
199
+ const solution = await solveMeasuresWithRectifications(measure, { solver });
200
+ if (solution) {
201
+ measure.applySolution(solution);
202
+ solutionStore.set(hash, solution);
203
+ solutionStore.set(measure.regulationHash, measure.asSolution());
204
+ pyClients.logger.info(`[RegulateWithTopo] solutionStore set: ${measure.measureIndex}, ${hash}, ${measure.regulationHash}`);
205
+ }
206
+
207
+ const stat = evaluateMeasure(measure);
208
+ onSaveIssueMeasure?.({
209
+ measureIndex: measure.measureIndex,
210
+ measure: new EditableMeasure(measure),
211
+ status: stat.error ? 2 : 1,
212
+ });
213
+ if (stat.perfect) solvedIndices.push(measure.measureIndex);
214
+ else if (stat.error) errorIndices.push(measure.measureIndex);
215
+ })
216
+ );
217
+
218
+ const n_issues = issueMeasures.length - solvedIndices.length - errorIndices.length;
219
+ pyClients.logger.info(`[RegulateWithTopo] score: ${score.title}, solved/issue/fatal: ${solvedIndices.length}/${n_issues}/${errorIndices.length}`);
220
+ if (solvedIndices.length) pyClients.logger.info(`[RegulateWithTopo] solved measures: ${solvedIndices.join(', ')}`);
221
+ if (errorIndices.length) pyClients.logger.info(`[RegulateWithTopo] error measures: ${errorIndices.join(', ')}`);
222
+
223
+ return {
224
+ solved: solvedIndices.length,
225
+ issue: n_issues,
226
+ fatal: errorIndices.length,
227
+ };
228
+ }
229
+
230
+ interface RegulationStat {
231
+ baseCost: number; // in milliseconds
232
+ topoCost: number; // in milliseconds
233
+ baseMeasures: BaseRegulationStat;
234
+ topoMeasures?: TopoRegulationStat;
235
+ qualityScore: number;
236
+ }
237
+
238
+ const doRegulate = async (
239
+ score: starry.Score,
240
+ { pyClients, solver, solutionStore = DefaultSolutionStore, onSaveIssueMeasure }: RegulateMaybeWithTopoOption
241
+ ): Promise<RegulationStat> => {
242
+ pyClients?.logger?.info(`[doRegulate] score: ${score.title}`);
243
+
244
+ score.spartito = undefined;
245
+ score.assemble();
246
+ const spartito = score.makeSpartito();
247
+
248
+ spartito.measures.forEach((measure) => score.assignBackgroundForMeasure(measure));
249
+
250
+ const t0 = Date.now();
251
+
252
+ const baseMeasures = await solveMeasures(spartito.measures, { solver, quotaMax: 1000, solutionStore, logger: pyClients?.logger });
253
+
254
+ const t1 = Date.now();
255
+
256
+ const topoMeasures = pyClients ? await doRegulateWithTopo(score, { pyClients, solver, solutionStore, onSaveIssueMeasure }) : undefined;
257
+
258
+ const t2 = Date.now();
259
+
260
+ return {
261
+ baseCost: t1 - t0,
262
+ topoCost: t2 - t1,
263
+ baseMeasures,
264
+ topoMeasures,
265
+ qualityScore: spartito.qualityScore,
266
+ };
267
+ };
268
+
269
+ const doSimpleRegulate = async (
270
+ score: starry.Score,
271
+ { solver, solutionStore = DefaultSolutionStore, logger, quotaMax = 240, quotaFactor = 16 }: RegulateSimpleOption
272
+ ): Promise<void> => {
273
+ score.assemble();
274
+ const spartito = score.spartito || score.makeSpartito();
275
+ const measures = spartito.measures.filter((measure) => !measure.regulated);
276
+
277
+ await solveMeasures(measures, { solver, quotaMax, quotaFactor, solutionStore, logger });
278
+
279
+ console.assert(score.spartito?.regulated, 'doSimpleRegulate: regulation incomplete:', spartito.measures.filter((measure) => !measure.regulated).length);
280
+ };
281
+
282
+ const evaluateScoreQuality = async (score: starry.Score, options: RegulateSimpleOption): Promise<number | null> => {
283
+ if (!score.spartito?.regulated) await doSimpleRegulate(score, options);
284
+
285
+ return score.spartito!.regulated ? score.spartito!.qualityScore : null;
286
+ };
287
+
288
+ interface RegulationSummary {
289
+ scoreN: number;
290
+
291
+ baseCostTotal: number; // in milliseconds
292
+ topoCostTotal: number; // in milliseconds
293
+ baseCostPerMeasure: number | null; // in milliseconds
294
+ topoCostPerMeasure: number | null; // in milliseconds
295
+
296
+ cached: number;
297
+ baseComputed: number;
298
+ baseSolved: number;
299
+ topoSolved: number;
300
+ topoIssue: number;
301
+ topoFatal: number;
302
+ }
303
+
304
+ const abstractRegulationStats = (stats: RegulationStat[]): RegulationSummary => {
305
+ const { baseCostTotal, topoCostTotal, baseMeasures, topoMeasures } = stats.reduce(
306
+ (sum, stat) => ({
307
+ baseCostTotal: sum.baseCostTotal + stat.baseCost,
308
+ topoCostTotal: sum.topoCostTotal + stat.topoCost,
309
+ baseMeasures: sum.baseMeasures + stat.baseMeasures.computed,
310
+ topoMeasures: sum.topoMeasures + (stat.topoMeasures!.solved + stat.topoMeasures!.issue + stat.topoMeasures!.fatal),
311
+ }),
312
+ {
313
+ baseCostTotal: 0,
314
+ topoCostTotal: 0,
315
+ baseMeasures: 0,
316
+ topoMeasures: 0,
317
+ }
318
+ );
319
+
320
+ const baseCostPerMeasure = baseMeasures > 0 ? baseCostTotal / baseMeasures : null;
321
+ const topoCostPerMeasure = topoMeasures > 0 ? topoCostTotal / topoMeasures : null;
322
+
323
+ const { cached, baseComputed, baseSolved, topoSolved, topoIssue, topoFatal } = stats.reduce(
324
+ (sum, stat) => ({
325
+ cached: sum.cached + stat.baseMeasures.cached,
326
+ baseComputed: sum.baseComputed + stat.baseMeasures.computed,
327
+ baseSolved: sum.baseSolved + stat.baseMeasures.solved,
328
+ topoSolved: sum.topoSolved + stat.topoMeasures!.solved,
329
+ topoIssue: sum.topoIssue + stat.topoMeasures!.issue,
330
+ topoFatal: sum.topoFatal + stat.topoMeasures!.fatal,
331
+ }),
332
+ { cached: 0, baseComputed: 0, baseSolved: 0, topoSolved: 0, topoIssue: 0, topoFatal: 0 }
333
+ );
334
+
335
+ return {
336
+ scoreN: stats.length,
337
+ baseCostTotal,
338
+ topoCostTotal,
339
+ baseCostPerMeasure,
340
+ topoCostPerMeasure,
341
+ cached,
342
+ baseComputed,
343
+ baseSolved,
344
+ topoSolved,
345
+ topoIssue,
346
+ topoFatal,
347
+ };
348
+ };
349
+
350
+ export { doRegulate, doSimpleRegulate, evaluateScoreQuality, abstractRegulationStats };
backend/libs/regulationBead.ts ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as starry from '../../src/starry';
2
+ import { Logger } from './ZeroClient';
3
+ import { SolutionStore, DefaultSolutionStore, SaveIssueMeasure, MeasureStatus } from './store';
4
+
5
+ interface BeadRegulationCounting {
6
+ cached: number;
7
+ simple: number;
8
+ computed: number;
9
+ tryTimes: number;
10
+ solved: number;
11
+ issue: number;
12
+ fatal: number;
13
+ }
14
+
15
+ interface RegulationBeadStat {
16
+ totalCost: number; // in milliseconds
17
+ pickerCost: number; // in milliseconds
18
+ measures: BeadRegulationCounting;
19
+ qualityScore: number;
20
+ }
21
+
22
+ interface RegulationBeadSummary {
23
+ scoreN: number;
24
+
25
+ totalCost: number; // in milliseconds
26
+ pickerCost: number; // in milliseconds
27
+ costPerMeasure: number | null; // in milliseconds
28
+ costPerTime: number | null; // in milliseconds
29
+
30
+ cached: number;
31
+ simple: number;
32
+ computed: number;
33
+ tryTimes: number;
34
+ solved: number;
35
+ issue: number;
36
+ fatal: number;
37
+ }
38
+
39
+ interface ProgressInfo {
40
+ pass: number;
41
+ remaining: number;
42
+ total: number;
43
+ }
44
+
45
+ interface RegulateBeadOption {
46
+ logger?: Logger;
47
+ pickers: starry.BeadPicker[];
48
+ solutionStore?: SolutionStore;
49
+ ignoreCache?: boolean;
50
+ freshOnly?: boolean;
51
+ onSaveIssueMeasure?: SaveIssueMeasure;
52
+ onProgress?: (measure: starry.SpartitoMeasure, evaluation: starry.MeasureEvaluation, better: boolean, progress: ProgressInfo) => void;
53
+ onPassStart?: (pass: number, conditionName: string, pendingCount: number) => void;
54
+ }
55
+
56
+ interface MeasureReord {
57
+ origin: starry.SpartitoMeasure;
58
+ current: starry.SpartitoMeasure;
59
+ evaluation?: starry.MeasureEvaluation;
60
+ baseQuality: number;
61
+ picker: starry.BeadPicker;
62
+ }
63
+
64
+ interface BeadSolverOptions {
65
+ stopLoss: number;
66
+ quotaMax: number;
67
+ quotaFactor: number;
68
+ ptFactor: number;
69
+ }
70
+
71
+ enum PendingCondition {
72
+ ErrorOnly,
73
+ NotFine,
74
+ Imperfect,
75
+ }
76
+
77
+ const isPending = (evaluation: starry.MeasureEvaluation, condition: PendingCondition) => {
78
+ switch (condition) {
79
+ case PendingCondition.ErrorOnly:
80
+ return evaluation.error;
81
+
82
+ case PendingCondition.Imperfect:
83
+ return !evaluation.perfect;
84
+ }
85
+
86
+ return !evaluation.fine;
87
+ };
88
+
89
+ type OnUpdate = (measure: starry.SpartitoMeasure, evaluation: starry.MeasureEvaluation, better: boolean) => void;
90
+
91
+ const solveMeasureRecords = async (
92
+ records: MeasureReord[],
93
+ onUpdate: OnUpdate,
94
+ stdout: NodeJS.WritableStream | null,
95
+ options: Partial<BeadSolverOptions>,
96
+ pendingCondition: PendingCondition = PendingCondition.NotFine,
97
+ pass: number = 0,
98
+ onProgress?: RegulateBeadOption['onProgress']
99
+ ): Promise<number> => {
100
+ const pendingRecords = records.filter(({ evaluation }) => !evaluation || isPending(evaluation, pendingCondition));
101
+ stdout?.write('.'.repeat(pendingRecords.length));
102
+ stdout?.write('\b'.repeat(pendingRecords.length));
103
+
104
+ const total = pendingRecords.length;
105
+ let done = 0;
106
+
107
+ for (const record of pendingRecords) {
108
+ const measure = record.current.deepCopy();
109
+ measure.staffGroups = record.current.staffGroups;
110
+
111
+ const solution = await starry.beadSolver.solveMeasure(measure, { picker: record.picker, ...options });
112
+ measure.applySolution(solution);
113
+
114
+ const evaluation = starry.evaluateMeasure(measure);
115
+ const better =
116
+ !record.evaluation ||
117
+ evaluation.fine > record.evaluation.fine ||
118
+ (evaluation.qualityScore > record.evaluation.qualityScore && evaluation.fine === record.evaluation.fine);
119
+ if (better) {
120
+ record.evaluation = evaluation;
121
+ Object.assign(record.current, measure);
122
+ }
123
+
124
+ onUpdate(record.current, evaluation, better);
125
+
126
+ done++;
127
+ onProgress?.(record.current, evaluation, better, { pass, remaining: total - done, total });
128
+ }
129
+
130
+ if (pendingRecords.length) stdout?.write('\n');
131
+
132
+ return pendingRecords.length;
133
+ };
134
+
135
+ const regulateWithBeadSolver = async (
136
+ score: starry.Score,
137
+ { logger, pickers, solutionStore = DefaultSolutionStore, ignoreCache, freshOnly, onSaveIssueMeasure, onProgress, onPassStart }: RegulateBeadOption
138
+ ): Promise<RegulationBeadStat> => {
139
+ score.spartito = undefined;
140
+ score.assemble();
141
+ const spartito = score.makeSpartito();
142
+
143
+ spartito.measures.forEach((measure) => score.assignBackgroundForMeasure(measure));
144
+
145
+ const t0 = Date.now();
146
+ logger?.info(`[regulateWithBeadSolver] begin, measure total: ${spartito.measures.length}.`, ignoreCache ? 'ignoreCache' : '', freshOnly ? 'freshOnly' : '');
147
+
148
+ const records = spartito.measures
149
+ .filter((measure) => measure.events?.length && !measure.patched)
150
+ .map(
151
+ (measure) =>
152
+ ({
153
+ origin: measure.deepCopy(),
154
+ current: measure,
155
+ evaluation: undefined,
156
+ baseQuality: 0,
157
+ } as MeasureReord)
158
+ );
159
+
160
+ // rectify time signature
161
+ for (const measure of spartito.measures.filter((measure) => measure.events?.length)) {
162
+ const picker = pickers.find((picker) => picker.n_seq > measure.events.length + 1);
163
+ if (picker) await starry.beadSolver.estimateMeasure(measure, picker);
164
+ }
165
+ spartito.rectifyTimeSignatures(logger as any);
166
+
167
+ // zero pickers' cost
168
+ pickers.forEach((picker) => (picker.cost = 0));
169
+
170
+ const counting = {
171
+ cached: 0,
172
+ simple: 0,
173
+ computed: 0,
174
+ tryTimes: 0,
175
+ solved: 0,
176
+ issue: 0,
177
+ fatal: 0,
178
+ };
179
+
180
+ logger?.info(`[regulateWithBeadSolver] measures estimation finished.`);
181
+
182
+ // apply solutions
183
+ if (solutionStore && !ignoreCache)
184
+ for (const record of records) {
185
+ const solution = await solutionStore.get(record.origin.regulationHash0);
186
+ if (solution) {
187
+ record.current.applySolution(solution);
188
+ ++counting.cached;
189
+
190
+ record.evaluation = starry.evaluateMeasure(record.current);
191
+ record.baseQuality = record.evaluation.qualityScore;
192
+ }
193
+ }
194
+
195
+ logger?.info('[regulateWithBeadSolver]', `${counting.cached}/${records.length}`, 'solutions loaded.');
196
+
197
+ const stdout = logger ? null : process.stdout;
198
+ if (counting.cached) stdout?.write(`${counting.cached}c`);
199
+
200
+ records.forEach((record) => {
201
+ const picker = pickers.find((picker) => picker.n_seq > record.current.events.length + 1);
202
+ if (!picker) {
203
+ logger?.info(`[regulateWithBeadSolver] measure[${record.current.measureIndex}] size out of range:`, record.current.events.length);
204
+ } else record.picker = picker;
205
+ });
206
+
207
+ const pendingRecords = records.filter((record) => record.picker && (!record.evaluation || (!record.evaluation.fine && !freshOnly))) as (MeasureReord & {
208
+ evaluation: starry.MeasureEvaluation;
209
+ })[];
210
+
211
+ // solve by simple policy
212
+ pendingRecords.forEach((record) => {
213
+ const measure = record.current.deepCopy();
214
+ measure.staffGroups = record.current.staffGroups;
215
+
216
+ measure.regulate({ policy: 'simple' });
217
+
218
+ const evaluation = starry.evaluateMeasure(measure);
219
+ const better = !record.evaluation || evaluation.qualityScore > record.evaluation.qualityScore;
220
+ if (better) {
221
+ record.evaluation = evaluation;
222
+ Object.assign(record.current, measure);
223
+
224
+ if (evaluation.perfect) {
225
+ logger?.info(`[regulateWithBeadSolver] measure[${record.current.measureIndex}] regulated by simple policy.`);
226
+ ++counting.simple;
227
+ }
228
+ }
229
+ });
230
+ counting.computed = pendingRecords.length - counting.simple;
231
+
232
+ if (counting.simple) stdout?.write(`${counting.simple}s`);
233
+
234
+ const onUpdate = (measure, evaluation, better) => {
235
+ logger?.info(
236
+ `[regulateWithBeadSolver] measure[${measure.measureIndex}/${spartito.measures.length}] regulated${
237
+ better ? '+' : '-'
238
+ }: ${evaluation.qualityScore.toFixed(3)}, ${evaluation.fine ? 'solved' : evaluation.error ? 'error' : 'issue'}, ${measure.regulationHash}`
239
+ );
240
+
241
+ stdout?.write(`\x1b[${evaluation.fine ? '32' : evaluation.error ? '31' : '33'}m${better ? '+' : '-'}\x1b[0m`);
242
+ };
243
+
244
+ // Global progress: total = all measures, remaining = non-fine measures across all passes
245
+ const totalMeasures = spartito.measures.length;
246
+ const computeRemaining = () => pendingRecords.filter((r) => !r.evaluation?.fine).length;
247
+ const wrappedOnProgress = onProgress
248
+ ? (measure: starry.SpartitoMeasure, evaluation: starry.MeasureEvaluation, better: boolean, progress: ProgressInfo) => {
249
+ onProgress(measure, evaluation, better, { pass: progress.pass, remaining: computeRemaining(), total: totalMeasures });
250
+ }
251
+ : undefined;
252
+
253
+ onPassStart?.(1, 'Imperfect', computeRemaining());
254
+ counting.tryTimes += await solveMeasureRecords(
255
+ pendingRecords,
256
+ onUpdate,
257
+ stdout,
258
+ { stopLoss: 0.05, quotaMax: 200, quotaFactor: 3, ptFactor: 1 },
259
+ PendingCondition.Imperfect,
260
+ 1,
261
+ wrappedOnProgress
262
+ );
263
+ onPassStart?.(2, 'NotFine', computeRemaining());
264
+ counting.tryTimes += await solveMeasureRecords(
265
+ pendingRecords,
266
+ onUpdate,
267
+ stdout,
268
+ { stopLoss: 0.08, quotaMax: 1000, quotaFactor: 20, ptFactor: 1.6 },
269
+ PendingCondition.NotFine,
270
+ 2,
271
+ wrappedOnProgress
272
+ );
273
+ onPassStart?.(3, 'ErrorOnly', computeRemaining());
274
+ counting.tryTimes += await solveMeasureRecords(
275
+ pendingRecords,
276
+ onUpdate,
277
+ stdout,
278
+ { stopLoss: 0.08, quotaMax: 1000, quotaFactor: 40, ptFactor: 3 },
279
+ PendingCondition.ErrorOnly,
280
+ 3,
281
+ wrappedOnProgress
282
+ );
283
+
284
+ pendingRecords.forEach(({ evaluation, baseQuality, current, origin }) => {
285
+ if (evaluation.fine) ++counting.solved;
286
+ else if (evaluation.error) ++counting.fatal;
287
+ else ++counting.issue;
288
+
289
+ if (evaluation.qualityScore > baseQuality || !baseQuality) {
290
+ solutionStore.set(origin.regulationHash0, { ...current.asSolution(origin), priority: -current?.solutionStat?.loss! });
291
+ if (current.regulationHash !== origin.regulationHash0)
292
+ solutionStore.set(current.regulationHash, { ...current.asSolution(), priority: -current?.solutionStat?.loss! });
293
+ //console.log('better:', current.measureIndex, evaluation.qualityScore, baseQuality);
294
+ }
295
+
296
+ if (!evaluation.fine) {
297
+ onSaveIssueMeasure?.({
298
+ measureIndex: current.measureIndex,
299
+ measure: new starry.EditableMeasure(current),
300
+ status: evaluation.error ? MeasureStatus.Fatal : MeasureStatus.Issue,
301
+ });
302
+ }
303
+ });
304
+
305
+ const t1 = Date.now();
306
+ const pickerCost = pickers.reduce((cost, picker) => cost + picker.cost, 0);
307
+
308
+ const qualityScore = spartito.qualityScore;
309
+ const totalCost = t1 - t0;
310
+
311
+ logger?.info('[regulateWithBeadSolver] done in ', totalCost, 'ms, qualityScore:', qualityScore);
312
+
313
+ // zero 'cached' statistics for freshOnly mode
314
+ if (freshOnly) counting.cached = 0;
315
+
316
+ return {
317
+ totalCost: t1 - t0,
318
+ pickerCost,
319
+ measures: counting,
320
+ qualityScore,
321
+ };
322
+ };
323
+
324
+ const abstractRegulationBeadStats = (stats: RegulationBeadStat[]): RegulationBeadSummary => {
325
+ const { totalCost, pickerCost, measureN, timeN } = stats.reduce(
326
+ (sum, stat) => ({
327
+ totalCost: sum.totalCost + stat.totalCost,
328
+ pickerCost: sum.pickerCost + stat.pickerCost,
329
+ measureN: sum.measureN + stat.measures.computed,
330
+ timeN: sum.timeN + stat.measures.tryTimes,
331
+ }),
332
+ {
333
+ totalCost: 0,
334
+ pickerCost: 0,
335
+ measureN: 0,
336
+ timeN: 0,
337
+ }
338
+ );
339
+
340
+ const costPerMeasure = measureN > 0 ? totalCost / measureN : null;
341
+ const costPerTime = timeN > 0 ? totalCost / timeN : null;
342
+
343
+ const { cached, simple, computed, tryTimes, solved, issue, fatal } = stats.reduce(
344
+ (sum, stat) => ({
345
+ cached: sum.cached + stat.measures.cached,
346
+ simple: sum.simple + stat.measures.simple,
347
+ computed: sum.computed + stat.measures.computed,
348
+ tryTimes: sum.tryTimes + stat.measures.tryTimes,
349
+ solved: sum.solved + stat.measures.solved,
350
+ issue: sum.issue + stat.measures.issue,
351
+ fatal: sum.fatal + stat.measures.fatal,
352
+ }),
353
+ { cached: 0, simple: 0, computed: 0, tryTimes: 0, solved: 0, issue: 0, fatal: 0 }
354
+ );
355
+
356
+ return {
357
+ scoreN: stats.length,
358
+ totalCost,
359
+ pickerCost,
360
+ costPerMeasure,
361
+ costPerTime,
362
+ cached,
363
+ simple,
364
+ computed,
365
+ tryTimes,
366
+ solved,
367
+ issue,
368
+ fatal,
369
+ };
370
+ };
371
+
372
+ export { regulateWithBeadSolver, abstractRegulationBeadStats, RegulationBeadStat, ProgressInfo };
backend/libs/store.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { WeakLRUCache } from 'weak-lru-cache';
2
+
3
+ import { RegulationSolution, SpartitoMeasure } from '../../src/starry';
4
+
5
+ const lruCache = new WeakLRUCache();
6
+
7
+ interface SolutionStore {
8
+ get: (key: string) => Promise<RegulationSolution>;
9
+ set: (key: string, val: RegulationSolution) => Promise<void>;
10
+ batchGet: (keys: string[]) => Promise<RegulationSolution[]>;
11
+ }
12
+
13
+ // 默认store
14
+ const DefaultSolutionStore: SolutionStore = {
15
+ async get(key: string) {
16
+ return lruCache.getValue(key) as RegulationSolution;
17
+ },
18
+ async set(key: string, val: RegulationSolution) {
19
+ lruCache.setValue(key, val);
20
+ },
21
+ async batchGet(keys: string[]) {
22
+ return keys.map((key) => lruCache.getValue(key) as RegulationSolution);
23
+ },
24
+ };
25
+
26
+ const enum MeasureStatus {
27
+ Discard = -1,
28
+ Solved = 0,
29
+ Issue = 1,
30
+ Fatal = 2,
31
+ }
32
+
33
+ interface IssueMeasure {
34
+ scoreId: string;
35
+ measureIndex: number;
36
+ measure: SpartitoMeasure;
37
+ status: MeasureStatus;
38
+ }
39
+
40
+ type SaveIssueMeasure = (data: Omit<IssueMeasure, 'scoreId'>) => void;
41
+
42
+ export { SolutionStore, DefaultSolutionStore, MeasureStatus, IssueMeasure, SaveIssueMeasure };
backend/libs/three/Three.Legacy.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ export {};
backend/libs/three/Three.Legacy.js ADDED
@@ -0,0 +1,1444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Audio } from './audio/Audio.js';
2
+ import { AudioAnalyser } from './audio/AudioAnalyser.js';
3
+ import { PerspectiveCamera } from './cameras/PerspectiveCamera.js';
4
+ import { FlatShading, sRGBEncoding, LinearEncoding, StaticDrawUsage, DynamicDrawUsage, TrianglesDrawMode } from './constants.js';
5
+ import {
6
+ Float64BufferAttribute,
7
+ Float32BufferAttribute,
8
+ Uint32BufferAttribute,
9
+ Int32BufferAttribute,
10
+ Uint16BufferAttribute,
11
+ Int16BufferAttribute,
12
+ Uint8ClampedBufferAttribute,
13
+ Uint8BufferAttribute,
14
+ Int8BufferAttribute,
15
+ BufferAttribute,
16
+ } from './core/BufferAttribute.js';
17
+ import { BufferGeometry } from './core/BufferGeometry.js';
18
+ import { InterleavedBuffer } from './core/InterleavedBuffer.js';
19
+ import { Object3D } from './core/Object3D.js';
20
+ import { Uniform } from './core/Uniform.js';
21
+ import { Curve } from './extras/core/Curve.js';
22
+ import { Path } from './extras/core/Path.js';
23
+ import { AxesHelper } from './helpers/AxesHelper.js';
24
+ import { BoxHelper } from './helpers/BoxHelper.js';
25
+ import { GridHelper } from './helpers/GridHelper.js';
26
+ import { SkeletonHelper } from './helpers/SkeletonHelper.js';
27
+ import { EdgesGeometry } from './geometries/EdgesGeometry.js';
28
+ import { ExtrudeGeometry } from './geometries/ExtrudeGeometry.js';
29
+ import { ShapeGeometry } from './geometries/ShapeGeometry.js';
30
+ import { WireframeGeometry } from './geometries/WireframeGeometry.js';
31
+ import { Light } from './lights/Light.js';
32
+ import { Loader } from './loaders/Loader.js';
33
+ import { LoaderUtils } from './loaders/LoaderUtils.js';
34
+ import { FileLoader } from './loaders/FileLoader.js';
35
+ import { AudioLoader } from './loaders/AudioLoader.js';
36
+ import { CubeTextureLoader } from './loaders/CubeTextureLoader.js';
37
+ import { DataTextureLoader } from './loaders/DataTextureLoader.js';
38
+ import { TextureLoader } from './loaders/TextureLoader.js';
39
+ import { Material } from './materials/Material.js';
40
+ import { LineBasicMaterial } from './materials/LineBasicMaterial.js';
41
+ import { PointsMaterial } from './materials/PointsMaterial.js';
42
+ import { ShaderMaterial } from './materials/ShaderMaterial.js';
43
+ import { Box2 } from './math/Box2.js';
44
+ import { Box3 } from './math/Box3.js';
45
+ import { Sphere } from './math/Sphere.js';
46
+ import { Color } from './math/Color.js';
47
+ import { Frustum } from './math/Frustum.js';
48
+ import { Line3 } from './math/Line3.js';
49
+ import * as MathUtils from './math/MathUtils.js';
50
+ import { Matrix3 } from './math/Matrix3.js';
51
+ import { Matrix4 } from './math/Matrix4.js';
52
+ import { Plane } from './math/Plane.js';
53
+ import { Quaternion } from './math/Quaternion.js';
54
+ import { Ray } from './math/Ray.js';
55
+ import { Triangle } from './math/Triangle.js';
56
+ import { Vector2 } from './math/Vector2.js';
57
+ import { Vector3 } from './math/Vector3.js';
58
+ import { Vector4 } from './math/Vector4.js';
59
+ import { Mesh } from './objects/Mesh.js';
60
+ import { LineSegments } from './objects/LineSegments.js';
61
+ import { Points } from './objects/Points.js';
62
+ import { Sprite } from './objects/Sprite.js';
63
+ import { SkinnedMesh } from './objects/SkinnedMesh.js';
64
+ import { WebGLRenderer } from './renderers/WebGLRenderer.js';
65
+ import { WebGLRenderTarget } from './renderers/WebGLRenderTarget.js';
66
+ import { WebGLCubeRenderTarget } from './renderers/WebGLCubeRenderTarget.js';
67
+ import { WebGLShadowMap } from './renderers/webgl/WebGLShadowMap.js';
68
+ import { ImageUtils } from './extras/ImageUtils.js';
69
+ import { Shape } from './extras/core/Shape.js';
70
+ import { CubeCamera } from './cameras/CubeCamera.js';
71
+ import { Scene } from './scenes/Scene.js';
72
+
73
+ export { MathUtils as Math };
74
+
75
+ export const LineStrip = 0;
76
+ export const LinePieces = 1;
77
+ export const NoColors = 0;
78
+ export const FaceColors = 1;
79
+ export const VertexColors = 2;
80
+
81
+ export function MeshFaceMaterial(materials) {
82
+ console.warn('THREE.MeshFaceMaterial has been removed. Use an Array instead.');
83
+ return materials;
84
+ }
85
+
86
+ export function MultiMaterial(materials = []) {
87
+ console.warn('THREE.MultiMaterial has been removed. Use an Array instead.');
88
+ materials.isMultiMaterial = true;
89
+ materials.materials = materials;
90
+ materials.clone = function () {
91
+ return materials.slice();
92
+ };
93
+
94
+ return materials;
95
+ }
96
+
97
+ export function PointCloud(geometry, material) {
98
+ console.warn('THREE.PointCloud has been renamed to THREE.Points.');
99
+ return new Points(geometry, material);
100
+ }
101
+
102
+ export function Particle(material) {
103
+ console.warn('THREE.Particle has been renamed to THREE.Sprite.');
104
+ return new Sprite(material);
105
+ }
106
+
107
+ export function ParticleSystem(geometry, material) {
108
+ console.warn('THREE.ParticleSystem has been renamed to THREE.Points.');
109
+ return new Points(geometry, material);
110
+ }
111
+
112
+ export function PointCloudMaterial(parameters) {
113
+ console.warn('THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial.');
114
+ return new PointsMaterial(parameters);
115
+ }
116
+
117
+ export function ParticleBasicMaterial(parameters) {
118
+ console.warn('THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial.');
119
+ return new PointsMaterial(parameters);
120
+ }
121
+
122
+ export function ParticleSystemMaterial(parameters) {
123
+ console.warn('THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial.');
124
+ return new PointsMaterial(parameters);
125
+ }
126
+
127
+ export function Vertex(x, y, z) {
128
+ console.warn('THREE.Vertex has been removed. Use THREE.Vector3 instead.');
129
+ return new Vector3(x, y, z);
130
+ }
131
+
132
+ //
133
+
134
+ export function DynamicBufferAttribute(array, itemSize) {
135
+ console.warn('THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setUsage( THREE.DynamicDrawUsage ) instead.');
136
+ return new BufferAttribute(array, itemSize).setUsage(DynamicDrawUsage);
137
+ }
138
+
139
+ export function Int8Attribute(array, itemSize) {
140
+ console.warn('THREE.Int8Attribute has been removed. Use new THREE.Int8BufferAttribute() instead.');
141
+ return new Int8BufferAttribute(array, itemSize);
142
+ }
143
+
144
+ export function Uint8Attribute(array, itemSize) {
145
+ console.warn('THREE.Uint8Attribute has been removed. Use new THREE.Uint8BufferAttribute() instead.');
146
+ return new Uint8BufferAttribute(array, itemSize);
147
+ }
148
+
149
+ export function Uint8ClampedAttribute(array, itemSize) {
150
+ console.warn('THREE.Uint8ClampedAttribute has been removed. Use new THREE.Uint8ClampedBufferAttribute() instead.');
151
+ return new Uint8ClampedBufferAttribute(array, itemSize);
152
+ }
153
+
154
+ export function Int16Attribute(array, itemSize) {
155
+ console.warn('THREE.Int16Attribute has been removed. Use new THREE.Int16BufferAttribute() instead.');
156
+ return new Int16BufferAttribute(array, itemSize);
157
+ }
158
+
159
+ export function Uint16Attribute(array, itemSize) {
160
+ console.warn('THREE.Uint16Attribute has been removed. Use new THREE.Uint16BufferAttribute() instead.');
161
+ return new Uint16BufferAttribute(array, itemSize);
162
+ }
163
+
164
+ export function Int32Attribute(array, itemSize) {
165
+ console.warn('THREE.Int32Attribute has been removed. Use new THREE.Int32BufferAttribute() instead.');
166
+ return new Int32BufferAttribute(array, itemSize);
167
+ }
168
+
169
+ export function Uint32Attribute(array, itemSize) {
170
+ console.warn('THREE.Uint32Attribute has been removed. Use new THREE.Uint32BufferAttribute() instead.');
171
+ return new Uint32BufferAttribute(array, itemSize);
172
+ }
173
+
174
+ export function Float32Attribute(array, itemSize) {
175
+ console.warn('THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead.');
176
+ return new Float32BufferAttribute(array, itemSize);
177
+ }
178
+
179
+ export function Float64Attribute(array, itemSize) {
180
+ console.warn('THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead.');
181
+ return new Float64BufferAttribute(array, itemSize);
182
+ }
183
+
184
+ //
185
+
186
+ Curve.create = function (construct, getPoint) {
187
+ console.log('THREE.Curve.create() has been deprecated');
188
+
189
+ construct.prototype = Object.create(Curve.prototype);
190
+ construct.prototype.constructor = construct;
191
+ construct.prototype.getPoint = getPoint;
192
+
193
+ return construct;
194
+ };
195
+
196
+ //
197
+
198
+ Path.prototype.fromPoints = function (points) {
199
+ console.warn('THREE.Path: .fromPoints() has been renamed to .setFromPoints().');
200
+ return this.setFromPoints(points);
201
+ };
202
+
203
+ //
204
+
205
+ export function AxisHelper(size) {
206
+ console.warn('THREE.AxisHelper has been renamed to THREE.AxesHelper.');
207
+ return new AxesHelper(size);
208
+ }
209
+
210
+ export function BoundingBoxHelper(object, color) {
211
+ console.warn('THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead.');
212
+ return new BoxHelper(object, color);
213
+ }
214
+
215
+ export function EdgesHelper(object, hex) {
216
+ console.warn('THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead.');
217
+ return new LineSegments(new EdgesGeometry(object.geometry), new LineBasicMaterial({ color: hex !== undefined ? hex : 0xffffff }));
218
+ }
219
+
220
+ GridHelper.prototype.setColors = function () {
221
+ console.error('THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.');
222
+ };
223
+
224
+ SkeletonHelper.prototype.update = function () {
225
+ console.error('THREE.SkeletonHelper: update() no longer needs to be called.');
226
+ };
227
+
228
+ export function WireframeHelper(object, hex) {
229
+ console.warn('THREE.WireframeHelper has been removed. Use THREE.WireframeGeometry instead.');
230
+ return new LineSegments(new WireframeGeometry(object.geometry), new LineBasicMaterial({ color: hex !== undefined ? hex : 0xffffff }));
231
+ }
232
+
233
+ //
234
+
235
+ Loader.prototype.extractUrlBase = function (url) {
236
+ console.warn('THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.');
237
+ return LoaderUtils.extractUrlBase(url);
238
+ };
239
+
240
+ Loader.Handlers = {
241
+ add: function (/* regex, loader */) {
242
+ console.error('THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.');
243
+ },
244
+
245
+ get: function (/* file */) {
246
+ console.error('THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.');
247
+ },
248
+ };
249
+
250
+ export function XHRLoader(manager) {
251
+ console.warn('THREE.XHRLoader has been renamed to THREE.FileLoader.');
252
+ return new FileLoader(manager);
253
+ }
254
+
255
+ export function BinaryTextureLoader(manager) {
256
+ console.warn('THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader.');
257
+ return new DataTextureLoader(manager);
258
+ }
259
+
260
+ //
261
+
262
+ Box2.prototype.center = function (optionalTarget) {
263
+ console.warn('THREE.Box2: .center() has been renamed to .getCenter().');
264
+ return this.getCenter(optionalTarget);
265
+ };
266
+
267
+ Box2.prototype.empty = function () {
268
+ console.warn('THREE.Box2: .empty() has been renamed to .isEmpty().');
269
+ return this.isEmpty();
270
+ };
271
+
272
+ Box2.prototype.isIntersectionBox = function (box) {
273
+ console.warn('THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().');
274
+ return this.intersectsBox(box);
275
+ };
276
+
277
+ Box2.prototype.size = function (optionalTarget) {
278
+ console.warn('THREE.Box2: .size() has been renamed to .getSize().');
279
+ return this.getSize(optionalTarget);
280
+ };
281
+
282
+ //
283
+
284
+ Box3.prototype.center = function (optionalTarget) {
285
+ console.warn('THREE.Box3: .center() has been renamed to .getCenter().');
286
+ return this.getCenter(optionalTarget);
287
+ };
288
+
289
+ Box3.prototype.empty = function () {
290
+ console.warn('THREE.Box3: .empty() has been renamed to .isEmpty().');
291
+ return this.isEmpty();
292
+ };
293
+
294
+ Box3.prototype.isIntersectionBox = function (box) {
295
+ console.warn('THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().');
296
+ return this.intersectsBox(box);
297
+ };
298
+
299
+ Box3.prototype.isIntersectionSphere = function (sphere) {
300
+ console.warn('THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().');
301
+ return this.intersectsSphere(sphere);
302
+ };
303
+
304
+ Box3.prototype.size = function (optionalTarget) {
305
+ console.warn('THREE.Box3: .size() has been renamed to .getSize().');
306
+ return this.getSize(optionalTarget);
307
+ };
308
+
309
+ //
310
+
311
+ Sphere.prototype.empty = function () {
312
+ console.warn('THREE.Sphere: .empty() has been renamed to .isEmpty().');
313
+ return this.isEmpty();
314
+ };
315
+
316
+ //
317
+
318
+ Frustum.prototype.setFromMatrix = function (m) {
319
+ console.warn('THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix().');
320
+ return this.setFromProjectionMatrix(m);
321
+ };
322
+
323
+ //
324
+
325
+ Line3.prototype.center = function (optionalTarget) {
326
+ console.warn('THREE.Line3: .center() has been renamed to .getCenter().');
327
+ return this.getCenter(optionalTarget);
328
+ };
329
+
330
+ //
331
+
332
+ Matrix3.prototype.flattenToArrayOffset = function (array, offset) {
333
+ console.warn('THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.');
334
+ return this.toArray(array, offset);
335
+ };
336
+
337
+ Matrix3.prototype.multiplyVector3 = function (vector) {
338
+ console.warn('THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.');
339
+ return vector.applyMatrix3(this);
340
+ };
341
+
342
+ Matrix3.prototype.multiplyVector3Array = function (/* a */) {
343
+ console.error('THREE.Matrix3: .multiplyVector3Array() has been removed.');
344
+ };
345
+
346
+ Matrix3.prototype.applyToBufferAttribute = function (attribute) {
347
+ console.warn('THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead.');
348
+ return attribute.applyMatrix3(this);
349
+ };
350
+
351
+ Matrix3.prototype.applyToVector3Array = function (/* array, offset, length */) {
352
+ console.error('THREE.Matrix3: .applyToVector3Array() has been removed.');
353
+ };
354
+
355
+ Matrix3.prototype.getInverse = function (matrix) {
356
+ console.warn('THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.');
357
+ return this.copy(matrix).invert();
358
+ };
359
+
360
+ //
361
+
362
+ Matrix4.prototype.extractPosition = function (m) {
363
+ console.warn('THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().');
364
+ return this.copyPosition(m);
365
+ };
366
+
367
+ Matrix4.prototype.flattenToArrayOffset = function (array, offset) {
368
+ console.warn('THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.');
369
+ return this.toArray(array, offset);
370
+ };
371
+
372
+ Matrix4.prototype.getPosition = function () {
373
+ console.warn('THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.');
374
+ return new Vector3().setFromMatrixColumn(this, 3);
375
+ };
376
+
377
+ Matrix4.prototype.setRotationFromQuaternion = function (q) {
378
+ console.warn('THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().');
379
+ return this.makeRotationFromQuaternion(q);
380
+ };
381
+
382
+ Matrix4.prototype.multiplyToArray = function () {
383
+ console.warn('THREE.Matrix4: .multiplyToArray() has been removed.');
384
+ };
385
+
386
+ Matrix4.prototype.multiplyVector3 = function (vector) {
387
+ console.warn('THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.');
388
+ return vector.applyMatrix4(this);
389
+ };
390
+
391
+ Matrix4.prototype.multiplyVector4 = function (vector) {
392
+ console.warn('THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.');
393
+ return vector.applyMatrix4(this);
394
+ };
395
+
396
+ Matrix4.prototype.multiplyVector3Array = function (/* a */) {
397
+ console.error('THREE.Matrix4: .multiplyVector3Array() has been removed.');
398
+ };
399
+
400
+ Matrix4.prototype.rotateAxis = function (v) {
401
+ console.warn('THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.');
402
+ v.transformDirection(this);
403
+ };
404
+
405
+ Matrix4.prototype.crossVector = function (vector) {
406
+ console.warn('THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.');
407
+ return vector.applyMatrix4(this);
408
+ };
409
+
410
+ Matrix4.prototype.translate = function () {
411
+ console.error('THREE.Matrix4: .translate() has been removed.');
412
+ };
413
+
414
+ Matrix4.prototype.rotateX = function () {
415
+ console.error('THREE.Matrix4: .rotateX() has been removed.');
416
+ };
417
+
418
+ Matrix4.prototype.rotateY = function () {
419
+ console.error('THREE.Matrix4: .rotateY() has been removed.');
420
+ };
421
+
422
+ Matrix4.prototype.rotateZ = function () {
423
+ console.error('THREE.Matrix4: .rotateZ() has been removed.');
424
+ };
425
+
426
+ Matrix4.prototype.rotateByAxis = function () {
427
+ console.error('THREE.Matrix4: .rotateByAxis() has been removed.');
428
+ };
429
+
430
+ Matrix4.prototype.applyToBufferAttribute = function (attribute) {
431
+ console.warn('THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead.');
432
+ return attribute.applyMatrix4(this);
433
+ };
434
+
435
+ Matrix4.prototype.applyToVector3Array = function (/* array, offset, length */) {
436
+ console.error('THREE.Matrix4: .applyToVector3Array() has been removed.');
437
+ };
438
+
439
+ Matrix4.prototype.makeFrustum = function (left, right, bottom, top, near, far) {
440
+ console.warn('THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.');
441
+ return this.makePerspective(left, right, top, bottom, near, far);
442
+ };
443
+
444
+ Matrix4.prototype.getInverse = function (matrix) {
445
+ console.warn('THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.');
446
+ return this.copy(matrix).invert();
447
+ };
448
+
449
+ //
450
+
451
+ Plane.prototype.isIntersectionLine = function (line) {
452
+ console.warn('THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().');
453
+ return this.intersectsLine(line);
454
+ };
455
+
456
+ //
457
+
458
+ Quaternion.prototype.multiplyVector3 = function (vector) {
459
+ console.warn('THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.');
460
+ return vector.applyQuaternion(this);
461
+ };
462
+
463
+ Quaternion.prototype.inverse = function () {
464
+ console.warn('THREE.Quaternion: .inverse() has been renamed to invert().');
465
+ return this.invert();
466
+ };
467
+
468
+ //
469
+
470
+ Ray.prototype.isIntersectionBox = function (box) {
471
+ console.warn('THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().');
472
+ return this.intersectsBox(box);
473
+ };
474
+
475
+ Ray.prototype.isIntersectionPlane = function (plane) {
476
+ console.warn('THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().');
477
+ return this.intersectsPlane(plane);
478
+ };
479
+
480
+ Ray.prototype.isIntersectionSphere = function (sphere) {
481
+ console.warn('THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().');
482
+ return this.intersectsSphere(sphere);
483
+ };
484
+
485
+ //
486
+
487
+ Triangle.prototype.area = function () {
488
+ console.warn('THREE.Triangle: .area() has been renamed to .getArea().');
489
+ return this.getArea();
490
+ };
491
+
492
+ Triangle.prototype.barycoordFromPoint = function (point, target) {
493
+ console.warn('THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().');
494
+ return this.getBarycoord(point, target);
495
+ };
496
+
497
+ Triangle.prototype.midpoint = function (target) {
498
+ console.warn('THREE.Triangle: .midpoint() has been renamed to .getMidpoint().');
499
+ return this.getMidpoint(target);
500
+ };
501
+
502
+ Triangle.prototypenormal = function (target) {
503
+ console.warn('THREE.Triangle: .normal() has been renamed to .getNormal().');
504
+ return this.getNormal(target);
505
+ };
506
+
507
+ Triangle.prototype.plane = function (target) {
508
+ console.warn('THREE.Triangle: .plane() has been renamed to .getPlane().');
509
+ return this.getPlane(target);
510
+ };
511
+
512
+ Triangle.barycoordFromPoint = function (point, a, b, c, target) {
513
+ console.warn('THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().');
514
+ return Triangle.getBarycoord(point, a, b, c, target);
515
+ };
516
+
517
+ Triangle.normal = function (a, b, c, target) {
518
+ console.warn('THREE.Triangle: .normal() has been renamed to .getNormal().');
519
+ return Triangle.getNormal(a, b, c, target);
520
+ };
521
+
522
+ //
523
+
524
+ Shape.prototype.extractAllPoints = function (divisions) {
525
+ console.warn('THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.');
526
+ return this.extractPoints(divisions);
527
+ };
528
+
529
+ Shape.prototype.extrude = function (options) {
530
+ console.warn('THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.');
531
+ return new ExtrudeGeometry(this, options);
532
+ };
533
+
534
+ Shape.prototype.makeGeometry = function (options) {
535
+ console.warn('THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.');
536
+ return new ShapeGeometry(this, options);
537
+ };
538
+
539
+ //
540
+
541
+ Vector2.prototype.fromAttribute = function (attribute, index, offset) {
542
+ console.warn('THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().');
543
+ return this.fromBufferAttribute(attribute, index, offset);
544
+ };
545
+
546
+ Vector2.prototype.distanceToManhattan = function (v) {
547
+ console.warn('THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().');
548
+ return this.manhattanDistanceTo(v);
549
+ };
550
+
551
+ Vector2.prototype.lengthManhattan = function () {
552
+ console.warn('THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().');
553
+ return this.manhattanLength();
554
+ };
555
+
556
+ //
557
+
558
+ Vector3.prototype.setEulerFromRotationMatrix = function () {
559
+ console.error('THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.');
560
+ };
561
+
562
+ Vector3.prototype.setEulerFromQuaternion = function () {
563
+ console.error('THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.');
564
+ };
565
+
566
+ Vector3.prototype.getPositionFromMatrix = function (m) {
567
+ console.warn('THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().');
568
+ return this.setFromMatrixPosition(m);
569
+ };
570
+
571
+ Vector3.prototype.getScaleFromMatrix = function (m) {
572
+ console.warn('THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().');
573
+ return this.setFromMatrixScale(m);
574
+ };
575
+
576
+ Vector3.prototype.getColumnFromMatrix = function (index, matrix) {
577
+ console.warn('THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().');
578
+ return this.setFromMatrixColumn(matrix, index);
579
+ };
580
+
581
+ Vector3.prototype.applyProjection = function (m) {
582
+ console.warn('THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.');
583
+ return this.applyMatrix4(m);
584
+ };
585
+
586
+ Vector3.prototype.fromAttribute = function (attribute, index, offset) {
587
+ console.warn('THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().');
588
+ return this.fromBufferAttribute(attribute, index, offset);
589
+ };
590
+
591
+ Vector3.prototype.distanceToManhattan = function (v) {
592
+ console.warn('THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().');
593
+ return this.manhattanDistanceTo(v);
594
+ };
595
+
596
+ Vector3.prototype.lengthManhattan = function () {
597
+ console.warn('THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().');
598
+ return this.manhattanLength();
599
+ };
600
+
601
+ //
602
+
603
+ Vector4.prototype.fromAttribute = function (attribute, index, offset) {
604
+ console.warn('THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().');
605
+ return this.fromBufferAttribute(attribute, index, offset);
606
+ };
607
+
608
+ Vector4.prototype.lengthManhattan = function () {
609
+ console.warn('THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().');
610
+ return this.manhattanLength();
611
+ };
612
+
613
+ //
614
+
615
+ Object3D.prototype.getChildByName = function (name) {
616
+ console.warn('THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().');
617
+ return this.getObjectByName(name);
618
+ };
619
+
620
+ Object3D.prototype.renderDepth = function () {
621
+ console.warn('THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.');
622
+ };
623
+
624
+ Object3D.prototype.translate = function (distance, axis) {
625
+ console.warn('THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.');
626
+ return this.translateOnAxis(axis, distance);
627
+ };
628
+
629
+ Object3D.prototype.getWorldRotation = function () {
630
+ console.error('THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.');
631
+ };
632
+
633
+ Object3D.prototype.applyMatrix = function (matrix) {
634
+ console.warn('THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4().');
635
+ return this.applyMatrix4(matrix);
636
+ };
637
+
638
+ Object.defineProperties(Object3D.prototype, {
639
+ eulerOrder: {
640
+ get: function () {
641
+ console.warn('THREE.Object3D: .eulerOrder is now .rotation.order.');
642
+ return this.rotation.order;
643
+ },
644
+ set: function (value) {
645
+ console.warn('THREE.Object3D: .eulerOrder is now .rotation.order.');
646
+ this.rotation.order = value;
647
+ },
648
+ },
649
+ useQuaternion: {
650
+ get: function () {
651
+ console.warn('THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.');
652
+ },
653
+ set: function () {
654
+ console.warn('THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.');
655
+ },
656
+ },
657
+ });
658
+
659
+ Mesh.prototype.setDrawMode = function () {
660
+ console.error(
661
+ 'THREE.Mesh: .setDrawMode() has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.'
662
+ );
663
+ };
664
+
665
+ Object.defineProperties(Mesh.prototype, {
666
+ drawMode: {
667
+ get: function () {
668
+ console.error('THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode.');
669
+ return TrianglesDrawMode;
670
+ },
671
+ set: function () {
672
+ console.error(
673
+ 'THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.'
674
+ );
675
+ },
676
+ },
677
+ });
678
+
679
+ SkinnedMesh.prototype.initBones = function () {
680
+ console.error('THREE.SkinnedMesh: initBones() has been removed.');
681
+ };
682
+
683
+ //
684
+
685
+ PerspectiveCamera.prototype.setLens = function (focalLength, filmGauge) {
686
+ console.warn('THREE.PerspectiveCamera.setLens is deprecated. ' + 'Use .setFocalLength and .filmGauge for a photographic setup.');
687
+
688
+ if (filmGauge !== undefined) this.filmGauge = filmGauge;
689
+ this.setFocalLength(focalLength);
690
+ };
691
+
692
+ //
693
+
694
+ Object.defineProperties(Light.prototype, {
695
+ onlyShadow: {
696
+ set: function () {
697
+ console.warn('THREE.Light: .onlyShadow has been removed.');
698
+ },
699
+ },
700
+ shadowCameraFov: {
701
+ set: function (value) {
702
+ console.warn('THREE.Light: .shadowCameraFov is now .shadow.camera.fov.');
703
+ this.shadow.camera.fov = value;
704
+ },
705
+ },
706
+ shadowCameraLeft: {
707
+ set: function (value) {
708
+ console.warn('THREE.Light: .shadowCameraLeft is now .shadow.camera.left.');
709
+ this.shadow.camera.left = value;
710
+ },
711
+ },
712
+ shadowCameraRight: {
713
+ set: function (value) {
714
+ console.warn('THREE.Light: .shadowCameraRight is now .shadow.camera.right.');
715
+ this.shadow.camera.right = value;
716
+ },
717
+ },
718
+ shadowCameraTop: {
719
+ set: function (value) {
720
+ console.warn('THREE.Light: .shadowCameraTop is now .shadow.camera.top.');
721
+ this.shadow.camera.top = value;
722
+ },
723
+ },
724
+ shadowCameraBottom: {
725
+ set: function (value) {
726
+ console.warn('THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.');
727
+ this.shadow.camera.bottom = value;
728
+ },
729
+ },
730
+ shadowCameraNear: {
731
+ set: function (value) {
732
+ console.warn('THREE.Light: .shadowCameraNear is now .shadow.camera.near.');
733
+ this.shadow.camera.near = value;
734
+ },
735
+ },
736
+ shadowCameraFar: {
737
+ set: function (value) {
738
+ console.warn('THREE.Light: .shadowCameraFar is now .shadow.camera.far.');
739
+ this.shadow.camera.far = value;
740
+ },
741
+ },
742
+ shadowCameraVisible: {
743
+ set: function () {
744
+ console.warn('THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.');
745
+ },
746
+ },
747
+ shadowBias: {
748
+ set: function (value) {
749
+ console.warn('THREE.Light: .shadowBias is now .shadow.bias.');
750
+ this.shadow.bias = value;
751
+ },
752
+ },
753
+ shadowDarkness: {
754
+ set: function () {
755
+ console.warn('THREE.Light: .shadowDarkness has been removed.');
756
+ },
757
+ },
758
+ shadowMapWidth: {
759
+ set: function (value) {
760
+ console.warn('THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.');
761
+ this.shadow.mapSize.width = value;
762
+ },
763
+ },
764
+ shadowMapHeight: {
765
+ set: function (value) {
766
+ console.warn('THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.');
767
+ this.shadow.mapSize.height = value;
768
+ },
769
+ },
770
+ });
771
+
772
+ //
773
+
774
+ Object.defineProperties(BufferAttribute.prototype, {
775
+ length: {
776
+ get: function () {
777
+ console.warn('THREE.BufferAttribute: .length has been deprecated. Use .count instead.');
778
+ return this.array.length;
779
+ },
780
+ },
781
+ dynamic: {
782
+ get: function () {
783
+ console.warn('THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.');
784
+ return this.usage === DynamicDrawUsage;
785
+ },
786
+ set: function (/* value */) {
787
+ console.warn('THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.');
788
+ this.setUsage(DynamicDrawUsage);
789
+ },
790
+ },
791
+ });
792
+
793
+ BufferAttribute.prototype.setDynamic = function (value) {
794
+ console.warn('THREE.BufferAttribute: .setDynamic() has been deprecated. Use .setUsage() instead.');
795
+ this.setUsage(value === true ? DynamicDrawUsage : StaticDrawUsage);
796
+ return this;
797
+ };
798
+
799
+ (BufferAttribute.prototype.copyIndicesArray = function (/* indices */) {
800
+ console.error('THREE.BufferAttribute: .copyIndicesArray() has been removed.');
801
+ }),
802
+ (BufferAttribute.prototype.setArray = function (/* array */) {
803
+ console.error('THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers');
804
+ });
805
+
806
+ //
807
+
808
+ BufferGeometry.prototype.addIndex = function (index) {
809
+ console.warn('THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().');
810
+ this.setIndex(index);
811
+ };
812
+
813
+ BufferGeometry.prototype.addAttribute = function (name, attribute) {
814
+ console.warn('THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute().');
815
+
816
+ if (!(attribute && attribute.isBufferAttribute) && !(attribute && attribute.isInterleavedBufferAttribute)) {
817
+ console.warn('THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).');
818
+
819
+ return this.setAttribute(name, new BufferAttribute(arguments[1], arguments[2]));
820
+ }
821
+
822
+ if (name === 'index') {
823
+ console.warn('THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.');
824
+ this.setIndex(attribute);
825
+
826
+ return this;
827
+ }
828
+
829
+ return this.setAttribute(name, attribute);
830
+ };
831
+
832
+ BufferGeometry.prototype.addDrawCall = function (start, count, indexOffset) {
833
+ if (indexOffset !== undefined) {
834
+ console.warn('THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.');
835
+ }
836
+
837
+ console.warn('THREE.BufferGeometry: .addDrawCall() is now .addGroup().');
838
+ this.addGroup(start, count);
839
+ };
840
+
841
+ BufferGeometry.prototype.clearDrawCalls = function () {
842
+ console.warn('THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().');
843
+ this.clearGroups();
844
+ };
845
+
846
+ BufferGeometry.prototype.computeOffsets = function () {
847
+ console.warn('THREE.BufferGeometry: .computeOffsets() has been removed.');
848
+ };
849
+
850
+ BufferGeometry.prototype.removeAttribute = function (name) {
851
+ console.warn('THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute().');
852
+
853
+ return this.deleteAttribute(name);
854
+ };
855
+
856
+ BufferGeometry.prototype.applyMatrix = function (matrix) {
857
+ console.warn('THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4().');
858
+ return this.applyMatrix4(matrix);
859
+ };
860
+
861
+ Object.defineProperties(BufferGeometry.prototype, {
862
+ drawcalls: {
863
+ get: function () {
864
+ console.error('THREE.BufferGeometry: .drawcalls has been renamed to .groups.');
865
+ return this.groups;
866
+ },
867
+ },
868
+ offsets: {
869
+ get: function () {
870
+ console.warn('THREE.BufferGeometry: .offsets has been renamed to .groups.');
871
+ return this.groups;
872
+ },
873
+ },
874
+ });
875
+
876
+ InterleavedBuffer.prototype.setDynamic = function (value) {
877
+ console.warn('THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead.');
878
+ this.setUsage(value === true ? DynamicDrawUsage : StaticDrawUsage);
879
+ return this;
880
+ };
881
+
882
+ InterleavedBuffer.prototype.setArray = function (/* array */) {
883
+ console.error('THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers');
884
+ };
885
+
886
+ //
887
+
888
+ ExtrudeGeometry.prototype.getArrays = function () {
889
+ console.error('THREE.ExtrudeGeometry: .getArrays() has been removed.');
890
+ };
891
+
892
+ ExtrudeGeometry.prototype.addShapeList = function () {
893
+ console.error('THREE.ExtrudeGeometry: .addShapeList() has been removed.');
894
+ };
895
+
896
+ ExtrudeGeometry.prototype.addShape = function () {
897
+ console.error('THREE.ExtrudeGeometry: .addShape() has been removed.');
898
+ };
899
+
900
+ //
901
+
902
+ Scene.prototype.dispose = function () {
903
+ console.error('THREE.Scene: .dispose() has been removed.');
904
+ };
905
+
906
+ //
907
+
908
+ Uniform.prototype.onUpdate = function () {
909
+ console.warn('THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.');
910
+ return this;
911
+ };
912
+
913
+ //
914
+
915
+ Object.defineProperties(Material.prototype, {
916
+ wrapAround: {
917
+ get: function () {
918
+ console.warn('THREE.Material: .wrapAround has been removed.');
919
+ },
920
+ set: function () {
921
+ console.warn('THREE.Material: .wrapAround has been removed.');
922
+ },
923
+ },
924
+
925
+ overdraw: {
926
+ get: function () {
927
+ console.warn('THREE.Material: .overdraw has been removed.');
928
+ },
929
+ set: function () {
930
+ console.warn('THREE.Material: .overdraw has been removed.');
931
+ },
932
+ },
933
+
934
+ wrapRGB: {
935
+ get: function () {
936
+ console.warn('THREE.Material: .wrapRGB has been removed.');
937
+ return new Color();
938
+ },
939
+ },
940
+
941
+ shading: {
942
+ get: function () {
943
+ console.error('THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.');
944
+ },
945
+ set: function (value) {
946
+ console.warn('THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.');
947
+ this.flatShading = value === FlatShading;
948
+ },
949
+ },
950
+
951
+ stencilMask: {
952
+ get: function () {
953
+ console.warn('THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.');
954
+ return this.stencilFuncMask;
955
+ },
956
+ set: function (value) {
957
+ console.warn('THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.');
958
+ this.stencilFuncMask = value;
959
+ },
960
+ },
961
+
962
+ vertexTangents: {
963
+ get: function () {
964
+ console.warn('THREE.' + this.type + ': .vertexTangents has been removed.');
965
+ },
966
+ set: function () {
967
+ console.warn('THREE.' + this.type + ': .vertexTangents has been removed.');
968
+ },
969
+ },
970
+ });
971
+
972
+ Object.defineProperties(ShaderMaterial.prototype, {
973
+ derivatives: {
974
+ get: function () {
975
+ console.warn('THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.');
976
+ return this.extensions.derivatives;
977
+ },
978
+ set: function (value) {
979
+ console.warn('THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.');
980
+ this.extensions.derivatives = value;
981
+ },
982
+ },
983
+ });
984
+
985
+ //
986
+
987
+ WebGLRenderer.prototype.clearTarget = function (renderTarget, color, depth, stencil) {
988
+ console.warn('THREE.WebGLRenderer: .clearTarget() has been deprecated. Use .setRenderTarget() and .clear() instead.');
989
+ this.setRenderTarget(renderTarget);
990
+ this.clear(color, depth, stencil);
991
+ };
992
+
993
+ WebGLRenderer.prototype.animate = function (callback) {
994
+ console.warn('THREE.WebGLRenderer: .animate() is now .setAnimationLoop().');
995
+ this.setAnimationLoop(callback);
996
+ };
997
+
998
+ WebGLRenderer.prototype.getCurrentRenderTarget = function () {
999
+ console.warn('THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().');
1000
+ return this.getRenderTarget();
1001
+ };
1002
+
1003
+ WebGLRenderer.prototype.getMaxAnisotropy = function () {
1004
+ console.warn('THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().');
1005
+ return this.capabilities.getMaxAnisotropy();
1006
+ };
1007
+
1008
+ WebGLRenderer.prototype.getPrecision = function () {
1009
+ console.warn('THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.');
1010
+ return this.capabilities.precision;
1011
+ };
1012
+
1013
+ WebGLRenderer.prototype.resetGLState = function () {
1014
+ console.warn('THREE.WebGLRenderer: .resetGLState() is now .state.reset().');
1015
+ return this.state.reset();
1016
+ };
1017
+
1018
+ WebGLRenderer.prototype.supportsFloatTextures = function () {
1019
+ console.warn("THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( 'OES_texture_float' ).");
1020
+ return this.extensions.get('OES_texture_float');
1021
+ };
1022
+
1023
+ WebGLRenderer.prototype.supportsHalfFloatTextures = function () {
1024
+ console.warn("THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( 'OES_texture_half_float' ).");
1025
+ return this.extensions.get('OES_texture_half_float');
1026
+ };
1027
+
1028
+ WebGLRenderer.prototype.supportsStandardDerivatives = function () {
1029
+ console.warn("THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( 'OES_standard_derivatives' ).");
1030
+ return this.extensions.get('OES_standard_derivatives');
1031
+ };
1032
+
1033
+ WebGLRenderer.prototype.supportsCompressedTextureS3TC = function () {
1034
+ console.warn("THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( 'WEBGL_compressed_texture_s3tc' ).");
1035
+ return this.extensions.get('WEBGL_compressed_texture_s3tc');
1036
+ };
1037
+
1038
+ WebGLRenderer.prototype.supportsCompressedTexturePVRTC = function () {
1039
+ console.warn("THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( 'WEBGL_compressed_texture_pvrtc' ).");
1040
+ return this.extensions.get('WEBGL_compressed_texture_pvrtc');
1041
+ };
1042
+
1043
+ WebGLRenderer.prototype.supportsBlendMinMax = function () {
1044
+ console.warn("THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( 'EXT_blend_minmax' ).");
1045
+ return this.extensions.get('EXT_blend_minmax');
1046
+ };
1047
+
1048
+ WebGLRenderer.prototype.supportsVertexTextures = function () {
1049
+ console.warn('THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.');
1050
+ return this.capabilities.vertexTextures;
1051
+ };
1052
+
1053
+ WebGLRenderer.prototype.supportsInstancedArrays = function () {
1054
+ console.warn("THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( 'ANGLE_instanced_arrays' ).");
1055
+ return this.extensions.get('ANGLE_instanced_arrays');
1056
+ };
1057
+
1058
+ WebGLRenderer.prototype.enableScissorTest = function (boolean) {
1059
+ console.warn('THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().');
1060
+ this.setScissorTest(boolean);
1061
+ };
1062
+
1063
+ WebGLRenderer.prototype.initMaterial = function () {
1064
+ console.warn('THREE.WebGLRenderer: .initMaterial() has been removed.');
1065
+ };
1066
+
1067
+ WebGLRenderer.prototype.addPrePlugin = function () {
1068
+ console.warn('THREE.WebGLRenderer: .addPrePlugin() has been removed.');
1069
+ };
1070
+
1071
+ WebGLRenderer.prototype.addPostPlugin = function () {
1072
+ console.warn('THREE.WebGLRenderer: .addPostPlugin() has been removed.');
1073
+ };
1074
+
1075
+ WebGLRenderer.prototype.updateShadowMap = function () {
1076
+ console.warn('THREE.WebGLRenderer: .updateShadowMap() has been removed.');
1077
+ };
1078
+
1079
+ WebGLRenderer.prototype.setFaceCulling = function () {
1080
+ console.warn('THREE.WebGLRenderer: .setFaceCulling() has been removed.');
1081
+ };
1082
+
1083
+ WebGLRenderer.prototype.allocTextureUnit = function () {
1084
+ console.warn('THREE.WebGLRenderer: .allocTextureUnit() has been removed.');
1085
+ };
1086
+
1087
+ WebGLRenderer.prototype.setTexture = function () {
1088
+ console.warn('THREE.WebGLRenderer: .setTexture() has been removed.');
1089
+ };
1090
+
1091
+ WebGLRenderer.prototype.setTexture2D = function () {
1092
+ console.warn('THREE.WebGLRenderer: .setTexture2D() has been removed.');
1093
+ };
1094
+
1095
+ WebGLRenderer.prototype.setTextureCube = function () {
1096
+ console.warn('THREE.WebGLRenderer: .setTextureCube() has been removed.');
1097
+ };
1098
+
1099
+ WebGLRenderer.prototype.getActiveMipMapLevel = function () {
1100
+ console.warn('THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel().');
1101
+ return this.getActiveMipmapLevel();
1102
+ };
1103
+
1104
+ Object.defineProperties(WebGLRenderer.prototype, {
1105
+ shadowMapEnabled: {
1106
+ get: function () {
1107
+ return this.shadowMap.enabled;
1108
+ },
1109
+ set: function (value) {
1110
+ console.warn('THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.');
1111
+ this.shadowMap.enabled = value;
1112
+ },
1113
+ },
1114
+ shadowMapType: {
1115
+ get: function () {
1116
+ return this.shadowMap.type;
1117
+ },
1118
+ set: function (value) {
1119
+ console.warn('THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.');
1120
+ this.shadowMap.type = value;
1121
+ },
1122
+ },
1123
+ shadowMapCullFace: {
1124
+ get: function () {
1125
+ console.warn('THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.');
1126
+ return undefined;
1127
+ },
1128
+ set: function (/* value */) {
1129
+ console.warn('THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.');
1130
+ },
1131
+ },
1132
+ context: {
1133
+ get: function () {
1134
+ console.warn('THREE.WebGLRenderer: .context has been removed. Use .getContext() instead.');
1135
+ return this.getContext();
1136
+ },
1137
+ },
1138
+ vr: {
1139
+ get: function () {
1140
+ console.warn('THREE.WebGLRenderer: .vr has been renamed to .xr');
1141
+ return this.xr;
1142
+ },
1143
+ },
1144
+ gammaInput: {
1145
+ get: function () {
1146
+ console.warn('THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.');
1147
+ return false;
1148
+ },
1149
+ set: function () {
1150
+ console.warn('THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.');
1151
+ },
1152
+ },
1153
+ gammaOutput: {
1154
+ get: function () {
1155
+ console.warn('THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.');
1156
+ return false;
1157
+ },
1158
+ set: function (value) {
1159
+ console.warn('THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.');
1160
+ this.outputEncoding = value === true ? sRGBEncoding : LinearEncoding;
1161
+ },
1162
+ },
1163
+ toneMappingWhitePoint: {
1164
+ get: function () {
1165
+ console.warn('THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.');
1166
+ return 1.0;
1167
+ },
1168
+ set: function () {
1169
+ console.warn('THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.');
1170
+ },
1171
+ },
1172
+ gammaFactor: {
1173
+ get: function () {
1174
+ console.warn('THREE.WebGLRenderer: .gammaFactor has been removed.');
1175
+ return 2;
1176
+ },
1177
+ set: function () {
1178
+ console.warn('THREE.WebGLRenderer: .gammaFactor has been removed.');
1179
+ },
1180
+ },
1181
+ });
1182
+
1183
+ Object.defineProperties(WebGLShadowMap.prototype, {
1184
+ cullFace: {
1185
+ get: function () {
1186
+ console.warn('THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.');
1187
+ return undefined;
1188
+ },
1189
+ set: function (/* cullFace */) {
1190
+ console.warn('THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.');
1191
+ },
1192
+ },
1193
+ renderReverseSided: {
1194
+ get: function () {
1195
+ console.warn('THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.');
1196
+ return undefined;
1197
+ },
1198
+ set: function () {
1199
+ console.warn('THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.');
1200
+ },
1201
+ },
1202
+ renderSingleSided: {
1203
+ get: function () {
1204
+ console.warn('THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.');
1205
+ return undefined;
1206
+ },
1207
+ set: function () {
1208
+ console.warn('THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.');
1209
+ },
1210
+ },
1211
+ });
1212
+
1213
+ export function WebGLRenderTargetCube(width, height, options) {
1214
+ console.warn('THREE.WebGLRenderTargetCube( width, height, options ) is now WebGLCubeRenderTarget( size, options ).');
1215
+ return new WebGLCubeRenderTarget(width, options);
1216
+ }
1217
+
1218
+ //
1219
+
1220
+ Object.defineProperties(WebGLRenderTarget.prototype, {
1221
+ wrapS: {
1222
+ get: function () {
1223
+ console.warn('THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.');
1224
+ return this.texture.wrapS;
1225
+ },
1226
+ set: function (value) {
1227
+ console.warn('THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.');
1228
+ this.texture.wrapS = value;
1229
+ },
1230
+ },
1231
+ wrapT: {
1232
+ get: function () {
1233
+ console.warn('THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.');
1234
+ return this.texture.wrapT;
1235
+ },
1236
+ set: function (value) {
1237
+ console.warn('THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.');
1238
+ this.texture.wrapT = value;
1239
+ },
1240
+ },
1241
+ magFilter: {
1242
+ get: function () {
1243
+ console.warn('THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.');
1244
+ return this.texture.magFilter;
1245
+ },
1246
+ set: function (value) {
1247
+ console.warn('THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.');
1248
+ this.texture.magFilter = value;
1249
+ },
1250
+ },
1251
+ minFilter: {
1252
+ get: function () {
1253
+ console.warn('THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.');
1254
+ return this.texture.minFilter;
1255
+ },
1256
+ set: function (value) {
1257
+ console.warn('THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.');
1258
+ this.texture.minFilter = value;
1259
+ },
1260
+ },
1261
+ anisotropy: {
1262
+ get: function () {
1263
+ console.warn('THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.');
1264
+ return this.texture.anisotropy;
1265
+ },
1266
+ set: function (value) {
1267
+ console.warn('THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.');
1268
+ this.texture.anisotropy = value;
1269
+ },
1270
+ },
1271
+ offset: {
1272
+ get: function () {
1273
+ console.warn('THREE.WebGLRenderTarget: .offset is now .texture.offset.');
1274
+ return this.texture.offset;
1275
+ },
1276
+ set: function (value) {
1277
+ console.warn('THREE.WebGLRenderTarget: .offset is now .texture.offset.');
1278
+ this.texture.offset = value;
1279
+ },
1280
+ },
1281
+ repeat: {
1282
+ get: function () {
1283
+ console.warn('THREE.WebGLRenderTarget: .repeat is now .texture.repeat.');
1284
+ return this.texture.repeat;
1285
+ },
1286
+ set: function (value) {
1287
+ console.warn('THREE.WebGLRenderTarget: .repeat is now .texture.repeat.');
1288
+ this.texture.repeat = value;
1289
+ },
1290
+ },
1291
+ format: {
1292
+ get: function () {
1293
+ console.warn('THREE.WebGLRenderTarget: .format is now .texture.format.');
1294
+ return this.texture.format;
1295
+ },
1296
+ set: function (value) {
1297
+ console.warn('THREE.WebGLRenderTarget: .format is now .texture.format.');
1298
+ this.texture.format = value;
1299
+ },
1300
+ },
1301
+ type: {
1302
+ get: function () {
1303
+ console.warn('THREE.WebGLRenderTarget: .type is now .texture.type.');
1304
+ return this.texture.type;
1305
+ },
1306
+ set: function (value) {
1307
+ console.warn('THREE.WebGLRenderTarget: .type is now .texture.type.');
1308
+ this.texture.type = value;
1309
+ },
1310
+ },
1311
+ generateMipmaps: {
1312
+ get: function () {
1313
+ console.warn('THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.');
1314
+ return this.texture.generateMipmaps;
1315
+ },
1316
+ set: function (value) {
1317
+ console.warn('THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.');
1318
+ this.texture.generateMipmaps = value;
1319
+ },
1320
+ },
1321
+ });
1322
+
1323
+ //
1324
+
1325
+ Audio.prototype.load = function (file) {
1326
+ console.warn('THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.');
1327
+ const scope = this;
1328
+ const audioLoader = new AudioLoader();
1329
+ audioLoader.load(file, function (buffer) {
1330
+ scope.setBuffer(buffer);
1331
+ });
1332
+ return this;
1333
+ };
1334
+
1335
+ AudioAnalyser.prototype.getData = function () {
1336
+ console.warn('THREE.AudioAnalyser: .getData() is now .getFrequencyData().');
1337
+ return this.getFrequencyData();
1338
+ };
1339
+
1340
+ //
1341
+
1342
+ CubeCamera.prototype.updateCubeMap = function (renderer, scene) {
1343
+ console.warn('THREE.CubeCamera: .updateCubeMap() is now .update().');
1344
+ return this.update(renderer, scene);
1345
+ };
1346
+
1347
+ CubeCamera.prototype.clear = function (renderer, color, depth, stencil) {
1348
+ console.warn('THREE.CubeCamera: .clear() is now .renderTarget.clear().');
1349
+ return this.renderTarget.clear(renderer, color, depth, stencil);
1350
+ };
1351
+
1352
+ ImageUtils.crossOrigin = undefined;
1353
+
1354
+ ImageUtils.loadTexture = function (url, mapping, onLoad, onError) {
1355
+ console.warn('THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.');
1356
+
1357
+ const loader = new TextureLoader();
1358
+ loader.setCrossOrigin(this.crossOrigin);
1359
+
1360
+ const texture = loader.load(url, onLoad, undefined, onError);
1361
+
1362
+ if (mapping) texture.mapping = mapping;
1363
+
1364
+ return texture;
1365
+ };
1366
+
1367
+ ImageUtils.loadTextureCube = function (urls, mapping, onLoad, onError) {
1368
+ console.warn('THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.');
1369
+
1370
+ const loader = new CubeTextureLoader();
1371
+ loader.setCrossOrigin(this.crossOrigin);
1372
+
1373
+ const texture = loader.load(urls, onLoad, undefined, onError);
1374
+
1375
+ if (mapping) texture.mapping = mapping;
1376
+
1377
+ return texture;
1378
+ };
1379
+
1380
+ ImageUtils.loadCompressedTexture = function () {
1381
+ console.error('THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.');
1382
+ };
1383
+
1384
+ ImageUtils.loadCompressedTextureCube = function () {
1385
+ console.error('THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.');
1386
+ };
1387
+
1388
+ //
1389
+
1390
+ export function CanvasRenderer() {
1391
+ console.error('THREE.CanvasRenderer has been removed');
1392
+ }
1393
+
1394
+ //
1395
+
1396
+ export function JSONLoader() {
1397
+ console.error('THREE.JSONLoader has been removed.');
1398
+ }
1399
+
1400
+ //
1401
+
1402
+ export const SceneUtils = {
1403
+ createMultiMaterialObject: function (/* geometry, materials */) {
1404
+ console.error('THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js');
1405
+ },
1406
+
1407
+ detach: function (/* child, parent, scene */) {
1408
+ console.error('THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js');
1409
+ },
1410
+
1411
+ attach: function (/* child, scene, parent */) {
1412
+ console.error('THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js');
1413
+ },
1414
+ };
1415
+
1416
+ //
1417
+
1418
+ export function LensFlare() {
1419
+ console.error('THREE.LensFlare has been moved to /examples/jsm/objects/Lensflare.js');
1420
+ }
1421
+
1422
+ //
1423
+
1424
+ export function ParametricGeometry() {
1425
+ console.error('THREE.ParametricGeometry has been moved to /examples/jsm/geometries/ParametricGeometry.js');
1426
+ return new BufferGeometry();
1427
+ }
1428
+
1429
+ export function TextGeometry() {
1430
+ console.error('THREE.TextGeometry has been moved to /examples/jsm/geometries/TextGeometry.js');
1431
+ return new BufferGeometry();
1432
+ }
1433
+
1434
+ export function FontLoader() {
1435
+ console.error('THREE.FontLoader has been moved to /examples/jsm/loaders/FontLoader.js');
1436
+ }
1437
+
1438
+ export function Font() {
1439
+ console.error('THREE.Font has been moved to /examples/jsm/loaders/FontLoader.js');
1440
+ }
1441
+
1442
+ export function ImmediateRenderObject() {
1443
+ console.error('THREE.ImmediateRenderObject has been removed.');
1444
+ }
backend/libs/three/Three.d.ts ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * SRC
3
+ */
4
+ export * from './constants';
5
+ export * from './Three.Legacy';
6
+ export * from './utils';
7
+ /**
8
+ * Animation
9
+ */
10
+ export * from './animation/tracks/VectorKeyframeTrack';
11
+ export * from './animation/tracks/StringKeyframeTrack';
12
+ export * from './animation/tracks/QuaternionKeyframeTrack';
13
+ export * from './animation/tracks/NumberKeyframeTrack';
14
+ export * from './animation/tracks/ColorKeyframeTrack';
15
+ export * from './animation/tracks/BooleanKeyframeTrack';
16
+ export * from './animation/PropertyMixer';
17
+ export * from './animation/PropertyBinding';
18
+ export * from './animation/KeyframeTrack';
19
+ export * from './animation/AnimationUtils';
20
+ export * from './animation/AnimationObjectGroup';
21
+ export * from './animation/AnimationMixer';
22
+ export * from './animation/AnimationClip';
23
+ export * from './animation/AnimationAction';
24
+ /**
25
+ * Audio
26
+ */
27
+ export * from './audio/AudioListener';
28
+ export * from './audio/PositionalAudio';
29
+ export * from './audio/AudioContext';
30
+ export * from './audio/AudioAnalyser';
31
+ export * from './audio/Audio';
32
+ /**
33
+ * Cameras
34
+ */
35
+ export * from './cameras/StereoCamera';
36
+ export * from './cameras/PerspectiveCamera';
37
+ export * from './cameras/OrthographicCamera';
38
+ export * from './cameras/CubeCamera';
39
+ export * from './cameras/ArrayCamera';
40
+ export * from './cameras/Camera';
41
+ /**
42
+ * Core
43
+ */
44
+ export * from './core/Uniform';
45
+ export * from './core/InstancedBufferGeometry';
46
+ export * from './core/BufferGeometry';
47
+ export * from './core/InterleavedBufferAttribute';
48
+ export * from './core/InstancedInterleavedBuffer';
49
+ export * from './core/InterleavedBuffer';
50
+ export * from './core/InstancedBufferAttribute';
51
+ export * from './core/GLBufferAttribute';
52
+ export * from './core/BufferAttribute';
53
+ export * from './core/Object3D';
54
+ export * from './core/Raycaster';
55
+ export * from './core/Layers';
56
+ export * from './core/EventDispatcher';
57
+ export * from './core/Clock';
58
+ /**
59
+ * Extras
60
+ */
61
+ export * from './extras/curves/Curves';
62
+ export * from './extras/core/Shape';
63
+ export * from './extras/core/Path';
64
+ export * from './extras/core/ShapePath';
65
+ export * from './extras/core/CurvePath';
66
+ export * from './extras/core/Curve';
67
+ export * from './extras/DataUtils';
68
+ export * from './extras/ImageUtils';
69
+ export * from './extras/ShapeUtils';
70
+ export * from './extras/PMREMGenerator';
71
+ /**
72
+ * Geometries
73
+ */
74
+ export * from './geometries/Geometries';
75
+ /**
76
+ * Helpers
77
+ */
78
+ export * from './helpers/SpotLightHelper';
79
+ export * from './helpers/SkeletonHelper';
80
+ export * from './helpers/PointLightHelper';
81
+ export * from './helpers/HemisphereLightHelper';
82
+ export * from './helpers/GridHelper';
83
+ export * from './helpers/PolarGridHelper';
84
+ export * from './helpers/DirectionalLightHelper';
85
+ export * from './helpers/CameraHelper';
86
+ export * from './helpers/BoxHelper';
87
+ export * from './helpers/Box3Helper';
88
+ export * from './helpers/PlaneHelper';
89
+ export * from './helpers/ArrowHelper';
90
+ export * from './helpers/AxesHelper';
91
+ /**
92
+ * Lights
93
+ */
94
+ export * from './lights/SpotLightShadow';
95
+ export * from './lights/SpotLight';
96
+ export * from './lights/PointLight';
97
+ export * from './lights/PointLightShadow';
98
+ export * from './lights/RectAreaLight';
99
+ export * from './lights/HemisphereLight';
100
+ export * from './lights/DirectionalLightShadow';
101
+ export * from './lights/DirectionalLight';
102
+ export * from './lights/AmbientLight';
103
+ export * from './lights/LightShadow';
104
+ export * from './lights/Light';
105
+ export * from './lights/AmbientLightProbe';
106
+ export * from './lights/HemisphereLightProbe';
107
+ export * from './lights/LightProbe';
108
+ /**
109
+ * Loaders
110
+ */
111
+ export * from './loaders/AnimationLoader';
112
+ export * from './loaders/CompressedTextureLoader';
113
+ export * from './loaders/DataTextureLoader';
114
+ export * from './loaders/CubeTextureLoader';
115
+ export * from './loaders/TextureLoader';
116
+ export * from './loaders/ObjectLoader';
117
+ export * from './loaders/MaterialLoader';
118
+ export * from './loaders/BufferGeometryLoader';
119
+ export * from './loaders/LoadingManager';
120
+ export * from './loaders/ImageLoader';
121
+ export * from './loaders/ImageBitmapLoader';
122
+ export * from './loaders/FileLoader';
123
+ export * from './loaders/Loader';
124
+ export * from './loaders/LoaderUtils';
125
+ export * from './loaders/Cache';
126
+ export * from './loaders/AudioLoader';
127
+ /**
128
+ * Materials
129
+ */
130
+ export * from './materials/Materials';
131
+ /**
132
+ * Math
133
+ */
134
+ export * from './math/interpolants/QuaternionLinearInterpolant';
135
+ export * from './math/interpolants/LinearInterpolant';
136
+ export * from './math/interpolants/DiscreteInterpolant';
137
+ export * from './math/interpolants/CubicInterpolant';
138
+ export * from './math/Interpolant';
139
+ export * from './math/Triangle';
140
+ export * from './math/Spherical';
141
+ export * from './math/Cylindrical';
142
+ export * from './math/Plane';
143
+ export * from './math/Frustum';
144
+ export * from './math/Sphere';
145
+ export * from './math/Ray';
146
+ export * from './math/Matrix4';
147
+ export * from './math/Matrix3';
148
+ export * from './math/Box3';
149
+ export * from './math/Box2';
150
+ export * from './math/Line3';
151
+ export * from './math/Euler';
152
+ export * from './math/Vector4';
153
+ export * from './math/Vector3';
154
+ export * from './math/Vector2';
155
+ export * from './math/Quaternion';
156
+ export * from './math/Color';
157
+ export * from './math/SphericalHarmonics3';
158
+ import * as MathUtils from './math/MathUtils';
159
+ export { MathUtils };
160
+ /**
161
+ * Objects
162
+ */
163
+ export * from './objects/Sprite';
164
+ export * from './objects/LOD';
165
+ export * from './objects/InstancedMesh';
166
+ export * from './objects/SkinnedMesh';
167
+ export * from './objects/Skeleton';
168
+ export * from './objects/Bone';
169
+ export * from './objects/Mesh';
170
+ export * from './objects/LineSegments';
171
+ export * from './objects/LineLoop';
172
+ export * from './objects/Line';
173
+ export * from './objects/Points';
174
+ export * from './objects/Group';
175
+ /**
176
+ * Renderers
177
+ */
178
+ export * from './renderers/WebGLMultisampleRenderTarget';
179
+ export * from './renderers/WebGLCubeRenderTarget';
180
+ export * from './renderers/WebGLMultipleRenderTargets';
181
+ export * from './renderers/WebGLRenderTarget';
182
+ export * from './renderers/WebGLRenderer';
183
+ export * from './renderers/WebGL1Renderer';
184
+ export * from './renderers/WebGL3DRenderTarget';
185
+ export * from './renderers/WebGLArrayRenderTarget';
186
+ export * from './renderers/shaders/ShaderLib';
187
+ export * from './renderers/shaders/UniformsLib';
188
+ export * from './renderers/shaders/UniformsUtils';
189
+ export * from './renderers/shaders/ShaderChunk';
190
+ export * from './renderers/webgl/WebGLBufferRenderer';
191
+ export * from './renderers/webgl/WebGLCapabilities';
192
+ export * from './renderers/webgl/WebGLClipping';
193
+ export * from './renderers/webgl/WebGLCubeUVMaps';
194
+ export * from './renderers/webgl/WebGLExtensions';
195
+ export * from './renderers/webgl/WebGLGeometries';
196
+ export * from './renderers/webgl/WebGLIndexedBufferRenderer';
197
+ export * from './renderers/webgl/WebGLInfo';
198
+ export * from './renderers/webgl/WebGLLights';
199
+ export * from './renderers/webgl/WebGLObjects';
200
+ export * from './renderers/webgl/WebGLProgram';
201
+ export * from './renderers/webgl/WebGLPrograms';
202
+ export * from './renderers/webgl/WebGLProperties';
203
+ export * from './renderers/webgl/WebGLRenderLists';
204
+ export * from './renderers/webgl/WebGLShader';
205
+ export * from './renderers/webgl/WebGLShadowMap';
206
+ export * from './renderers/webgl/WebGLState';
207
+ export * from './renderers/webgl/WebGLTextures';
208
+ export * from './renderers/webgl/WebGLUniforms';
209
+ export * from './renderers/webxr/WebXR';
210
+ export * from './renderers/webxr/WebXRController';
211
+ export * from './renderers/webxr/WebXRManager';
212
+ export { WebGLUtils } from './renderers/webgl/WebGLUtils.js';
213
+ /**
214
+ * Scenes
215
+ */
216
+ export * from './scenes/FogExp2';
217
+ export * from './scenes/Fog';
218
+ export * from './scenes/Scene';
219
+ /**
220
+ * Textures
221
+ */
222
+ export * from './textures/VideoTexture';
223
+ export * from './textures/DataTexture';
224
+ export * from './textures/DataTexture2DArray';
225
+ export * from './textures/DataTexture3D';
226
+ export * from './textures/CompressedTexture';
227
+ export * from './textures/CubeTexture';
228
+ export * from './textures/Data3DTexture';
229
+ export * from './textures/DataArrayTexture';
230
+ export * from './textures/CanvasTexture';
231
+ export * from './textures/DepthTexture';
232
+ export * from './textures/FramebufferTexture';
233
+ export * from './textures/Source';
234
+ export * from './textures/Texture';
backend/libs/three/Three.js ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { REVISION } from './constants.js';
2
+
3
+ export { WebGLMultipleRenderTargets } from './renderers/WebGLMultipleRenderTargets.js';
4
+ export { WebGLMultisampleRenderTarget } from './renderers/WebGLMultisampleRenderTarget.js';
5
+ export { WebGLCubeRenderTarget } from './renderers/WebGLCubeRenderTarget.js';
6
+ export { WebGLRenderTarget } from './renderers/WebGLRenderTarget.js';
7
+ export { WebGLRenderer } from './renderers/WebGLRenderer.js';
8
+ export { WebGL1Renderer } from './renderers/WebGL1Renderer.js';
9
+ export { ShaderLib } from './renderers/shaders/ShaderLib.js';
10
+ export { UniformsLib } from './renderers/shaders/UniformsLib.js';
11
+ export { UniformsUtils } from './renderers/shaders/UniformsUtils.js';
12
+ export { ShaderChunk } from './renderers/shaders/ShaderChunk.js';
13
+ export { FogExp2 } from './scenes/FogExp2.js';
14
+ export { Fog } from './scenes/Fog.js';
15
+ export { Scene } from './scenes/Scene.js';
16
+ export { Sprite } from './objects/Sprite.js';
17
+ export { LOD } from './objects/LOD.js';
18
+ export { SkinnedMesh } from './objects/SkinnedMesh.js';
19
+ export { Skeleton } from './objects/Skeleton.js';
20
+ export { Bone } from './objects/Bone.js';
21
+ export { Mesh } from './objects/Mesh.js';
22
+ export { InstancedMesh } from './objects/InstancedMesh.js';
23
+ export { LineSegments } from './objects/LineSegments.js';
24
+ export { LineLoop } from './objects/LineLoop.js';
25
+ export { Line } from './objects/Line.js';
26
+ export { Points } from './objects/Points.js';
27
+ export { Group } from './objects/Group.js';
28
+ export { VideoTexture } from './textures/VideoTexture.js';
29
+ export { FramebufferTexture } from './textures/FramebufferTexture.js';
30
+ export { DataTexture } from './textures/DataTexture.js';
31
+ export { DataTexture2DArray } from './textures/DataTexture2DArray.js';
32
+ export { DataTexture3D } from './textures/DataTexture3D.js';
33
+ export { CompressedTexture } from './textures/CompressedTexture.js';
34
+ export { CubeTexture } from './textures/CubeTexture.js';
35
+ export { CanvasTexture } from './textures/CanvasTexture.js';
36
+ export { DepthTexture } from './textures/DepthTexture.js';
37
+ export { Texture } from './textures/Texture.js';
38
+ export * from './geometries/Geometries.js';
39
+ export * from './materials/Materials.js';
40
+ export { AnimationLoader } from './loaders/AnimationLoader.js';
41
+ export { CompressedTextureLoader } from './loaders/CompressedTextureLoader.js';
42
+ export { CubeTextureLoader } from './loaders/CubeTextureLoader.js';
43
+ export { DataTextureLoader } from './loaders/DataTextureLoader.js';
44
+ export { TextureLoader } from './loaders/TextureLoader.js';
45
+ export { ObjectLoader } from './loaders/ObjectLoader.js';
46
+ export { MaterialLoader } from './loaders/MaterialLoader.js';
47
+ export { BufferGeometryLoader } from './loaders/BufferGeometryLoader.js';
48
+ export { DefaultLoadingManager, LoadingManager } from './loaders/LoadingManager.js';
49
+ export { ImageLoader } from './loaders/ImageLoader.js';
50
+ export { ImageBitmapLoader } from './loaders/ImageBitmapLoader.js';
51
+ export { FileLoader } from './loaders/FileLoader.js';
52
+ export { Loader } from './loaders/Loader.js';
53
+ export { LoaderUtils } from './loaders/LoaderUtils.js';
54
+ export { Cache } from './loaders/Cache.js';
55
+ export { AudioLoader } from './loaders/AudioLoader.js';
56
+ export { SpotLight } from './lights/SpotLight.js';
57
+ export { PointLight } from './lights/PointLight.js';
58
+ export { RectAreaLight } from './lights/RectAreaLight.js';
59
+ export { HemisphereLight } from './lights/HemisphereLight.js';
60
+ export { HemisphereLightProbe } from './lights/HemisphereLightProbe.js';
61
+ export { DirectionalLight } from './lights/DirectionalLight.js';
62
+ export { AmbientLight } from './lights/AmbientLight.js';
63
+ export { AmbientLightProbe } from './lights/AmbientLightProbe.js';
64
+ export { Light } from './lights/Light.js';
65
+ export { LightProbe } from './lights/LightProbe.js';
66
+ export { StereoCamera } from './cameras/StereoCamera.js';
67
+ export { PerspectiveCamera } from './cameras/PerspectiveCamera.js';
68
+ export { OrthographicCamera } from './cameras/OrthographicCamera.js';
69
+ export { CubeCamera } from './cameras/CubeCamera.js';
70
+ export { ArrayCamera } from './cameras/ArrayCamera.js';
71
+ export { Camera } from './cameras/Camera.js';
72
+ export { AudioListener } from './audio/AudioListener.js';
73
+ export { PositionalAudio } from './audio/PositionalAudio.js';
74
+ export { AudioContext } from './audio/AudioContext.js';
75
+ export { AudioAnalyser } from './audio/AudioAnalyser.js';
76
+ export { Audio } from './audio/Audio.js';
77
+ export { VectorKeyframeTrack } from './animation/tracks/VectorKeyframeTrack.js';
78
+ export { StringKeyframeTrack } from './animation/tracks/StringKeyframeTrack.js';
79
+ export { QuaternionKeyframeTrack } from './animation/tracks/QuaternionKeyframeTrack.js';
80
+ export { NumberKeyframeTrack } from './animation/tracks/NumberKeyframeTrack.js';
81
+ export { ColorKeyframeTrack } from './animation/tracks/ColorKeyframeTrack.js';
82
+ export { BooleanKeyframeTrack } from './animation/tracks/BooleanKeyframeTrack.js';
83
+ export { PropertyMixer } from './animation/PropertyMixer.js';
84
+ export { PropertyBinding } from './animation/PropertyBinding.js';
85
+ export { KeyframeTrack } from './animation/KeyframeTrack.js';
86
+ export { AnimationUtils } from './animation/AnimationUtils.js';
87
+ export { AnimationObjectGroup } from './animation/AnimationObjectGroup.js';
88
+ export { AnimationMixer } from './animation/AnimationMixer.js';
89
+ export { AnimationClip } from './animation/AnimationClip.js';
90
+ export { Uniform } from './core/Uniform.js';
91
+ export { InstancedBufferGeometry } from './core/InstancedBufferGeometry.js';
92
+ export { BufferGeometry } from './core/BufferGeometry.js';
93
+ export { InterleavedBufferAttribute } from './core/InterleavedBufferAttribute.js';
94
+ export { InstancedInterleavedBuffer } from './core/InstancedInterleavedBuffer.js';
95
+ export { InterleavedBuffer } from './core/InterleavedBuffer.js';
96
+ export { InstancedBufferAttribute } from './core/InstancedBufferAttribute.js';
97
+ export { GLBufferAttribute } from './core/GLBufferAttribute.js';
98
+ export * from './core/BufferAttribute.js';
99
+ export { Object3D } from './core/Object3D.js';
100
+ export { Raycaster } from './core/Raycaster.js';
101
+ export { Layers } from './core/Layers.js';
102
+ export { EventDispatcher } from './core/EventDispatcher.js';
103
+ export { Clock } from './core/Clock.js';
104
+ export { QuaternionLinearInterpolant } from './math/interpolants/QuaternionLinearInterpolant.js';
105
+ export { LinearInterpolant } from './math/interpolants/LinearInterpolant.js';
106
+ export { DiscreteInterpolant } from './math/interpolants/DiscreteInterpolant.js';
107
+ export { CubicInterpolant } from './math/interpolants/CubicInterpolant.js';
108
+ export { Interpolant } from './math/Interpolant.js';
109
+ export { Triangle } from './math/Triangle.js';
110
+ export * as MathUtils from './math/MathUtils.js';
111
+ export { Spherical } from './math/Spherical.js';
112
+ export { Cylindrical } from './math/Cylindrical.js';
113
+ export { Plane } from './math/Plane.js';
114
+ export { Frustum } from './math/Frustum.js';
115
+ export { Sphere } from './math/Sphere.js';
116
+ export { Ray } from './math/Ray.js';
117
+ export { Matrix4 } from './math/Matrix4.js';
118
+ export { Matrix3 } from './math/Matrix3.js';
119
+ export { Box3 } from './math/Box3.js';
120
+ export { Box2 } from './math/Box2.js';
121
+ export { Line3 } from './math/Line3.js';
122
+ export { Euler } from './math/Euler.js';
123
+ export { Vector4 } from './math/Vector4.js';
124
+ export { Vector3 } from './math/Vector3.js';
125
+ export { Vector2 } from './math/Vector2.js';
126
+ export { Quaternion } from './math/Quaternion.js';
127
+ export { Color } from './math/Color.js';
128
+ export { SphericalHarmonics3 } from './math/SphericalHarmonics3.js';
129
+ export { SpotLightHelper } from './helpers/SpotLightHelper.js';
130
+ export { SkeletonHelper } from './helpers/SkeletonHelper.js';
131
+ export { PointLightHelper } from './helpers/PointLightHelper.js';
132
+ export { HemisphereLightHelper } from './helpers/HemisphereLightHelper.js';
133
+ export { GridHelper } from './helpers/GridHelper.js';
134
+ export { PolarGridHelper } from './helpers/PolarGridHelper.js';
135
+ export { DirectionalLightHelper } from './helpers/DirectionalLightHelper.js';
136
+ export { CameraHelper } from './helpers/CameraHelper.js';
137
+ export { BoxHelper } from './helpers/BoxHelper.js';
138
+ export { Box3Helper } from './helpers/Box3Helper.js';
139
+ export { PlaneHelper } from './helpers/PlaneHelper.js';
140
+ export { ArrowHelper } from './helpers/ArrowHelper.js';
141
+ export { AxesHelper } from './helpers/AxesHelper.js';
142
+ export * from './extras/curves/Curves.js';
143
+ export { Shape } from './extras/core/Shape.js';
144
+ export { Path } from './extras/core/Path.js';
145
+ export { ShapePath } from './extras/core/ShapePath.js';
146
+ export { CurvePath } from './extras/core/CurvePath.js';
147
+ export { Curve } from './extras/core/Curve.js';
148
+ export { DataUtils } from './extras/DataUtils.js';
149
+ export { ImageUtils } from './extras/ImageUtils.js';
150
+ export { ShapeUtils } from './extras/ShapeUtils.js';
151
+ export { PMREMGenerator } from './extras/PMREMGenerator.js';
152
+ export { WebGLUtils } from './renderers/webgl/WebGLUtils.js';
153
+ export * from './constants.js';
154
+ export * from './Three.Legacy.js';
155
+
156
+ if (typeof __THREE_DEVTOOLS__ !== 'undefined') {
157
+ __THREE_DEVTOOLS__.dispatchEvent(
158
+ new CustomEvent('register', {
159
+ detail: {
160
+ revision: REVISION,
161
+ },
162
+ })
163
+ );
164
+ }
165
+
166
+ if (typeof window !== 'undefined') {
167
+ if (window.__THREE__) {
168
+ console.warn('WARNING: Multiple instances of Three.js being imported.');
169
+ } else {
170
+ window.__THREE__ = REVISION;
171
+ }
172
+ }
backend/libs/three/animation/AnimationAction.d.ts ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AnimationMixer } from './AnimationMixer';
2
+ import { AnimationClip } from './AnimationClip';
3
+ import { AnimationActionLoopStyles, AnimationBlendMode } from '../constants';
4
+ import { Object3D } from '../core/Object3D';
5
+ // Animation ////////////////////////////////////////////////////////////////////////////////////////
6
+
7
+ export class AnimationAction {
8
+ constructor(mixer: AnimationMixer, clip: AnimationClip, localRoot?: Object3D, blendMode?: AnimationBlendMode);
9
+
10
+ blendMode: AnimationBlendMode;
11
+
12
+ /**
13
+ * @default THREE.LoopRepeat
14
+ */
15
+ loop: AnimationActionLoopStyles;
16
+
17
+ /**
18
+ * @default 0
19
+ */
20
+ time: number;
21
+
22
+ /**
23
+ * @default 1
24
+ */
25
+ timeScale: number;
26
+
27
+ /**
28
+ * @default 1
29
+ */
30
+ weight: number;
31
+
32
+ /**
33
+ * @default Infinity
34
+ */
35
+ repetitions: number;
36
+
37
+ /**
38
+ * @default false
39
+ */
40
+ paused: boolean;
41
+
42
+ /**
43
+ * @default true
44
+ */
45
+ enabled: boolean;
46
+
47
+ /**
48
+ * @default false
49
+ */
50
+ clampWhenFinished: boolean;
51
+
52
+ /**
53
+ * @default true
54
+ */
55
+ zeroSlopeAtStart: boolean;
56
+
57
+ /**
58
+ * @default true
59
+ */
60
+ zeroSlopeAtEnd: boolean;
61
+
62
+ play(): AnimationAction;
63
+ stop(): AnimationAction;
64
+ reset(): AnimationAction;
65
+ isRunning(): boolean;
66
+ isScheduled(): boolean;
67
+ startAt(time: number): AnimationAction;
68
+ setLoop(mode: AnimationActionLoopStyles, repetitions: number): AnimationAction;
69
+ setEffectiveWeight(weight: number): AnimationAction;
70
+ getEffectiveWeight(): number;
71
+ fadeIn(duration: number): AnimationAction;
72
+ fadeOut(duration: number): AnimationAction;
73
+ crossFadeFrom(fadeOutAction: AnimationAction, duration: number, warp: boolean): AnimationAction;
74
+ crossFadeTo(fadeInAction: AnimationAction, duration: number, warp: boolean): AnimationAction;
75
+ stopFading(): AnimationAction;
76
+ setEffectiveTimeScale(timeScale: number): AnimationAction;
77
+ getEffectiveTimeScale(): number;
78
+ setDuration(duration: number): AnimationAction;
79
+ syncWith(action: AnimationAction): AnimationAction;
80
+ halt(duration: number): AnimationAction;
81
+ warp(statTimeScale: number, endTimeScale: number, duration: number): AnimationAction;
82
+ stopWarping(): AnimationAction;
83
+ getMixer(): AnimationMixer;
84
+ getClip(): AnimationClip;
85
+ getRoot(): Object3D;
86
+ }
backend/libs/three/animation/AnimationAction.js ADDED
@@ -0,0 +1,553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ WrapAroundEnding,
3
+ ZeroCurvatureEnding,
4
+ ZeroSlopeEnding,
5
+ LoopPingPong,
6
+ LoopOnce,
7
+ LoopRepeat,
8
+ NormalAnimationBlendMode,
9
+ AdditiveAnimationBlendMode,
10
+ } from '../constants.js';
11
+
12
+ class AnimationAction {
13
+ constructor(mixer, clip, localRoot = null, blendMode = clip.blendMode) {
14
+ this._mixer = mixer;
15
+ this._clip = clip;
16
+ this._localRoot = localRoot;
17
+ this.blendMode = blendMode;
18
+
19
+ const tracks = clip.tracks,
20
+ nTracks = tracks.length,
21
+ interpolants = new Array(nTracks);
22
+
23
+ const interpolantSettings = {
24
+ endingStart: ZeroCurvatureEnding,
25
+ endingEnd: ZeroCurvatureEnding,
26
+ };
27
+
28
+ for (let i = 0; i !== nTracks; ++i) {
29
+ const interpolant = tracks[i].createInterpolant(null);
30
+ interpolants[i] = interpolant;
31
+ interpolant.settings = interpolantSettings;
32
+ }
33
+
34
+ this._interpolantSettings = interpolantSettings;
35
+
36
+ this._interpolants = interpolants; // bound by the mixer
37
+
38
+ // inside: PropertyMixer (managed by the mixer)
39
+ this._propertyBindings = new Array(nTracks);
40
+
41
+ this._cacheIndex = null; // for the memory manager
42
+ this._byClipCacheIndex = null; // for the memory manager
43
+
44
+ this._timeScaleInterpolant = null;
45
+ this._weightInterpolant = null;
46
+
47
+ this.loop = LoopRepeat;
48
+ this._loopCount = -1;
49
+
50
+ // global mixer time when the action is to be started
51
+ // it's set back to 'null' upon start of the action
52
+ this._startTime = null;
53
+
54
+ // scaled local time of the action
55
+ // gets clamped or wrapped to 0..clip.duration according to loop
56
+ this.time = 0;
57
+
58
+ this.timeScale = 1;
59
+ this._effectiveTimeScale = 1;
60
+
61
+ this.weight = 1;
62
+ this._effectiveWeight = 1;
63
+
64
+ this.repetitions = Infinity; // no. of repetitions when looping
65
+
66
+ this.paused = false; // true -> zero effective time scale
67
+ this.enabled = true; // false -> zero effective weight
68
+
69
+ this.clampWhenFinished = false; // keep feeding the last frame?
70
+
71
+ this.zeroSlopeAtStart = true; // for smooth interpolation w/o separate
72
+ this.zeroSlopeAtEnd = true; // clips for start, loop and end
73
+ }
74
+
75
+ // State & Scheduling
76
+
77
+ play() {
78
+ this._mixer._activateAction(this);
79
+
80
+ return this;
81
+ }
82
+
83
+ stop() {
84
+ this._mixer._deactivateAction(this);
85
+
86
+ return this.reset();
87
+ }
88
+
89
+ reset() {
90
+ this.paused = false;
91
+ this.enabled = true;
92
+
93
+ this.time = 0; // restart clip
94
+ this._loopCount = -1; // forget previous loops
95
+ this._startTime = null; // forget scheduling
96
+
97
+ return this.stopFading().stopWarping();
98
+ }
99
+
100
+ isRunning() {
101
+ return this.enabled && !this.paused && this.timeScale !== 0 && this._startTime === null && this._mixer._isActiveAction(this);
102
+ }
103
+
104
+ // return true when play has been called
105
+ isScheduled() {
106
+ return this._mixer._isActiveAction(this);
107
+ }
108
+
109
+ startAt(time) {
110
+ this._startTime = time;
111
+
112
+ return this;
113
+ }
114
+
115
+ setLoop(mode, repetitions) {
116
+ this.loop = mode;
117
+ this.repetitions = repetitions;
118
+
119
+ return this;
120
+ }
121
+
122
+ // Weight
123
+
124
+ // set the weight stopping any scheduled fading
125
+ // although .enabled = false yields an effective weight of zero, this
126
+ // method does *not* change .enabled, because it would be confusing
127
+ setEffectiveWeight(weight) {
128
+ this.weight = weight;
129
+
130
+ // note: same logic as when updated at runtime
131
+ this._effectiveWeight = this.enabled ? weight : 0;
132
+
133
+ return this.stopFading();
134
+ }
135
+
136
+ // return the weight considering fading and .enabled
137
+ getEffectiveWeight() {
138
+ return this._effectiveWeight;
139
+ }
140
+
141
+ fadeIn(duration) {
142
+ return this._scheduleFading(duration, 0, 1);
143
+ }
144
+
145
+ fadeOut(duration) {
146
+ return this._scheduleFading(duration, 1, 0);
147
+ }
148
+
149
+ crossFadeFrom(fadeOutAction, duration, warp) {
150
+ fadeOutAction.fadeOut(duration);
151
+ this.fadeIn(duration);
152
+
153
+ if (warp) {
154
+ const fadeInDuration = this._clip.duration,
155
+ fadeOutDuration = fadeOutAction._clip.duration,
156
+ startEndRatio = fadeOutDuration / fadeInDuration,
157
+ endStartRatio = fadeInDuration / fadeOutDuration;
158
+
159
+ fadeOutAction.warp(1.0, startEndRatio, duration);
160
+ this.warp(endStartRatio, 1.0, duration);
161
+ }
162
+
163
+ return this;
164
+ }
165
+
166
+ crossFadeTo(fadeInAction, duration, warp) {
167
+ return fadeInAction.crossFadeFrom(this, duration, warp);
168
+ }
169
+
170
+ stopFading() {
171
+ const weightInterpolant = this._weightInterpolant;
172
+
173
+ if (weightInterpolant !== null) {
174
+ this._weightInterpolant = null;
175
+ this._mixer._takeBackControlInterpolant(weightInterpolant);
176
+ }
177
+
178
+ return this;
179
+ }
180
+
181
+ // Time Scale Control
182
+
183
+ // set the time scale stopping any scheduled warping
184
+ // although .paused = true yields an effective time scale of zero, this
185
+ // method does *not* change .paused, because it would be confusing
186
+ setEffectiveTimeScale(timeScale) {
187
+ this.timeScale = timeScale;
188
+ this._effectiveTimeScale = this.paused ? 0 : timeScale;
189
+
190
+ return this.stopWarping();
191
+ }
192
+
193
+ // return the time scale considering warping and .paused
194
+ getEffectiveTimeScale() {
195
+ return this._effectiveTimeScale;
196
+ }
197
+
198
+ setDuration(duration) {
199
+ this.timeScale = this._clip.duration / duration;
200
+
201
+ return this.stopWarping();
202
+ }
203
+
204
+ syncWith(action) {
205
+ this.time = action.time;
206
+ this.timeScale = action.timeScale;
207
+
208
+ return this.stopWarping();
209
+ }
210
+
211
+ halt(duration) {
212
+ return this.warp(this._effectiveTimeScale, 0, duration);
213
+ }
214
+
215
+ warp(startTimeScale, endTimeScale, duration) {
216
+ const mixer = this._mixer,
217
+ now = mixer.time,
218
+ timeScale = this.timeScale;
219
+
220
+ let interpolant = this._timeScaleInterpolant;
221
+
222
+ if (interpolant === null) {
223
+ interpolant = mixer._lendControlInterpolant();
224
+ this._timeScaleInterpolant = interpolant;
225
+ }
226
+
227
+ const times = interpolant.parameterPositions,
228
+ values = interpolant.sampleValues;
229
+
230
+ times[0] = now;
231
+ times[1] = now + duration;
232
+
233
+ values[0] = startTimeScale / timeScale;
234
+ values[1] = endTimeScale / timeScale;
235
+
236
+ return this;
237
+ }
238
+
239
+ stopWarping() {
240
+ const timeScaleInterpolant = this._timeScaleInterpolant;
241
+
242
+ if (timeScaleInterpolant !== null) {
243
+ this._timeScaleInterpolant = null;
244
+ this._mixer._takeBackControlInterpolant(timeScaleInterpolant);
245
+ }
246
+
247
+ return this;
248
+ }
249
+
250
+ // Object Accessors
251
+
252
+ getMixer() {
253
+ return this._mixer;
254
+ }
255
+
256
+ getClip() {
257
+ return this._clip;
258
+ }
259
+
260
+ getRoot() {
261
+ return this._localRoot || this._mixer._root;
262
+ }
263
+
264
+ // Interna
265
+
266
+ _update(time, deltaTime, timeDirection, accuIndex) {
267
+ // called by the mixer
268
+
269
+ if (!this.enabled) {
270
+ // call ._updateWeight() to update ._effectiveWeight
271
+
272
+ this._updateWeight(time);
273
+ return;
274
+ }
275
+
276
+ const startTime = this._startTime;
277
+
278
+ if (startTime !== null) {
279
+ // check for scheduled start of action
280
+
281
+ const timeRunning = (time - startTime) * timeDirection;
282
+ if (timeRunning < 0 || timeDirection === 0) {
283
+ return; // yet to come / don't decide when delta = 0
284
+ }
285
+
286
+ // start
287
+
288
+ this._startTime = null; // unschedule
289
+ deltaTime = timeDirection * timeRunning;
290
+ }
291
+
292
+ // apply time scale and advance time
293
+
294
+ deltaTime *= this._updateTimeScale(time);
295
+ const clipTime = this._updateTime(deltaTime);
296
+
297
+ // note: _updateTime may disable the action resulting in
298
+ // an effective weight of 0
299
+
300
+ const weight = this._updateWeight(time);
301
+
302
+ if (weight > 0) {
303
+ const interpolants = this._interpolants;
304
+ const propertyMixers = this._propertyBindings;
305
+
306
+ switch (this.blendMode) {
307
+ case AdditiveAnimationBlendMode:
308
+ for (let j = 0, m = interpolants.length; j !== m; ++j) {
309
+ interpolants[j].evaluate(clipTime);
310
+ propertyMixers[j].accumulateAdditive(weight);
311
+ }
312
+
313
+ break;
314
+
315
+ case NormalAnimationBlendMode:
316
+ default:
317
+ for (let j = 0, m = interpolants.length; j !== m; ++j) {
318
+ interpolants[j].evaluate(clipTime);
319
+ propertyMixers[j].accumulate(accuIndex, weight);
320
+ }
321
+ }
322
+ }
323
+ }
324
+
325
+ _updateWeight(time) {
326
+ let weight = 0;
327
+
328
+ if (this.enabled) {
329
+ weight = this.weight;
330
+ const interpolant = this._weightInterpolant;
331
+
332
+ if (interpolant !== null) {
333
+ const interpolantValue = interpolant.evaluate(time)[0];
334
+
335
+ weight *= interpolantValue;
336
+
337
+ if (time > interpolant.parameterPositions[1]) {
338
+ this.stopFading();
339
+
340
+ if (interpolantValue === 0) {
341
+ // faded out, disable
342
+ this.enabled = false;
343
+ }
344
+ }
345
+ }
346
+ }
347
+
348
+ this._effectiveWeight = weight;
349
+ return weight;
350
+ }
351
+
352
+ _updateTimeScale(time) {
353
+ let timeScale = 0;
354
+
355
+ if (!this.paused) {
356
+ timeScale = this.timeScale;
357
+
358
+ const interpolant = this._timeScaleInterpolant;
359
+
360
+ if (interpolant !== null) {
361
+ const interpolantValue = interpolant.evaluate(time)[0];
362
+
363
+ timeScale *= interpolantValue;
364
+
365
+ if (time > interpolant.parameterPositions[1]) {
366
+ this.stopWarping();
367
+
368
+ if (timeScale === 0) {
369
+ // motion has halted, pause
370
+ this.paused = true;
371
+ } else {
372
+ // warp done - apply final time scale
373
+ this.timeScale = timeScale;
374
+ }
375
+ }
376
+ }
377
+ }
378
+
379
+ this._effectiveTimeScale = timeScale;
380
+ return timeScale;
381
+ }
382
+
383
+ _updateTime(deltaTime) {
384
+ const duration = this._clip.duration;
385
+ const loop = this.loop;
386
+
387
+ let time = this.time + deltaTime;
388
+ let loopCount = this._loopCount;
389
+
390
+ const pingPong = loop === LoopPingPong;
391
+
392
+ if (deltaTime === 0) {
393
+ if (loopCount === -1) return time;
394
+
395
+ return pingPong && (loopCount & 1) === 1 ? duration - time : time;
396
+ }
397
+
398
+ if (loop === LoopOnce) {
399
+ if (loopCount === -1) {
400
+ // just started
401
+
402
+ this._loopCount = 0;
403
+ this._setEndings(true, true, false);
404
+ }
405
+
406
+ handle_stop: {
407
+ if (time >= duration) {
408
+ time = duration;
409
+ } else if (time < 0) {
410
+ time = 0;
411
+ } else {
412
+ this.time = time;
413
+
414
+ break handle_stop;
415
+ }
416
+
417
+ if (this.clampWhenFinished) this.paused = true;
418
+ else this.enabled = false;
419
+
420
+ this.time = time;
421
+
422
+ this._mixer.dispatchEvent({
423
+ type: 'finished',
424
+ action: this,
425
+ direction: deltaTime < 0 ? -1 : 1,
426
+ });
427
+ }
428
+ } else {
429
+ // repetitive Repeat or PingPong
430
+
431
+ if (loopCount === -1) {
432
+ // just started
433
+
434
+ if (deltaTime >= 0) {
435
+ loopCount = 0;
436
+
437
+ this._setEndings(true, this.repetitions === 0, pingPong);
438
+ } else {
439
+ // when looping in reverse direction, the initial
440
+ // transition through zero counts as a repetition,
441
+ // so leave loopCount at -1
442
+
443
+ this._setEndings(this.repetitions === 0, true, pingPong);
444
+ }
445
+ }
446
+
447
+ if (time >= duration || time < 0) {
448
+ // wrap around
449
+
450
+ const loopDelta = Math.floor(time / duration); // signed
451
+ time -= duration * loopDelta;
452
+
453
+ loopCount += Math.abs(loopDelta);
454
+
455
+ const pending = this.repetitions - loopCount;
456
+
457
+ if (pending <= 0) {
458
+ // have to stop (switch state, clamp time, fire event)
459
+
460
+ if (this.clampWhenFinished) this.paused = true;
461
+ else this.enabled = false;
462
+
463
+ time = deltaTime > 0 ? duration : 0;
464
+
465
+ this.time = time;
466
+
467
+ this._mixer.dispatchEvent({
468
+ type: 'finished',
469
+ action: this,
470
+ direction: deltaTime > 0 ? 1 : -1,
471
+ });
472
+ } else {
473
+ // keep running
474
+
475
+ if (pending === 1) {
476
+ // entering the last round
477
+
478
+ const atStart = deltaTime < 0;
479
+ this._setEndings(atStart, !atStart, pingPong);
480
+ } else {
481
+ this._setEndings(false, false, pingPong);
482
+ }
483
+
484
+ this._loopCount = loopCount;
485
+
486
+ this.time = time;
487
+
488
+ this._mixer.dispatchEvent({
489
+ type: 'loop',
490
+ action: this,
491
+ loopDelta: loopDelta,
492
+ });
493
+ }
494
+ } else {
495
+ this.time = time;
496
+ }
497
+
498
+ if (pingPong && (loopCount & 1) === 1) {
499
+ // invert time for the "pong round"
500
+
501
+ return duration - time;
502
+ }
503
+ }
504
+
505
+ return time;
506
+ }
507
+
508
+ _setEndings(atStart, atEnd, pingPong) {
509
+ const settings = this._interpolantSettings;
510
+
511
+ if (pingPong) {
512
+ settings.endingStart = ZeroSlopeEnding;
513
+ settings.endingEnd = ZeroSlopeEnding;
514
+ } else {
515
+ // assuming for LoopOnce atStart == atEnd == true
516
+
517
+ if (atStart) {
518
+ settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;
519
+ } else {
520
+ settings.endingStart = WrapAroundEnding;
521
+ }
522
+
523
+ if (atEnd) {
524
+ settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;
525
+ } else {
526
+ settings.endingEnd = WrapAroundEnding;
527
+ }
528
+ }
529
+ }
530
+
531
+ _scheduleFading(duration, weightNow, weightThen) {
532
+ const mixer = this._mixer,
533
+ now = mixer.time;
534
+ let interpolant = this._weightInterpolant;
535
+
536
+ if (interpolant === null) {
537
+ interpolant = mixer._lendControlInterpolant();
538
+ this._weightInterpolant = interpolant;
539
+ }
540
+
541
+ const times = interpolant.parameterPositions,
542
+ values = interpolant.sampleValues;
543
+
544
+ times[0] = now;
545
+ values[0] = weightNow;
546
+ times[1] = now + duration;
547
+ values[1] = weightThen;
548
+
549
+ return this;
550
+ }
551
+ }
552
+
553
+ export { AnimationAction };
backend/libs/three/animation/AnimationClip.d.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from './KeyframeTrack';
2
+ import { Vector3 } from './../math/Vector3';
3
+ import { Bone } from './../objects/Bone';
4
+ import { AnimationBlendMode } from '../constants';
5
+
6
+ export interface MorphTarget {
7
+ name: string;
8
+ vertices: Vector3[];
9
+ }
10
+
11
+ export class AnimationClip {
12
+ constructor(name?: string, duration?: number, tracks?: KeyframeTrack[], blendMode?: AnimationBlendMode);
13
+
14
+ name: string;
15
+ tracks: KeyframeTrack[];
16
+
17
+ /**
18
+ * @default THREE.NormalAnimationBlendMode
19
+ */
20
+ blendMode: AnimationBlendMode;
21
+
22
+ /**
23
+ * @default -1
24
+ */
25
+ duration: number;
26
+ uuid: string;
27
+ results: any[];
28
+
29
+ resetDuration(): AnimationClip;
30
+ trim(): AnimationClip;
31
+ validate(): boolean;
32
+ optimize(): AnimationClip;
33
+ clone(): this;
34
+ toJSON(clip: AnimationClip): any;
35
+
36
+ static CreateFromMorphTargetSequence(name: string, morphTargetSequence: MorphTarget[], fps: number, noLoop: boolean): AnimationClip;
37
+ static findByName(clipArray: AnimationClip[], name: string): AnimationClip;
38
+ static CreateClipsFromMorphTargetSequences(morphTargets: MorphTarget[], fps: number, noLoop: boolean): AnimationClip[];
39
+ static parse(json: any): AnimationClip;
40
+ static parseAnimation(animation: any, bones: Bone[]): AnimationClip;
41
+ static toJSON(clip: AnimationClip): any;
42
+ }
backend/libs/three/animation/AnimationClip.js ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AnimationUtils } from './AnimationUtils.js';
2
+ import { KeyframeTrack } from './KeyframeTrack.js';
3
+ import { BooleanKeyframeTrack } from './tracks/BooleanKeyframeTrack.js';
4
+ import { ColorKeyframeTrack } from './tracks/ColorKeyframeTrack.js';
5
+ import { NumberKeyframeTrack } from './tracks/NumberKeyframeTrack.js';
6
+ import { QuaternionKeyframeTrack } from './tracks/QuaternionKeyframeTrack.js';
7
+ import { StringKeyframeTrack } from './tracks/StringKeyframeTrack.js';
8
+ import { VectorKeyframeTrack } from './tracks/VectorKeyframeTrack.js';
9
+ import * as MathUtils from '../math/MathUtils.js';
10
+ import { NormalAnimationBlendMode } from '../constants.js';
11
+
12
+ class AnimationClip {
13
+ constructor(name, duration = -1, tracks, blendMode = NormalAnimationBlendMode) {
14
+ this.name = name;
15
+ this.tracks = tracks;
16
+ this.duration = duration;
17
+ this.blendMode = blendMode;
18
+
19
+ this.uuid = MathUtils.generateUUID();
20
+
21
+ // this means it should figure out its duration by scanning the tracks
22
+ if (this.duration < 0) {
23
+ this.resetDuration();
24
+ }
25
+ }
26
+
27
+ static parse(json) {
28
+ const tracks = [],
29
+ jsonTracks = json.tracks,
30
+ frameTime = 1.0 / (json.fps || 1.0);
31
+
32
+ for (let i = 0, n = jsonTracks.length; i !== n; ++i) {
33
+ tracks.push(parseKeyframeTrack(jsonTracks[i]).scale(frameTime));
34
+ }
35
+
36
+ const clip = new this(json.name, json.duration, tracks, json.blendMode);
37
+ clip.uuid = json.uuid;
38
+
39
+ return clip;
40
+ }
41
+
42
+ static toJSON(clip) {
43
+ const tracks = [],
44
+ clipTracks = clip.tracks;
45
+
46
+ const json = {
47
+ name: clip.name,
48
+ duration: clip.duration,
49
+ tracks: tracks,
50
+ uuid: clip.uuid,
51
+ blendMode: clip.blendMode,
52
+ };
53
+
54
+ for (let i = 0, n = clipTracks.length; i !== n; ++i) {
55
+ tracks.push(KeyframeTrack.toJSON(clipTracks[i]));
56
+ }
57
+
58
+ return json;
59
+ }
60
+
61
+ static CreateFromMorphTargetSequence(name, morphTargetSequence, fps, noLoop) {
62
+ const numMorphTargets = morphTargetSequence.length;
63
+ const tracks = [];
64
+
65
+ for (let i = 0; i < numMorphTargets; i++) {
66
+ let times = [];
67
+ let values = [];
68
+
69
+ times.push((i + numMorphTargets - 1) % numMorphTargets, i, (i + 1) % numMorphTargets);
70
+
71
+ values.push(0, 1, 0);
72
+
73
+ const order = AnimationUtils.getKeyframeOrder(times);
74
+ times = AnimationUtils.sortedArray(times, 1, order);
75
+ values = AnimationUtils.sortedArray(values, 1, order);
76
+
77
+ // if there is a key at the first frame, duplicate it as the
78
+ // last frame as well for perfect loop.
79
+ if (!noLoop && times[0] === 0) {
80
+ times.push(numMorphTargets);
81
+ values.push(values[0]);
82
+ }
83
+
84
+ tracks.push(new NumberKeyframeTrack('.morphTargetInfluences[' + morphTargetSequence[i].name + ']', times, values).scale(1.0 / fps));
85
+ }
86
+
87
+ return new this(name, -1, tracks);
88
+ }
89
+
90
+ static findByName(objectOrClipArray, name) {
91
+ let clipArray = objectOrClipArray;
92
+
93
+ if (!Array.isArray(objectOrClipArray)) {
94
+ const o = objectOrClipArray;
95
+ clipArray = (o.geometry && o.geometry.animations) || o.animations;
96
+ }
97
+
98
+ for (let i = 0; i < clipArray.length; i++) {
99
+ if (clipArray[i].name === name) {
100
+ return clipArray[i];
101
+ }
102
+ }
103
+
104
+ return null;
105
+ }
106
+
107
+ static CreateClipsFromMorphTargetSequences(morphTargets, fps, noLoop) {
108
+ const animationToMorphTargets = {};
109
+
110
+ // tested with https://regex101.com/ on trick sequences
111
+ // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
112
+ const pattern = /^([\w-]*?)([\d]+)$/;
113
+
114
+ // sort morph target names into animation groups based
115
+ // patterns like Walk_001, Walk_002, Run_001, Run_002
116
+ for (let i = 0, il = morphTargets.length; i < il; i++) {
117
+ const morphTarget = morphTargets[i];
118
+ const parts = morphTarget.name.match(pattern);
119
+
120
+ if (parts && parts.length > 1) {
121
+ const name = parts[1];
122
+
123
+ let animationMorphTargets = animationToMorphTargets[name];
124
+
125
+ if (!animationMorphTargets) {
126
+ animationToMorphTargets[name] = animationMorphTargets = [];
127
+ }
128
+
129
+ animationMorphTargets.push(morphTarget);
130
+ }
131
+ }
132
+
133
+ const clips = [];
134
+
135
+ for (const name in animationToMorphTargets) {
136
+ clips.push(this.CreateFromMorphTargetSequence(name, animationToMorphTargets[name], fps, noLoop));
137
+ }
138
+
139
+ return clips;
140
+ }
141
+
142
+ // parse the animation.hierarchy format
143
+ static parseAnimation(animation, bones) {
144
+ if (!animation) {
145
+ console.error('THREE.AnimationClip: No animation in JSONLoader data.');
146
+ return null;
147
+ }
148
+
149
+ const addNonemptyTrack = function (trackType, trackName, animationKeys, propertyName, destTracks) {
150
+ // only return track if there are actually keys.
151
+ if (animationKeys.length !== 0) {
152
+ const times = [];
153
+ const values = [];
154
+
155
+ AnimationUtils.flattenJSON(animationKeys, times, values, propertyName);
156
+
157
+ // empty keys are filtered out, so check again
158
+ if (times.length !== 0) {
159
+ destTracks.push(new trackType(trackName, times, values));
160
+ }
161
+ }
162
+ };
163
+
164
+ const tracks = [];
165
+
166
+ const clipName = animation.name || 'default';
167
+ const fps = animation.fps || 30;
168
+ const blendMode = animation.blendMode;
169
+
170
+ // automatic length determination in AnimationClip.
171
+ let duration = animation.length || -1;
172
+
173
+ const hierarchyTracks = animation.hierarchy || [];
174
+
175
+ for (let h = 0; h < hierarchyTracks.length; h++) {
176
+ const animationKeys = hierarchyTracks[h].keys;
177
+
178
+ // skip empty tracks
179
+ if (!animationKeys || animationKeys.length === 0) continue;
180
+
181
+ // process morph targets
182
+ if (animationKeys[0].morphTargets) {
183
+ // figure out all morph targets used in this track
184
+ const morphTargetNames = {};
185
+
186
+ let k;
187
+
188
+ for (k = 0; k < animationKeys.length; k++) {
189
+ if (animationKeys[k].morphTargets) {
190
+ for (let m = 0; m < animationKeys[k].morphTargets.length; m++) {
191
+ morphTargetNames[animationKeys[k].morphTargets[m]] = -1;
192
+ }
193
+ }
194
+ }
195
+
196
+ // create a track for each morph target with all zero
197
+ // morphTargetInfluences except for the keys in which
198
+ // the morphTarget is named.
199
+ for (const morphTargetName in morphTargetNames) {
200
+ const times = [];
201
+ const values = [];
202
+
203
+ for (let m = 0; m !== animationKeys[k].morphTargets.length; ++m) {
204
+ const animationKey = animationKeys[k];
205
+
206
+ times.push(animationKey.time);
207
+ values.push(animationKey.morphTarget === morphTargetName ? 1 : 0);
208
+ }
209
+
210
+ tracks.push(new NumberKeyframeTrack('.morphTargetInfluence[' + morphTargetName + ']', times, values));
211
+ }
212
+
213
+ duration = morphTargetNames.length * (fps || 1.0);
214
+ } else {
215
+ // ...assume skeletal animation
216
+
217
+ const boneName = '.bones[' + bones[h].name + ']';
218
+
219
+ addNonemptyTrack(VectorKeyframeTrack, boneName + '.position', animationKeys, 'pos', tracks);
220
+
221
+ addNonemptyTrack(QuaternionKeyframeTrack, boneName + '.quaternion', animationKeys, 'rot', tracks);
222
+
223
+ addNonemptyTrack(VectorKeyframeTrack, boneName + '.scale', animationKeys, 'scl', tracks);
224
+ }
225
+ }
226
+
227
+ if (tracks.length === 0) {
228
+ return null;
229
+ }
230
+
231
+ const clip = new this(clipName, duration, tracks, blendMode);
232
+
233
+ return clip;
234
+ }
235
+
236
+ resetDuration() {
237
+ const tracks = this.tracks;
238
+ let duration = 0;
239
+
240
+ for (let i = 0, n = tracks.length; i !== n; ++i) {
241
+ const track = this.tracks[i];
242
+
243
+ duration = Math.max(duration, track.times[track.times.length - 1]);
244
+ }
245
+
246
+ this.duration = duration;
247
+
248
+ return this;
249
+ }
250
+
251
+ trim() {
252
+ for (let i = 0; i < this.tracks.length; i++) {
253
+ this.tracks[i].trim(0, this.duration);
254
+ }
255
+
256
+ return this;
257
+ }
258
+
259
+ validate() {
260
+ let valid = true;
261
+
262
+ for (let i = 0; i < this.tracks.length; i++) {
263
+ valid = valid && this.tracks[i].validate();
264
+ }
265
+
266
+ return valid;
267
+ }
268
+
269
+ optimize() {
270
+ for (let i = 0; i < this.tracks.length; i++) {
271
+ this.tracks[i].optimize();
272
+ }
273
+
274
+ return this;
275
+ }
276
+
277
+ clone() {
278
+ const tracks = [];
279
+
280
+ for (let i = 0; i < this.tracks.length; i++) {
281
+ tracks.push(this.tracks[i].clone());
282
+ }
283
+
284
+ return new this.constructor(this.name, this.duration, tracks, this.blendMode);
285
+ }
286
+
287
+ toJSON() {
288
+ return this.constructor.toJSON(this);
289
+ }
290
+ }
291
+
292
+ function getTrackTypeForValueTypeName(typeName) {
293
+ switch (typeName.toLowerCase()) {
294
+ case 'scalar':
295
+ case 'double':
296
+ case 'float':
297
+ case 'number':
298
+ case 'integer':
299
+ return NumberKeyframeTrack;
300
+
301
+ case 'vector':
302
+ case 'vector2':
303
+ case 'vector3':
304
+ case 'vector4':
305
+ return VectorKeyframeTrack;
306
+
307
+ case 'color':
308
+ return ColorKeyframeTrack;
309
+
310
+ case 'quaternion':
311
+ return QuaternionKeyframeTrack;
312
+
313
+ case 'bool':
314
+ case 'boolean':
315
+ return BooleanKeyframeTrack;
316
+
317
+ case 'string':
318
+ return StringKeyframeTrack;
319
+ }
320
+
321
+ throw new Error('THREE.KeyframeTrack: Unsupported typeName: ' + typeName);
322
+ }
323
+
324
+ function parseKeyframeTrack(json) {
325
+ if (json.type === undefined) {
326
+ throw new Error('THREE.KeyframeTrack: track type undefined, can not parse');
327
+ }
328
+
329
+ const trackType = getTrackTypeForValueTypeName(json.type);
330
+
331
+ if (json.times === undefined) {
332
+ const times = [],
333
+ values = [];
334
+
335
+ AnimationUtils.flattenJSON(json.keys, times, values, 'value');
336
+
337
+ json.times = times;
338
+ json.values = values;
339
+ }
340
+
341
+ // derived classes can define a static parse method
342
+ if (trackType.parse !== undefined) {
343
+ return trackType.parse(json);
344
+ } else {
345
+ // by default, we assume a constructor compatible with the base
346
+ return new trackType(json.name, json.times, json.values, json.interpolation);
347
+ }
348
+ }
349
+
350
+ export { AnimationClip };
backend/libs/three/animation/AnimationMixer.d.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AnimationClip } from './AnimationClip';
2
+ import { AnimationAction } from './AnimationAction';
3
+ import { AnimationBlendMode } from '../constants';
4
+ import { EventDispatcher } from './../core/EventDispatcher';
5
+ import { Object3D } from '../core/Object3D';
6
+ import { AnimationObjectGroup } from './AnimationObjectGroup';
7
+
8
+ export class AnimationMixer extends EventDispatcher {
9
+ constructor(root: Object3D | AnimationObjectGroup);
10
+
11
+ /**
12
+ * @default 0
13
+ */
14
+ time: number;
15
+
16
+ /**
17
+ * @default 1.0
18
+ */
19
+ timeScale: number;
20
+
21
+ clipAction(clip: AnimationClip, root?: Object3D | AnimationObjectGroup, blendMode?: AnimationBlendMode): AnimationAction;
22
+ existingAction(clip: AnimationClip, root?: Object3D | AnimationObjectGroup): AnimationAction | null;
23
+ stopAllAction(): AnimationMixer;
24
+ update(deltaTime: number): AnimationMixer;
25
+ setTime(timeInSeconds: number): AnimationMixer;
26
+ getRoot(): Object3D | AnimationObjectGroup;
27
+ uncacheClip(clip: AnimationClip): void;
28
+ uncacheRoot(root: Object3D | AnimationObjectGroup): void;
29
+ uncacheAction(clip: AnimationClip, root?: Object3D | AnimationObjectGroup): void;
30
+ }
backend/libs/three/animation/AnimationMixer.js ADDED
@@ -0,0 +1,590 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AnimationAction } from './AnimationAction.js';
2
+ import { EventDispatcher } from '../core/EventDispatcher.js';
3
+ import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
4
+ import { PropertyBinding } from './PropertyBinding.js';
5
+ import { PropertyMixer } from './PropertyMixer.js';
6
+ import { AnimationClip } from './AnimationClip.js';
7
+ import { NormalAnimationBlendMode } from '../constants.js';
8
+
9
+ class AnimationMixer extends EventDispatcher {
10
+ constructor(root) {
11
+ super();
12
+
13
+ this._root = root;
14
+ this._initMemoryManager();
15
+ this._accuIndex = 0;
16
+ this.time = 0;
17
+ this.timeScale = 1.0;
18
+ }
19
+
20
+ _bindAction(action, prototypeAction) {
21
+ const root = action._localRoot || this._root,
22
+ tracks = action._clip.tracks,
23
+ nTracks = tracks.length,
24
+ bindings = action._propertyBindings,
25
+ interpolants = action._interpolants,
26
+ rootUuid = root.uuid,
27
+ bindingsByRoot = this._bindingsByRootAndName;
28
+
29
+ let bindingsByName = bindingsByRoot[rootUuid];
30
+
31
+ if (bindingsByName === undefined) {
32
+ bindingsByName = {};
33
+ bindingsByRoot[rootUuid] = bindingsByName;
34
+ }
35
+
36
+ for (let i = 0; i !== nTracks; ++i) {
37
+ const track = tracks[i],
38
+ trackName = track.name;
39
+
40
+ let binding = bindingsByName[trackName];
41
+
42
+ if (binding !== undefined) {
43
+ ++binding.referenceCount;
44
+ bindings[i] = binding;
45
+ } else {
46
+ binding = bindings[i];
47
+
48
+ if (binding !== undefined) {
49
+ // existing binding, make sure the cache knows
50
+
51
+ if (binding._cacheIndex === null) {
52
+ ++binding.referenceCount;
53
+ this._addInactiveBinding(binding, rootUuid, trackName);
54
+ }
55
+
56
+ continue;
57
+ }
58
+
59
+ const path = prototypeAction && prototypeAction._propertyBindings[i].binding.parsedPath;
60
+
61
+ binding = new PropertyMixer(PropertyBinding.create(root, trackName, path), track.ValueTypeName, track.getValueSize());
62
+
63
+ ++binding.referenceCount;
64
+ this._addInactiveBinding(binding, rootUuid, trackName);
65
+
66
+ bindings[i] = binding;
67
+ }
68
+
69
+ interpolants[i].resultBuffer = binding.buffer;
70
+ }
71
+ }
72
+
73
+ _activateAction(action) {
74
+ if (!this._isActiveAction(action)) {
75
+ if (action._cacheIndex === null) {
76
+ // this action has been forgotten by the cache, but the user
77
+ // appears to be still using it -> rebind
78
+
79
+ const rootUuid = (action._localRoot || this._root).uuid,
80
+ clipUuid = action._clip.uuid,
81
+ actionsForClip = this._actionsByClip[clipUuid];
82
+
83
+ this._bindAction(action, actionsForClip && actionsForClip.knownActions[0]);
84
+
85
+ this._addInactiveAction(action, clipUuid, rootUuid);
86
+ }
87
+
88
+ const bindings = action._propertyBindings;
89
+
90
+ // increment reference counts / sort out state
91
+ for (let i = 0, n = bindings.length; i !== n; ++i) {
92
+ const binding = bindings[i];
93
+
94
+ if (binding.useCount++ === 0) {
95
+ this._lendBinding(binding);
96
+ binding.saveOriginalState();
97
+ }
98
+ }
99
+
100
+ this._lendAction(action);
101
+ }
102
+ }
103
+
104
+ _deactivateAction(action) {
105
+ if (this._isActiveAction(action)) {
106
+ const bindings = action._propertyBindings;
107
+
108
+ // decrement reference counts / sort out state
109
+ for (let i = 0, n = bindings.length; i !== n; ++i) {
110
+ const binding = bindings[i];
111
+
112
+ if (--binding.useCount === 0) {
113
+ binding.restoreOriginalState();
114
+ this._takeBackBinding(binding);
115
+ }
116
+ }
117
+
118
+ this._takeBackAction(action);
119
+ }
120
+ }
121
+
122
+ // Memory manager
123
+
124
+ _initMemoryManager() {
125
+ this._actions = []; // 'nActiveActions' followed by inactive ones
126
+ this._nActiveActions = 0;
127
+
128
+ this._actionsByClip = {};
129
+ // inside:
130
+ // {
131
+ // knownActions: Array< AnimationAction > - used as prototypes
132
+ // actionByRoot: AnimationAction - lookup
133
+ // }
134
+
135
+ this._bindings = []; // 'nActiveBindings' followed by inactive ones
136
+ this._nActiveBindings = 0;
137
+
138
+ this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
139
+
140
+ this._controlInterpolants = []; // same game as above
141
+ this._nActiveControlInterpolants = 0;
142
+
143
+ const scope = this;
144
+
145
+ this.stats = {
146
+ actions: {
147
+ get total() {
148
+ return scope._actions.length;
149
+ },
150
+ get inUse() {
151
+ return scope._nActiveActions;
152
+ },
153
+ },
154
+ bindings: {
155
+ get total() {
156
+ return scope._bindings.length;
157
+ },
158
+ get inUse() {
159
+ return scope._nActiveBindings;
160
+ },
161
+ },
162
+ controlInterpolants: {
163
+ get total() {
164
+ return scope._controlInterpolants.length;
165
+ },
166
+ get inUse() {
167
+ return scope._nActiveControlInterpolants;
168
+ },
169
+ },
170
+ };
171
+ }
172
+
173
+ // Memory management for AnimationAction objects
174
+
175
+ _isActiveAction(action) {
176
+ const index = action._cacheIndex;
177
+ return index !== null && index < this._nActiveActions;
178
+ }
179
+
180
+ _addInactiveAction(action, clipUuid, rootUuid) {
181
+ const actions = this._actions,
182
+ actionsByClip = this._actionsByClip;
183
+
184
+ let actionsForClip = actionsByClip[clipUuid];
185
+
186
+ if (actionsForClip === undefined) {
187
+ actionsForClip = {
188
+ knownActions: [action],
189
+ actionByRoot: {},
190
+ };
191
+
192
+ action._byClipCacheIndex = 0;
193
+
194
+ actionsByClip[clipUuid] = actionsForClip;
195
+ } else {
196
+ const knownActions = actionsForClip.knownActions;
197
+
198
+ action._byClipCacheIndex = knownActions.length;
199
+ knownActions.push(action);
200
+ }
201
+
202
+ action._cacheIndex = actions.length;
203
+ actions.push(action);
204
+
205
+ actionsForClip.actionByRoot[rootUuid] = action;
206
+ }
207
+
208
+ _removeInactiveAction(action) {
209
+ const actions = this._actions,
210
+ lastInactiveAction = actions[actions.length - 1],
211
+ cacheIndex = action._cacheIndex;
212
+
213
+ lastInactiveAction._cacheIndex = cacheIndex;
214
+ actions[cacheIndex] = lastInactiveAction;
215
+ actions.pop();
216
+
217
+ action._cacheIndex = null;
218
+
219
+ const clipUuid = action._clip.uuid,
220
+ actionsByClip = this._actionsByClip,
221
+ actionsForClip = actionsByClip[clipUuid],
222
+ knownActionsForClip = actionsForClip.knownActions,
223
+ lastKnownAction = knownActionsForClip[knownActionsForClip.length - 1],
224
+ byClipCacheIndex = action._byClipCacheIndex;
225
+
226
+ lastKnownAction._byClipCacheIndex = byClipCacheIndex;
227
+ knownActionsForClip[byClipCacheIndex] = lastKnownAction;
228
+ knownActionsForClip.pop();
229
+
230
+ action._byClipCacheIndex = null;
231
+
232
+ const actionByRoot = actionsForClip.actionByRoot,
233
+ rootUuid = (action._localRoot || this._root).uuid;
234
+
235
+ delete actionByRoot[rootUuid];
236
+
237
+ if (knownActionsForClip.length === 0) {
238
+ delete actionsByClip[clipUuid];
239
+ }
240
+
241
+ this._removeInactiveBindingsForAction(action);
242
+ }
243
+
244
+ _removeInactiveBindingsForAction(action) {
245
+ const bindings = action._propertyBindings;
246
+
247
+ for (let i = 0, n = bindings.length; i !== n; ++i) {
248
+ const binding = bindings[i];
249
+
250
+ if (--binding.referenceCount === 0) {
251
+ this._removeInactiveBinding(binding);
252
+ }
253
+ }
254
+ }
255
+
256
+ _lendAction(action) {
257
+ // [ active actions | inactive actions ]
258
+ // [ active actions >| inactive actions ]
259
+ // s a
260
+ // <-swap->
261
+ // a s
262
+
263
+ const actions = this._actions,
264
+ prevIndex = action._cacheIndex,
265
+ lastActiveIndex = this._nActiveActions++,
266
+ firstInactiveAction = actions[lastActiveIndex];
267
+
268
+ action._cacheIndex = lastActiveIndex;
269
+ actions[lastActiveIndex] = action;
270
+
271
+ firstInactiveAction._cacheIndex = prevIndex;
272
+ actions[prevIndex] = firstInactiveAction;
273
+ }
274
+
275
+ _takeBackAction(action) {
276
+ // [ active actions | inactive actions ]
277
+ // [ active actions |< inactive actions ]
278
+ // a s
279
+ // <-swap->
280
+ // s a
281
+
282
+ const actions = this._actions,
283
+ prevIndex = action._cacheIndex,
284
+ firstInactiveIndex = --this._nActiveActions,
285
+ lastActiveAction = actions[firstInactiveIndex];
286
+
287
+ action._cacheIndex = firstInactiveIndex;
288
+ actions[firstInactiveIndex] = action;
289
+
290
+ lastActiveAction._cacheIndex = prevIndex;
291
+ actions[prevIndex] = lastActiveAction;
292
+ }
293
+
294
+ // Memory management for PropertyMixer objects
295
+
296
+ _addInactiveBinding(binding, rootUuid, trackName) {
297
+ const bindingsByRoot = this._bindingsByRootAndName,
298
+ bindings = this._bindings;
299
+
300
+ let bindingByName = bindingsByRoot[rootUuid];
301
+
302
+ if (bindingByName === undefined) {
303
+ bindingByName = {};
304
+ bindingsByRoot[rootUuid] = bindingByName;
305
+ }
306
+
307
+ bindingByName[trackName] = binding;
308
+
309
+ binding._cacheIndex = bindings.length;
310
+ bindings.push(binding);
311
+ }
312
+
313
+ _removeInactiveBinding(binding) {
314
+ const bindings = this._bindings,
315
+ propBinding = binding.binding,
316
+ rootUuid = propBinding.rootNode.uuid,
317
+ trackName = propBinding.path,
318
+ bindingsByRoot = this._bindingsByRootAndName,
319
+ bindingByName = bindingsByRoot[rootUuid],
320
+ lastInactiveBinding = bindings[bindings.length - 1],
321
+ cacheIndex = binding._cacheIndex;
322
+
323
+ lastInactiveBinding._cacheIndex = cacheIndex;
324
+ bindings[cacheIndex] = lastInactiveBinding;
325
+ bindings.pop();
326
+
327
+ delete bindingByName[trackName];
328
+
329
+ if (Object.keys(bindingByName).length === 0) {
330
+ delete bindingsByRoot[rootUuid];
331
+ }
332
+ }
333
+
334
+ _lendBinding(binding) {
335
+ const bindings = this._bindings,
336
+ prevIndex = binding._cacheIndex,
337
+ lastActiveIndex = this._nActiveBindings++,
338
+ firstInactiveBinding = bindings[lastActiveIndex];
339
+
340
+ binding._cacheIndex = lastActiveIndex;
341
+ bindings[lastActiveIndex] = binding;
342
+
343
+ firstInactiveBinding._cacheIndex = prevIndex;
344
+ bindings[prevIndex] = firstInactiveBinding;
345
+ }
346
+
347
+ _takeBackBinding(binding) {
348
+ const bindings = this._bindings,
349
+ prevIndex = binding._cacheIndex,
350
+ firstInactiveIndex = --this._nActiveBindings,
351
+ lastActiveBinding = bindings[firstInactiveIndex];
352
+
353
+ binding._cacheIndex = firstInactiveIndex;
354
+ bindings[firstInactiveIndex] = binding;
355
+
356
+ lastActiveBinding._cacheIndex = prevIndex;
357
+ bindings[prevIndex] = lastActiveBinding;
358
+ }
359
+
360
+ // Memory management of Interpolants for weight and time scale
361
+
362
+ _lendControlInterpolant() {
363
+ const interpolants = this._controlInterpolants,
364
+ lastActiveIndex = this._nActiveControlInterpolants++;
365
+
366
+ let interpolant = interpolants[lastActiveIndex];
367
+
368
+ if (interpolant === undefined) {
369
+ interpolant = new LinearInterpolant(new Float32Array(2), new Float32Array(2), 1, this._controlInterpolantsResultBuffer);
370
+
371
+ interpolant.__cacheIndex = lastActiveIndex;
372
+ interpolants[lastActiveIndex] = interpolant;
373
+ }
374
+
375
+ return interpolant;
376
+ }
377
+
378
+ _takeBackControlInterpolant(interpolant) {
379
+ const interpolants = this._controlInterpolants,
380
+ prevIndex = interpolant.__cacheIndex,
381
+ firstInactiveIndex = --this._nActiveControlInterpolants,
382
+ lastActiveInterpolant = interpolants[firstInactiveIndex];
383
+
384
+ interpolant.__cacheIndex = firstInactiveIndex;
385
+ interpolants[firstInactiveIndex] = interpolant;
386
+
387
+ lastActiveInterpolant.__cacheIndex = prevIndex;
388
+ interpolants[prevIndex] = lastActiveInterpolant;
389
+ }
390
+
391
+ // return an action for a clip optionally using a custom root target
392
+ // object (this method allocates a lot of dynamic memory in case a
393
+ // previously unknown clip/root combination is specified)
394
+ clipAction(clip, optionalRoot, blendMode) {
395
+ const root = optionalRoot || this._root,
396
+ rootUuid = root.uuid;
397
+
398
+ let clipObject = typeof clip === 'string' ? AnimationClip.findByName(root, clip) : clip;
399
+
400
+ const clipUuid = clipObject !== null ? clipObject.uuid : clip;
401
+
402
+ const actionsForClip = this._actionsByClip[clipUuid];
403
+ let prototypeAction = null;
404
+
405
+ if (blendMode === undefined) {
406
+ if (clipObject !== null) {
407
+ blendMode = clipObject.blendMode;
408
+ } else {
409
+ blendMode = NormalAnimationBlendMode;
410
+ }
411
+ }
412
+
413
+ if (actionsForClip !== undefined) {
414
+ const existingAction = actionsForClip.actionByRoot[rootUuid];
415
+
416
+ if (existingAction !== undefined && existingAction.blendMode === blendMode) {
417
+ return existingAction;
418
+ }
419
+
420
+ // we know the clip, so we don't have to parse all
421
+ // the bindings again but can just copy
422
+ prototypeAction = actionsForClip.knownActions[0];
423
+
424
+ // also, take the clip from the prototype action
425
+ if (clipObject === null) clipObject = prototypeAction._clip;
426
+ }
427
+
428
+ // clip must be known when specified via string
429
+ if (clipObject === null) return null;
430
+
431
+ // allocate all resources required to run it
432
+ const newAction = new AnimationAction(this, clipObject, optionalRoot, blendMode);
433
+
434
+ this._bindAction(newAction, prototypeAction);
435
+
436
+ // and make the action known to the memory manager
437
+ this._addInactiveAction(newAction, clipUuid, rootUuid);
438
+
439
+ return newAction;
440
+ }
441
+
442
+ // get an existing action
443
+ existingAction(clip, optionalRoot) {
444
+ const root = optionalRoot || this._root,
445
+ rootUuid = root.uuid,
446
+ clipObject = typeof clip === 'string' ? AnimationClip.findByName(root, clip) : clip,
447
+ clipUuid = clipObject ? clipObject.uuid : clip,
448
+ actionsForClip = this._actionsByClip[clipUuid];
449
+
450
+ if (actionsForClip !== undefined) {
451
+ return actionsForClip.actionByRoot[rootUuid] || null;
452
+ }
453
+
454
+ return null;
455
+ }
456
+
457
+ // deactivates all previously scheduled actions
458
+ stopAllAction() {
459
+ const actions = this._actions,
460
+ nActions = this._nActiveActions;
461
+
462
+ for (let i = nActions - 1; i >= 0; --i) {
463
+ actions[i].stop();
464
+ }
465
+
466
+ return this;
467
+ }
468
+
469
+ // advance the time and update apply the animation
470
+ update(deltaTime) {
471
+ deltaTime *= this.timeScale;
472
+
473
+ const actions = this._actions,
474
+ nActions = this._nActiveActions,
475
+ time = (this.time += deltaTime),
476
+ timeDirection = Math.sign(deltaTime),
477
+ accuIndex = (this._accuIndex ^= 1);
478
+
479
+ // run active actions
480
+
481
+ for (let i = 0; i !== nActions; ++i) {
482
+ const action = actions[i];
483
+
484
+ action._update(time, deltaTime, timeDirection, accuIndex);
485
+ }
486
+
487
+ // update scene graph
488
+
489
+ const bindings = this._bindings,
490
+ nBindings = this._nActiveBindings;
491
+
492
+ for (let i = 0; i !== nBindings; ++i) {
493
+ bindings[i].apply(accuIndex);
494
+ }
495
+
496
+ return this;
497
+ }
498
+
499
+ // Allows you to seek to a specific time in an animation.
500
+ setTime(timeInSeconds) {
501
+ this.time = 0; // Zero out time attribute for AnimationMixer object;
502
+ for (let i = 0; i < this._actions.length; i++) {
503
+ this._actions[i].time = 0; // Zero out time attribute for all associated AnimationAction objects.
504
+ }
505
+
506
+ return this.update(timeInSeconds); // Update used to set exact time. Returns "this" AnimationMixer object.
507
+ }
508
+
509
+ // return this mixer's root target object
510
+ getRoot() {
511
+ return this._root;
512
+ }
513
+
514
+ // free all resources specific to a particular clip
515
+ uncacheClip(clip) {
516
+ const actions = this._actions,
517
+ clipUuid = clip.uuid,
518
+ actionsByClip = this._actionsByClip,
519
+ actionsForClip = actionsByClip[clipUuid];
520
+
521
+ if (actionsForClip !== undefined) {
522
+ // note: just calling _removeInactiveAction would mess up the
523
+ // iteration state and also require updating the state we can
524
+ // just throw away
525
+
526
+ const actionsToRemove = actionsForClip.knownActions;
527
+
528
+ for (let i = 0, n = actionsToRemove.length; i !== n; ++i) {
529
+ const action = actionsToRemove[i];
530
+
531
+ this._deactivateAction(action);
532
+
533
+ const cacheIndex = action._cacheIndex,
534
+ lastInactiveAction = actions[actions.length - 1];
535
+
536
+ action._cacheIndex = null;
537
+ action._byClipCacheIndex = null;
538
+
539
+ lastInactiveAction._cacheIndex = cacheIndex;
540
+ actions[cacheIndex] = lastInactiveAction;
541
+ actions.pop();
542
+
543
+ this._removeInactiveBindingsForAction(action);
544
+ }
545
+
546
+ delete actionsByClip[clipUuid];
547
+ }
548
+ }
549
+
550
+ // free all resources specific to a particular root target object
551
+ uncacheRoot(root) {
552
+ const rootUuid = root.uuid,
553
+ actionsByClip = this._actionsByClip;
554
+
555
+ for (const clipUuid in actionsByClip) {
556
+ const actionByRoot = actionsByClip[clipUuid].actionByRoot,
557
+ action = actionByRoot[rootUuid];
558
+
559
+ if (action !== undefined) {
560
+ this._deactivateAction(action);
561
+ this._removeInactiveAction(action);
562
+ }
563
+ }
564
+
565
+ const bindingsByRoot = this._bindingsByRootAndName,
566
+ bindingByName = bindingsByRoot[rootUuid];
567
+
568
+ if (bindingByName !== undefined) {
569
+ for (const trackName in bindingByName) {
570
+ const binding = bindingByName[trackName];
571
+ binding.restoreOriginalState();
572
+ this._removeInactiveBinding(binding);
573
+ }
574
+ }
575
+ }
576
+
577
+ // remove a targeted clip from the cache
578
+ uncacheAction(clip, optionalRoot) {
579
+ const action = this.existingAction(clip, optionalRoot);
580
+
581
+ if (action !== null) {
582
+ this._deactivateAction(action);
583
+ this._removeInactiveAction(action);
584
+ }
585
+ }
586
+ }
587
+
588
+ AnimationMixer.prototype._controlInterpolantsResultBuffer = new Float32Array(1);
589
+
590
+ export { AnimationMixer };
backend/libs/three/animation/AnimationObjectGroup.d.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class AnimationObjectGroup {
2
+ constructor(...args: any[]);
3
+
4
+ uuid: string;
5
+ stats: {
6
+ bindingsPerObject: number;
7
+ objects: {
8
+ total: number;
9
+ inUse: number;
10
+ };
11
+ };
12
+ readonly isAnimationObjectGroup: true;
13
+
14
+ add(...args: any[]): void;
15
+ remove(...args: any[]): void;
16
+ uncache(...args: any[]): void;
17
+ }
backend/libs/three/animation/AnimationObjectGroup.js ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PropertyBinding } from './PropertyBinding.js';
2
+ import * as MathUtils from '../math/MathUtils.js';
3
+
4
+ /**
5
+ *
6
+ * A group of objects that receives a shared animation state.
7
+ *
8
+ * Usage:
9
+ *
10
+ * - Add objects you would otherwise pass as 'root' to the
11
+ * constructor or the .clipAction method of AnimationMixer.
12
+ *
13
+ * - Instead pass this object as 'root'.
14
+ *
15
+ * - You can also add and remove objects later when the mixer
16
+ * is running.
17
+ *
18
+ * Note:
19
+ *
20
+ * Objects of this class appear as one object to the mixer,
21
+ * so cache control of the individual objects must be done
22
+ * on the group.
23
+ *
24
+ * Limitation:
25
+ *
26
+ * - The animated properties must be compatible among the
27
+ * all objects in the group.
28
+ *
29
+ * - A single property can either be controlled through a
30
+ * target group or directly, but not both.
31
+ */
32
+
33
+ class AnimationObjectGroup {
34
+ constructor() {
35
+ this.uuid = MathUtils.generateUUID();
36
+
37
+ // cached objects followed by the active ones
38
+ this._objects = Array.prototype.slice.call(arguments);
39
+
40
+ this.nCachedObjects_ = 0; // threshold
41
+ // note: read by PropertyBinding.Composite
42
+
43
+ const indices = {};
44
+ this._indicesByUUID = indices; // for bookkeeping
45
+
46
+ for (let i = 0, n = arguments.length; i !== n; ++i) {
47
+ indices[arguments[i].uuid] = i;
48
+ }
49
+
50
+ this._paths = []; // inside: string
51
+ this._parsedPaths = []; // inside: { we don't care, here }
52
+ this._bindings = []; // inside: Array< PropertyBinding >
53
+ this._bindingsIndicesByPath = {}; // inside: indices in these arrays
54
+
55
+ const scope = this;
56
+
57
+ this.stats = {
58
+ objects: {
59
+ get total() {
60
+ return scope._objects.length;
61
+ },
62
+ get inUse() {
63
+ return this.total - scope.nCachedObjects_;
64
+ },
65
+ },
66
+ get bindingsPerObject() {
67
+ return scope._bindings.length;
68
+ },
69
+ };
70
+ }
71
+
72
+ add() {
73
+ const objects = this._objects,
74
+ indicesByUUID = this._indicesByUUID,
75
+ paths = this._paths,
76
+ parsedPaths = this._parsedPaths,
77
+ bindings = this._bindings,
78
+ nBindings = bindings.length;
79
+
80
+ let knownObject = undefined,
81
+ nObjects = objects.length,
82
+ nCachedObjects = this.nCachedObjects_;
83
+
84
+ for (let i = 0, n = arguments.length; i !== n; ++i) {
85
+ const object = arguments[i],
86
+ uuid = object.uuid;
87
+ let index = indicesByUUID[uuid];
88
+
89
+ if (index === undefined) {
90
+ // unknown object -> add it to the ACTIVE region
91
+
92
+ index = nObjects++;
93
+ indicesByUUID[uuid] = index;
94
+ objects.push(object);
95
+
96
+ // accounting is done, now do the same for all bindings
97
+
98
+ for (let j = 0, m = nBindings; j !== m; ++j) {
99
+ bindings[j].push(new PropertyBinding(object, paths[j], parsedPaths[j]));
100
+ }
101
+ } else if (index < nCachedObjects) {
102
+ knownObject = objects[index];
103
+
104
+ // move existing object to the ACTIVE region
105
+
106
+ const firstActiveIndex = --nCachedObjects,
107
+ lastCachedObject = objects[firstActiveIndex];
108
+
109
+ indicesByUUID[lastCachedObject.uuid] = index;
110
+ objects[index] = lastCachedObject;
111
+
112
+ indicesByUUID[uuid] = firstActiveIndex;
113
+ objects[firstActiveIndex] = object;
114
+
115
+ // accounting is done, now do the same for all bindings
116
+
117
+ for (let j = 0, m = nBindings; j !== m; ++j) {
118
+ const bindingsForPath = bindings[j],
119
+ lastCached = bindingsForPath[firstActiveIndex];
120
+
121
+ let binding = bindingsForPath[index];
122
+
123
+ bindingsForPath[index] = lastCached;
124
+
125
+ if (binding === undefined) {
126
+ // since we do not bother to create new bindings
127
+ // for objects that are cached, the binding may
128
+ // or may not exist
129
+
130
+ binding = new PropertyBinding(object, paths[j], parsedPaths[j]);
131
+ }
132
+
133
+ bindingsForPath[firstActiveIndex] = binding;
134
+ }
135
+ } else if (objects[index] !== knownObject) {
136
+ console.error(
137
+ 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +
138
+ 'detected. Clean the caches or recreate your infrastructure when reloading scenes.'
139
+ );
140
+ } // else the object is already where we want it to be
141
+ } // for arguments
142
+
143
+ this.nCachedObjects_ = nCachedObjects;
144
+ }
145
+
146
+ remove() {
147
+ const objects = this._objects,
148
+ indicesByUUID = this._indicesByUUID,
149
+ bindings = this._bindings,
150
+ nBindings = bindings.length;
151
+
152
+ let nCachedObjects = this.nCachedObjects_;
153
+
154
+ for (let i = 0, n = arguments.length; i !== n; ++i) {
155
+ const object = arguments[i],
156
+ uuid = object.uuid,
157
+ index = indicesByUUID[uuid];
158
+
159
+ if (index !== undefined && index >= nCachedObjects) {
160
+ // move existing object into the CACHED region
161
+
162
+ const lastCachedIndex = nCachedObjects++,
163
+ firstActiveObject = objects[lastCachedIndex];
164
+
165
+ indicesByUUID[firstActiveObject.uuid] = index;
166
+ objects[index] = firstActiveObject;
167
+
168
+ indicesByUUID[uuid] = lastCachedIndex;
169
+ objects[lastCachedIndex] = object;
170
+
171
+ // accounting is done, now do the same for all bindings
172
+
173
+ for (let j = 0, m = nBindings; j !== m; ++j) {
174
+ const bindingsForPath = bindings[j],
175
+ firstActive = bindingsForPath[lastCachedIndex],
176
+ binding = bindingsForPath[index];
177
+
178
+ bindingsForPath[index] = firstActive;
179
+ bindingsForPath[lastCachedIndex] = binding;
180
+ }
181
+ }
182
+ } // for arguments
183
+
184
+ this.nCachedObjects_ = nCachedObjects;
185
+ }
186
+
187
+ // remove & forget
188
+ uncache() {
189
+ const objects = this._objects,
190
+ indicesByUUID = this._indicesByUUID,
191
+ bindings = this._bindings,
192
+ nBindings = bindings.length;
193
+
194
+ let nCachedObjects = this.nCachedObjects_,
195
+ nObjects = objects.length;
196
+
197
+ for (let i = 0, n = arguments.length; i !== n; ++i) {
198
+ const object = arguments[i],
199
+ uuid = object.uuid,
200
+ index = indicesByUUID[uuid];
201
+
202
+ if (index !== undefined) {
203
+ delete indicesByUUID[uuid];
204
+
205
+ if (index < nCachedObjects) {
206
+ // object is cached, shrink the CACHED region
207
+
208
+ const firstActiveIndex = --nCachedObjects,
209
+ lastCachedObject = objects[firstActiveIndex],
210
+ lastIndex = --nObjects,
211
+ lastObject = objects[lastIndex];
212
+
213
+ // last cached object takes this object's place
214
+ indicesByUUID[lastCachedObject.uuid] = index;
215
+ objects[index] = lastCachedObject;
216
+
217
+ // last object goes to the activated slot and pop
218
+ indicesByUUID[lastObject.uuid] = firstActiveIndex;
219
+ objects[firstActiveIndex] = lastObject;
220
+ objects.pop();
221
+
222
+ // accounting is done, now do the same for all bindings
223
+
224
+ for (let j = 0, m = nBindings; j !== m; ++j) {
225
+ const bindingsForPath = bindings[j],
226
+ lastCached = bindingsForPath[firstActiveIndex],
227
+ last = bindingsForPath[lastIndex];
228
+
229
+ bindingsForPath[index] = lastCached;
230
+ bindingsForPath[firstActiveIndex] = last;
231
+ bindingsForPath.pop();
232
+ }
233
+ } else {
234
+ // object is active, just swap with the last and pop
235
+
236
+ const lastIndex = --nObjects,
237
+ lastObject = objects[lastIndex];
238
+
239
+ if (lastIndex > 0) {
240
+ indicesByUUID[lastObject.uuid] = index;
241
+ }
242
+
243
+ objects[index] = lastObject;
244
+ objects.pop();
245
+
246
+ // accounting is done, now do the same for all bindings
247
+
248
+ for (let j = 0, m = nBindings; j !== m; ++j) {
249
+ const bindingsForPath = bindings[j];
250
+
251
+ bindingsForPath[index] = bindingsForPath[lastIndex];
252
+ bindingsForPath.pop();
253
+ }
254
+ } // cached or active
255
+ } // if object is known
256
+ } // for arguments
257
+
258
+ this.nCachedObjects_ = nCachedObjects;
259
+ }
260
+
261
+ // Internal interface used by befriended PropertyBinding.Composite:
262
+
263
+ subscribe_(path, parsedPath) {
264
+ // returns an array of bindings for the given path that is changed
265
+ // according to the contained objects in the group
266
+
267
+ const indicesByPath = this._bindingsIndicesByPath;
268
+ let index = indicesByPath[path];
269
+ const bindings = this._bindings;
270
+
271
+ if (index !== undefined) return bindings[index];
272
+
273
+ const paths = this._paths,
274
+ parsedPaths = this._parsedPaths,
275
+ objects = this._objects,
276
+ nObjects = objects.length,
277
+ nCachedObjects = this.nCachedObjects_,
278
+ bindingsForPath = new Array(nObjects);
279
+
280
+ index = bindings.length;
281
+
282
+ indicesByPath[path] = index;
283
+
284
+ paths.push(path);
285
+ parsedPaths.push(parsedPath);
286
+ bindings.push(bindingsForPath);
287
+
288
+ for (let i = nCachedObjects, n = objects.length; i !== n; ++i) {
289
+ const object = objects[i];
290
+ bindingsForPath[i] = new PropertyBinding(object, path, parsedPath);
291
+ }
292
+
293
+ return bindingsForPath;
294
+ }
295
+
296
+ unsubscribe_(path) {
297
+ // tells the group to forget about a property path and no longer
298
+ // update the array previously obtained with 'subscribe_'
299
+
300
+ const indicesByPath = this._bindingsIndicesByPath,
301
+ index = indicesByPath[path];
302
+
303
+ if (index !== undefined) {
304
+ const paths = this._paths,
305
+ parsedPaths = this._parsedPaths,
306
+ bindings = this._bindings,
307
+ lastBindingsIndex = bindings.length - 1,
308
+ lastBindings = bindings[lastBindingsIndex],
309
+ lastBindingsPath = path[lastBindingsIndex];
310
+
311
+ indicesByPath[lastBindingsPath] = index;
312
+
313
+ bindings[index] = lastBindings;
314
+ bindings.pop();
315
+
316
+ parsedPaths[index] = parsedPaths[lastBindingsIndex];
317
+ parsedPaths.pop();
318
+
319
+ paths[index] = paths[lastBindingsIndex];
320
+ paths.pop();
321
+ }
322
+ }
323
+ }
324
+
325
+ AnimationObjectGroup.prototype.isAnimationObjectGroup = true;
326
+
327
+ export { AnimationObjectGroup };
backend/libs/three/animation/AnimationUtils.d.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AnimationClip } from './AnimationClip';
2
+
3
+ export namespace AnimationUtils {
4
+ function arraySlice(array: any, from: number, to: number): any;
5
+ function convertArray(array: any, type: any, forceClone: boolean): any;
6
+ function isTypedArray(object: any): boolean;
7
+ function getKeyframeOrder(times: number[]): number[];
8
+ function sortedArray(values: any[], stride: number, order: number[]): any[];
9
+ function flattenJSON(jsonKeys: string[], times: any[], values: any[], valuePropertyName: string): void;
10
+
11
+ /**
12
+ * @param sourceClip
13
+ * @param name
14
+ * @param startFrame
15
+ * @param endFrame
16
+ * @param [fps=30]
17
+ */
18
+ function subclip(sourceClip: AnimationClip, name: string, startFrame: number, endFrame: number, fps?: number): AnimationClip;
19
+
20
+ /**
21
+ * @param targetClip
22
+ * @param [referenceFrame=0]
23
+ * @param [referenceClip=targetClip]
24
+ * @param [fps=30]
25
+ */
26
+ function makeClipAdditive(targetClip: AnimationClip, referenceFrame?: number, referenceClip?: AnimationClip, fps?: number): AnimationClip;
27
+ }
backend/libs/three/animation/AnimationUtils.js ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Quaternion } from '../math/Quaternion.js';
2
+ import { AdditiveAnimationBlendMode } from '../constants.js';
3
+
4
+ const AnimationUtils = {
5
+ // same as Array.prototype.slice, but also works on typed arrays
6
+ arraySlice: function (array, from, to) {
7
+ if (AnimationUtils.isTypedArray(array)) {
8
+ // in ios9 array.subarray(from, undefined) will return empty array
9
+ // but array.subarray(from) or array.subarray(from, len) is correct
10
+ return new array.constructor(array.subarray(from, to !== undefined ? to : array.length));
11
+ }
12
+
13
+ return array.slice(from, to);
14
+ },
15
+
16
+ // converts an array to a specific type
17
+ convertArray: function (array, type, forceClone) {
18
+ if (
19
+ !array || // let 'undefined' and 'null' pass
20
+ (!forceClone && array.constructor === type)
21
+ )
22
+ return array;
23
+
24
+ if (typeof type.BYTES_PER_ELEMENT === 'number') {
25
+ return new type(array); // create typed array
26
+ }
27
+
28
+ return Array.prototype.slice.call(array); // create Array
29
+ },
30
+
31
+ isTypedArray: function (object) {
32
+ return ArrayBuffer.isView(object) && !(object instanceof DataView);
33
+ },
34
+
35
+ // returns an array by which times and values can be sorted
36
+ getKeyframeOrder: function (times) {
37
+ function compareTime(i, j) {
38
+ return times[i] - times[j];
39
+ }
40
+
41
+ const n = times.length;
42
+ const result = new Array(n);
43
+ for (let i = 0; i !== n; ++i) result[i] = i;
44
+
45
+ result.sort(compareTime);
46
+
47
+ return result;
48
+ },
49
+
50
+ // uses the array previously returned by 'getKeyframeOrder' to sort data
51
+ sortedArray: function (values, stride, order) {
52
+ const nValues = values.length;
53
+ const result = new values.constructor(nValues);
54
+
55
+ for (let i = 0, dstOffset = 0; dstOffset !== nValues; ++i) {
56
+ const srcOffset = order[i] * stride;
57
+
58
+ for (let j = 0; j !== stride; ++j) {
59
+ result[dstOffset++] = values[srcOffset + j];
60
+ }
61
+ }
62
+
63
+ return result;
64
+ },
65
+
66
+ // function for parsing AOS keyframe formats
67
+ flattenJSON: function (jsonKeys, times, values, valuePropertyName) {
68
+ let i = 1,
69
+ key = jsonKeys[0];
70
+
71
+ while (key !== undefined && key[valuePropertyName] === undefined) {
72
+ key = jsonKeys[i++];
73
+ }
74
+
75
+ if (key === undefined) return; // no data
76
+
77
+ let value = key[valuePropertyName];
78
+ if (value === undefined) return; // no data
79
+
80
+ if (Array.isArray(value)) {
81
+ do {
82
+ value = key[valuePropertyName];
83
+
84
+ if (value !== undefined) {
85
+ times.push(key.time);
86
+ values.push.apply(values, value); // push all elements
87
+ }
88
+
89
+ key = jsonKeys[i++];
90
+ } while (key !== undefined);
91
+ } else if (value.toArray !== undefined) {
92
+ // ...assume THREE.Math-ish
93
+
94
+ do {
95
+ value = key[valuePropertyName];
96
+
97
+ if (value !== undefined) {
98
+ times.push(key.time);
99
+ value.toArray(values, values.length);
100
+ }
101
+
102
+ key = jsonKeys[i++];
103
+ } while (key !== undefined);
104
+ } else {
105
+ // otherwise push as-is
106
+
107
+ do {
108
+ value = key[valuePropertyName];
109
+
110
+ if (value !== undefined) {
111
+ times.push(key.time);
112
+ values.push(value);
113
+ }
114
+
115
+ key = jsonKeys[i++];
116
+ } while (key !== undefined);
117
+ }
118
+ },
119
+
120
+ subclip: function (sourceClip, name, startFrame, endFrame, fps = 30) {
121
+ const clip = sourceClip.clone();
122
+
123
+ clip.name = name;
124
+
125
+ const tracks = [];
126
+
127
+ for (let i = 0; i < clip.tracks.length; ++i) {
128
+ const track = clip.tracks[i];
129
+ const valueSize = track.getValueSize();
130
+
131
+ const times = [];
132
+ const values = [];
133
+
134
+ for (let j = 0; j < track.times.length; ++j) {
135
+ const frame = track.times[j] * fps;
136
+
137
+ if (frame < startFrame || frame >= endFrame) continue;
138
+
139
+ times.push(track.times[j]);
140
+
141
+ for (let k = 0; k < valueSize; ++k) {
142
+ values.push(track.values[j * valueSize + k]);
143
+ }
144
+ }
145
+
146
+ if (times.length === 0) continue;
147
+
148
+ track.times = AnimationUtils.convertArray(times, track.times.constructor);
149
+ track.values = AnimationUtils.convertArray(values, track.values.constructor);
150
+
151
+ tracks.push(track);
152
+ }
153
+
154
+ clip.tracks = tracks;
155
+
156
+ // find minimum .times value across all tracks in the trimmed clip
157
+
158
+ let minStartTime = Infinity;
159
+
160
+ for (let i = 0; i < clip.tracks.length; ++i) {
161
+ if (minStartTime > clip.tracks[i].times[0]) {
162
+ minStartTime = clip.tracks[i].times[0];
163
+ }
164
+ }
165
+
166
+ // shift all tracks such that clip begins at t=0
167
+
168
+ for (let i = 0; i < clip.tracks.length; ++i) {
169
+ clip.tracks[i].shift(-1 * minStartTime);
170
+ }
171
+
172
+ clip.resetDuration();
173
+
174
+ return clip;
175
+ },
176
+
177
+ makeClipAdditive: function (targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30) {
178
+ if (fps <= 0) fps = 30;
179
+
180
+ const numTracks = referenceClip.tracks.length;
181
+ const referenceTime = referenceFrame / fps;
182
+
183
+ // Make each track's values relative to the values at the reference frame
184
+ for (let i = 0; i < numTracks; ++i) {
185
+ const referenceTrack = referenceClip.tracks[i];
186
+ const referenceTrackType = referenceTrack.ValueTypeName;
187
+
188
+ // Skip this track if it's non-numeric
189
+ if (referenceTrackType === 'bool' || referenceTrackType === 'string') continue;
190
+
191
+ // Find the track in the target clip whose name and type matches the reference track
192
+ const targetTrack = targetClip.tracks.find(function (track) {
193
+ return track.name === referenceTrack.name && track.ValueTypeName === referenceTrackType;
194
+ });
195
+
196
+ if (targetTrack === undefined) continue;
197
+
198
+ let referenceOffset = 0;
199
+ const referenceValueSize = referenceTrack.getValueSize();
200
+
201
+ if (referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) {
202
+ referenceOffset = referenceValueSize / 3;
203
+ }
204
+
205
+ let targetOffset = 0;
206
+ const targetValueSize = targetTrack.getValueSize();
207
+
208
+ if (targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) {
209
+ targetOffset = targetValueSize / 3;
210
+ }
211
+
212
+ const lastIndex = referenceTrack.times.length - 1;
213
+ let referenceValue;
214
+
215
+ // Find the value to subtract out of the track
216
+ if (referenceTime <= referenceTrack.times[0]) {
217
+ // Reference frame is earlier than the first keyframe, so just use the first keyframe
218
+ const startIndex = referenceOffset;
219
+ const endIndex = referenceValueSize - referenceOffset;
220
+ referenceValue = AnimationUtils.arraySlice(referenceTrack.values, startIndex, endIndex);
221
+ } else if (referenceTime >= referenceTrack.times[lastIndex]) {
222
+ // Reference frame is after the last keyframe, so just use the last keyframe
223
+ const startIndex = lastIndex * referenceValueSize + referenceOffset;
224
+ const endIndex = startIndex + referenceValueSize - referenceOffset;
225
+ referenceValue = AnimationUtils.arraySlice(referenceTrack.values, startIndex, endIndex);
226
+ } else {
227
+ // Interpolate to the reference value
228
+ const interpolant = referenceTrack.createInterpolant();
229
+ const startIndex = referenceOffset;
230
+ const endIndex = referenceValueSize - referenceOffset;
231
+ interpolant.evaluate(referenceTime);
232
+ referenceValue = AnimationUtils.arraySlice(interpolant.resultBuffer, startIndex, endIndex);
233
+ }
234
+
235
+ // Conjugate the quaternion
236
+ if (referenceTrackType === 'quaternion') {
237
+ const referenceQuat = new Quaternion().fromArray(referenceValue).normalize().conjugate();
238
+ referenceQuat.toArray(referenceValue);
239
+ }
240
+
241
+ // Subtract the reference value from all of the track values
242
+
243
+ const numTimes = targetTrack.times.length;
244
+ for (let j = 0; j < numTimes; ++j) {
245
+ const valueStart = j * targetValueSize + targetOffset;
246
+
247
+ if (referenceTrackType === 'quaternion') {
248
+ // Multiply the conjugate for quaternion track types
249
+ Quaternion.multiplyQuaternionsFlat(targetTrack.values, valueStart, referenceValue, 0, targetTrack.values, valueStart);
250
+ } else {
251
+ const valueEnd = targetValueSize - targetOffset * 2;
252
+
253
+ // Subtract each value for all other numeric track types
254
+ for (let k = 0; k < valueEnd; ++k) {
255
+ targetTrack.values[valueStart + k] -= referenceValue[k];
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ targetClip.blendMode = AdditiveAnimationBlendMode;
262
+
263
+ return targetClip;
264
+ },
265
+ };
266
+
267
+ export { AnimationUtils };
backend/libs/three/animation/KeyframeTrack.d.ts ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DiscreteInterpolant } from './../math/interpolants/DiscreteInterpolant';
2
+ import { LinearInterpolant } from './../math/interpolants/LinearInterpolant';
3
+ import { CubicInterpolant } from './../math/interpolants/CubicInterpolant';
4
+ import { InterpolationModes } from '../constants';
5
+
6
+ export class KeyframeTrack {
7
+ /**
8
+ * @param name
9
+ * @param times
10
+ * @param values
11
+ * @param [interpolation=THREE.InterpolateLinear]
12
+ */
13
+ constructor(name: string, times: ArrayLike<any>, values: ArrayLike<any>, interpolation?: InterpolationModes);
14
+
15
+ name: string;
16
+ times: Float32Array;
17
+ values: Float32Array;
18
+
19
+ ValueTypeName: string;
20
+ TimeBufferType: Float32Array;
21
+ ValueBufferType: Float32Array;
22
+
23
+ /**
24
+ * @default THREE.InterpolateLinear
25
+ */
26
+ DefaultInterpolation: InterpolationModes;
27
+
28
+ InterpolantFactoryMethodDiscrete(result: any): DiscreteInterpolant;
29
+ InterpolantFactoryMethodLinear(result: any): LinearInterpolant;
30
+ InterpolantFactoryMethodSmooth(result: any): CubicInterpolant;
31
+
32
+ setInterpolation(interpolation: InterpolationModes): KeyframeTrack;
33
+ getInterpolation(): InterpolationModes;
34
+
35
+ getValueSize(): number;
36
+
37
+ shift(timeOffset: number): KeyframeTrack;
38
+ scale(timeScale: number): KeyframeTrack;
39
+ trim(startTime: number, endTime: number): KeyframeTrack;
40
+ validate(): boolean;
41
+ optimize(): KeyframeTrack;
42
+ clone(): this;
43
+
44
+ static toJSON(track: KeyframeTrack): any;
45
+ }
backend/libs/three/animation/KeyframeTrack.js ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { InterpolateLinear, InterpolateSmooth, InterpolateDiscrete } from '../constants.js';
2
+ import { CubicInterpolant } from '../math/interpolants/CubicInterpolant.js';
3
+ import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
4
+ import { DiscreteInterpolant } from '../math/interpolants/DiscreteInterpolant.js';
5
+ import { AnimationUtils } from './AnimationUtils.js';
6
+
7
+ class KeyframeTrack {
8
+ constructor(name, times, values, interpolation) {
9
+ if (name === undefined) throw new Error('THREE.KeyframeTrack: track name is undefined');
10
+ if (times === undefined || times.length === 0) throw new Error('THREE.KeyframeTrack: no keyframes in track named ' + name);
11
+
12
+ this.name = name;
13
+
14
+ this.times = AnimationUtils.convertArray(times, this.TimeBufferType);
15
+ this.values = AnimationUtils.convertArray(values, this.ValueBufferType);
16
+
17
+ this.setInterpolation(interpolation || this.DefaultInterpolation);
18
+ }
19
+
20
+ // Serialization (in static context, because of constructor invocation
21
+ // and automatic invocation of .toJSON):
22
+
23
+ static toJSON(track) {
24
+ const trackType = track.constructor;
25
+
26
+ let json;
27
+
28
+ // derived classes can define a static toJSON method
29
+ if (trackType.toJSON !== this.toJSON) {
30
+ json = trackType.toJSON(track);
31
+ } else {
32
+ // by default, we assume the data can be serialized as-is
33
+ json = {
34
+ name: track.name,
35
+ times: AnimationUtils.convertArray(track.times, Array),
36
+ values: AnimationUtils.convertArray(track.values, Array),
37
+ };
38
+
39
+ const interpolation = track.getInterpolation();
40
+
41
+ if (interpolation !== track.DefaultInterpolation) {
42
+ json.interpolation = interpolation;
43
+ }
44
+ }
45
+
46
+ json.type = track.ValueTypeName; // mandatory
47
+
48
+ return json;
49
+ }
50
+
51
+ InterpolantFactoryMethodDiscrete(result) {
52
+ return new DiscreteInterpolant(this.times, this.values, this.getValueSize(), result);
53
+ }
54
+
55
+ InterpolantFactoryMethodLinear(result) {
56
+ return new LinearInterpolant(this.times, this.values, this.getValueSize(), result);
57
+ }
58
+
59
+ InterpolantFactoryMethodSmooth(result) {
60
+ return new CubicInterpolant(this.times, this.values, this.getValueSize(), result);
61
+ }
62
+
63
+ setInterpolation(interpolation) {
64
+ let factoryMethod;
65
+
66
+ switch (interpolation) {
67
+ case InterpolateDiscrete:
68
+ factoryMethod = this.InterpolantFactoryMethodDiscrete;
69
+
70
+ break;
71
+
72
+ case InterpolateLinear:
73
+ factoryMethod = this.InterpolantFactoryMethodLinear;
74
+
75
+ break;
76
+
77
+ case InterpolateSmooth:
78
+ factoryMethod = this.InterpolantFactoryMethodSmooth;
79
+
80
+ break;
81
+ }
82
+
83
+ if (factoryMethod === undefined) {
84
+ const message = 'unsupported interpolation for ' + this.ValueTypeName + ' keyframe track named ' + this.name;
85
+
86
+ if (this.createInterpolant === undefined) {
87
+ // fall back to default, unless the default itself is messed up
88
+ if (interpolation !== this.DefaultInterpolation) {
89
+ this.setInterpolation(this.DefaultInterpolation);
90
+ } else {
91
+ throw new Error(message); // fatal, in this case
92
+ }
93
+ }
94
+
95
+ console.warn('THREE.KeyframeTrack:', message);
96
+ return this;
97
+ }
98
+
99
+ this.createInterpolant = factoryMethod;
100
+
101
+ return this;
102
+ }
103
+
104
+ getInterpolation() {
105
+ switch (this.createInterpolant) {
106
+ case this.InterpolantFactoryMethodDiscrete:
107
+ return InterpolateDiscrete;
108
+
109
+ case this.InterpolantFactoryMethodLinear:
110
+ return InterpolateLinear;
111
+
112
+ case this.InterpolantFactoryMethodSmooth:
113
+ return InterpolateSmooth;
114
+ }
115
+ }
116
+
117
+ getValueSize() {
118
+ return this.values.length / this.times.length;
119
+ }
120
+
121
+ // move all keyframes either forwards or backwards in time
122
+ shift(timeOffset) {
123
+ if (timeOffset !== 0.0) {
124
+ const times = this.times;
125
+
126
+ for (let i = 0, n = times.length; i !== n; ++i) {
127
+ times[i] += timeOffset;
128
+ }
129
+ }
130
+
131
+ return this;
132
+ }
133
+
134
+ // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
135
+ scale(timeScale) {
136
+ if (timeScale !== 1.0) {
137
+ const times = this.times;
138
+
139
+ for (let i = 0, n = times.length; i !== n; ++i) {
140
+ times[i] *= timeScale;
141
+ }
142
+ }
143
+
144
+ return this;
145
+ }
146
+
147
+ // removes keyframes before and after animation without changing any values within the range [startTime, endTime].
148
+ // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
149
+ trim(startTime, endTime) {
150
+ const times = this.times,
151
+ nKeys = times.length;
152
+
153
+ let from = 0,
154
+ to = nKeys - 1;
155
+
156
+ while (from !== nKeys && times[from] < startTime) {
157
+ ++from;
158
+ }
159
+
160
+ while (to !== -1 && times[to] > endTime) {
161
+ --to;
162
+ }
163
+
164
+ ++to; // inclusive -> exclusive bound
165
+
166
+ if (from !== 0 || to !== nKeys) {
167
+ // empty tracks are forbidden, so keep at least one keyframe
168
+ if (from >= to) {
169
+ to = Math.max(to, 1);
170
+ from = to - 1;
171
+ }
172
+
173
+ const stride = this.getValueSize();
174
+ this.times = AnimationUtils.arraySlice(times, from, to);
175
+ this.values = AnimationUtils.arraySlice(this.values, from * stride, to * stride);
176
+ }
177
+
178
+ return this;
179
+ }
180
+
181
+ // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
182
+ validate() {
183
+ let valid = true;
184
+
185
+ const valueSize = this.getValueSize();
186
+ if (valueSize - Math.floor(valueSize) !== 0) {
187
+ console.error('THREE.KeyframeTrack: Invalid value size in track.', this);
188
+ valid = false;
189
+ }
190
+
191
+ const times = this.times,
192
+ values = this.values,
193
+ nKeys = times.length;
194
+
195
+ if (nKeys === 0) {
196
+ console.error('THREE.KeyframeTrack: Track is empty.', this);
197
+ valid = false;
198
+ }
199
+
200
+ let prevTime = null;
201
+
202
+ for (let i = 0; i !== nKeys; i++) {
203
+ const currTime = times[i];
204
+
205
+ if (typeof currTime === 'number' && isNaN(currTime)) {
206
+ console.error('THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime);
207
+ valid = false;
208
+ break;
209
+ }
210
+
211
+ if (prevTime !== null && prevTime > currTime) {
212
+ console.error('THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime);
213
+ valid = false;
214
+ break;
215
+ }
216
+
217
+ prevTime = currTime;
218
+ }
219
+
220
+ if (values !== undefined) {
221
+ if (AnimationUtils.isTypedArray(values)) {
222
+ for (let i = 0, n = values.length; i !== n; ++i) {
223
+ const value = values[i];
224
+
225
+ if (isNaN(value)) {
226
+ console.error('THREE.KeyframeTrack: Value is not a valid number.', this, i, value);
227
+ valid = false;
228
+ break;
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ return valid;
235
+ }
236
+
237
+ // removes equivalent sequential keys as common in morph target sequences
238
+ // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
239
+ optimize() {
240
+ // times or values may be shared with other tracks, so overwriting is unsafe
241
+ const times = AnimationUtils.arraySlice(this.times),
242
+ values = AnimationUtils.arraySlice(this.values),
243
+ stride = this.getValueSize(),
244
+ smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
245
+ lastIndex = times.length - 1;
246
+
247
+ let writeIndex = 1;
248
+
249
+ for (let i = 1; i < lastIndex; ++i) {
250
+ let keep = false;
251
+
252
+ const time = times[i];
253
+ const timeNext = times[i + 1];
254
+
255
+ // remove adjacent keyframes scheduled at the same time
256
+
257
+ if (time !== timeNext && (i !== 1 || time !== times[0])) {
258
+ if (!smoothInterpolation) {
259
+ // remove unnecessary keyframes same as their neighbors
260
+
261
+ const offset = i * stride,
262
+ offsetP = offset - stride,
263
+ offsetN = offset + stride;
264
+
265
+ for (let j = 0; j !== stride; ++j) {
266
+ const value = values[offset + j];
267
+
268
+ if (value !== values[offsetP + j] || value !== values[offsetN + j]) {
269
+ keep = true;
270
+ break;
271
+ }
272
+ }
273
+ } else {
274
+ keep = true;
275
+ }
276
+ }
277
+
278
+ // in-place compaction
279
+
280
+ if (keep) {
281
+ if (i !== writeIndex) {
282
+ times[writeIndex] = times[i];
283
+
284
+ const readOffset = i * stride,
285
+ writeOffset = writeIndex * stride;
286
+
287
+ for (let j = 0; j !== stride; ++j) {
288
+ values[writeOffset + j] = values[readOffset + j];
289
+ }
290
+ }
291
+
292
+ ++writeIndex;
293
+ }
294
+ }
295
+
296
+ // flush last keyframe (compaction looks ahead)
297
+
298
+ if (lastIndex > 0) {
299
+ times[writeIndex] = times[lastIndex];
300
+
301
+ for (let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++j) {
302
+ values[writeOffset + j] = values[readOffset + j];
303
+ }
304
+
305
+ ++writeIndex;
306
+ }
307
+
308
+ if (writeIndex !== times.length) {
309
+ this.times = AnimationUtils.arraySlice(times, 0, writeIndex);
310
+ this.values = AnimationUtils.arraySlice(values, 0, writeIndex * stride);
311
+ } else {
312
+ this.times = times;
313
+ this.values = values;
314
+ }
315
+
316
+ return this;
317
+ }
318
+
319
+ clone() {
320
+ const times = AnimationUtils.arraySlice(this.times, 0);
321
+ const values = AnimationUtils.arraySlice(this.values, 0);
322
+
323
+ const TypedKeyframeTrack = this.constructor;
324
+ const track = new TypedKeyframeTrack(this.name, times, values);
325
+
326
+ // Interpolant argument to constructor is not saved, so copy the factory method directly.
327
+ track.createInterpolant = this.createInterpolant;
328
+
329
+ return track;
330
+ }
331
+ }
332
+
333
+ KeyframeTrack.prototype.TimeBufferType = Float32Array;
334
+ KeyframeTrack.prototype.ValueBufferType = Float32Array;
335
+ KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear;
336
+
337
+ export { KeyframeTrack };
backend/libs/three/animation/PropertyBinding.d.ts ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface ParseTrackNameResults {
2
+ nodeName: string;
3
+ objectName: string;
4
+ objectIndex: string;
5
+ propertyName: string;
6
+ propertyIndex: string;
7
+ }
8
+
9
+ export class PropertyBinding {
10
+ constructor(rootNode: any, path: string, parsedPath?: any);
11
+
12
+ path: string;
13
+ parsedPath: any;
14
+ node: any;
15
+ rootNode: any;
16
+
17
+ getValue(targetArray: any, offset: number): any;
18
+ setValue(sourceArray: any, offset: number): void;
19
+ bind(): void;
20
+ unbind(): void;
21
+
22
+ BindingType: { [bindingType: string]: number };
23
+ Versioning: { [versioning: string]: number };
24
+
25
+ GetterByBindingType: Array<() => void>;
26
+ SetterByBindingTypeAndVersioning: Array<Array<() => void>>;
27
+
28
+ static create(root: any, path: any, parsedPath?: any): PropertyBinding | PropertyBinding.Composite;
29
+ static sanitizeNodeName(name: string): string;
30
+ static parseTrackName(trackName: string): ParseTrackNameResults;
31
+ static findNode(root: any, nodeName: string): any;
32
+ }
33
+
34
+ export namespace PropertyBinding {
35
+ class Composite {
36
+ constructor(targetGroup: any, path: any, parsedPath?: any);
37
+
38
+ getValue(array: any, offset: number): any;
39
+ setValue(array: any, offset: number): void;
40
+ bind(): void;
41
+ unbind(): void;
42
+ }
43
+ }
backend/libs/three/animation/PropertyBinding.js ADDED
@@ -0,0 +1,534 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Characters [].:/ are reserved for track binding syntax.
2
+ const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
3
+ const _reservedRe = new RegExp('[' + _RESERVED_CHARS_RE + ']', 'g');
4
+
5
+ // Attempts to allow node names from any language. ES5's `\w` regexp matches
6
+ // only latin characters, and the unicode \p{L} is not yet supported. So
7
+ // instead, we exclude reserved characters and match everything else.
8
+ const _wordChar = '[^' + _RESERVED_CHARS_RE + ']';
9
+ const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace('\\.', '') + ']';
10
+
11
+ // Parent directories, delimited by '/' or ':'. Currently unused, but must
12
+ // be matched to parse the rest of the track name.
13
+ const _directoryRe = /((?:WC+[\/:])*)/.source.replace('WC', _wordChar);
14
+
15
+ // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
16
+ const _nodeRe = /(WCOD+)?/.source.replace('WCOD', _wordCharOrDot);
17
+
18
+ // Object on target node, and accessor. May not contain reserved
19
+ // characters. Accessor may contain any character except closing bracket.
20
+ const _objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace('WC', _wordChar);
21
+
22
+ // Property and accessor. May not contain reserved characters. Accessor may
23
+ // contain any non-bracket characters.
24
+ const _propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace('WC', _wordChar);
25
+
26
+ const _trackRe = new RegExp('' + '^' + _directoryRe + _nodeRe + _objectRe + _propertyRe + '$');
27
+
28
+ const _supportedObjectNames = ['material', 'materials', 'bones'];
29
+
30
+ class Composite {
31
+ constructor(targetGroup, path, optionalParsedPath) {
32
+ const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName(path);
33
+
34
+ this._targetGroup = targetGroup;
35
+ this._bindings = targetGroup.subscribe_(path, parsedPath);
36
+ }
37
+
38
+ getValue(array, offset) {
39
+ this.bind(); // bind all binding
40
+
41
+ const firstValidIndex = this._targetGroup.nCachedObjects_,
42
+ binding = this._bindings[firstValidIndex];
43
+
44
+ // and only call .getValue on the first
45
+ if (binding !== undefined) binding.getValue(array, offset);
46
+ }
47
+
48
+ setValue(array, offset) {
49
+ const bindings = this._bindings;
50
+
51
+ for (let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++i) {
52
+ bindings[i].setValue(array, offset);
53
+ }
54
+ }
55
+
56
+ bind() {
57
+ const bindings = this._bindings;
58
+
59
+ for (let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++i) {
60
+ bindings[i].bind();
61
+ }
62
+ }
63
+
64
+ unbind() {
65
+ const bindings = this._bindings;
66
+
67
+ for (let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++i) {
68
+ bindings[i].unbind();
69
+ }
70
+ }
71
+ }
72
+
73
+ // Note: This class uses a State pattern on a per-method basis:
74
+ // 'bind' sets 'this.getValue' / 'setValue' and shadows the
75
+ // prototype version of these methods with one that represents
76
+ // the bound state. When the property is not found, the methods
77
+ // become no-ops.
78
+ class PropertyBinding {
79
+ constructor(rootNode, path, parsedPath) {
80
+ this.path = path;
81
+ this.parsedPath = parsedPath || PropertyBinding.parseTrackName(path);
82
+
83
+ this.node = PropertyBinding.findNode(rootNode, this.parsedPath.nodeName) || rootNode;
84
+
85
+ this.rootNode = rootNode;
86
+
87
+ // initial state of these methods that calls 'bind'
88
+ this.getValue = this._getValue_unbound;
89
+ this.setValue = this._setValue_unbound;
90
+ }
91
+
92
+ static create(root, path, parsedPath) {
93
+ if (!(root && root.isAnimationObjectGroup)) {
94
+ return new PropertyBinding(root, path, parsedPath);
95
+ } else {
96
+ return new PropertyBinding.Composite(root, path, parsedPath);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Replaces spaces with underscores and removes unsupported characters from
102
+ * node names, to ensure compatibility with parseTrackName().
103
+ *
104
+ * @param {string} name Node name to be sanitized.
105
+ * @return {string}
106
+ */
107
+ static sanitizeNodeName(name) {
108
+ return name.replace(/\s/g, '_').replace(_reservedRe, '');
109
+ }
110
+
111
+ static parseTrackName(trackName) {
112
+ const matches = _trackRe.exec(trackName);
113
+
114
+ if (!matches) {
115
+ throw new Error('PropertyBinding: Cannot parse trackName: ' + trackName);
116
+ }
117
+
118
+ const results = {
119
+ // directoryName: matches[ 1 ], // (tschw) currently unused
120
+ nodeName: matches[2],
121
+ objectName: matches[3],
122
+ objectIndex: matches[4],
123
+ propertyName: matches[5], // required
124
+ propertyIndex: matches[6],
125
+ };
126
+
127
+ const lastDot = results.nodeName && results.nodeName.lastIndexOf('.');
128
+
129
+ if (lastDot !== undefined && lastDot !== -1) {
130
+ const objectName = results.nodeName.substring(lastDot + 1);
131
+
132
+ // Object names must be checked against an allowlist. Otherwise, there
133
+ // is no way to parse 'foo.bar.baz': 'baz' must be a property, but
134
+ // 'bar' could be the objectName, or part of a nodeName (which can
135
+ // include '.' characters).
136
+ if (_supportedObjectNames.indexOf(objectName) !== -1) {
137
+ results.nodeName = results.nodeName.substring(0, lastDot);
138
+ results.objectName = objectName;
139
+ }
140
+ }
141
+
142
+ if (results.propertyName === null || results.propertyName.length === 0) {
143
+ throw new Error('PropertyBinding: can not parse propertyName from trackName: ' + trackName);
144
+ }
145
+
146
+ return results;
147
+ }
148
+
149
+ static findNode(root, nodeName) {
150
+ if (!nodeName || nodeName === '' || nodeName === '.' || nodeName === -1 || nodeName === root.name || nodeName === root.uuid) {
151
+ return root;
152
+ }
153
+
154
+ // search into skeleton bones.
155
+ if (root.skeleton) {
156
+ const bone = root.skeleton.getBoneByName(nodeName);
157
+
158
+ if (bone !== undefined) {
159
+ return bone;
160
+ }
161
+ }
162
+
163
+ // search into node subtree.
164
+ if (root.children) {
165
+ const searchNodeSubtree = function (children) {
166
+ for (let i = 0; i < children.length; i++) {
167
+ const childNode = children[i];
168
+
169
+ if (childNode.name === nodeName || childNode.uuid === nodeName) {
170
+ return childNode;
171
+ }
172
+
173
+ const result = searchNodeSubtree(childNode.children);
174
+
175
+ if (result) return result;
176
+ }
177
+
178
+ return null;
179
+ };
180
+
181
+ const subTreeNode = searchNodeSubtree(root.children);
182
+
183
+ if (subTreeNode) {
184
+ return subTreeNode;
185
+ }
186
+ }
187
+
188
+ return null;
189
+ }
190
+
191
+ // these are used to "bind" a nonexistent property
192
+ _getValue_unavailable() {}
193
+ _setValue_unavailable() {}
194
+
195
+ // Getters
196
+
197
+ _getValue_direct(buffer, offset) {
198
+ buffer[offset] = this.targetObject[this.propertyName];
199
+ }
200
+
201
+ _getValue_array(buffer, offset) {
202
+ const source = this.resolvedProperty;
203
+
204
+ for (let i = 0, n = source.length; i !== n; ++i) {
205
+ buffer[offset++] = source[i];
206
+ }
207
+ }
208
+
209
+ _getValue_arrayElement(buffer, offset) {
210
+ buffer[offset] = this.resolvedProperty[this.propertyIndex];
211
+ }
212
+
213
+ _getValue_toArray(buffer, offset) {
214
+ this.resolvedProperty.toArray(buffer, offset);
215
+ }
216
+
217
+ // Direct
218
+
219
+ _setValue_direct(buffer, offset) {
220
+ this.targetObject[this.propertyName] = buffer[offset];
221
+ }
222
+
223
+ _setValue_direct_setNeedsUpdate(buffer, offset) {
224
+ this.targetObject[this.propertyName] = buffer[offset];
225
+ this.targetObject.needsUpdate = true;
226
+ }
227
+
228
+ _setValue_direct_setMatrixWorldNeedsUpdate(buffer, offset) {
229
+ this.targetObject[this.propertyName] = buffer[offset];
230
+ this.targetObject.matrixWorldNeedsUpdate = true;
231
+ }
232
+
233
+ // EntireArray
234
+
235
+ _setValue_array(buffer, offset) {
236
+ const dest = this.resolvedProperty;
237
+
238
+ for (let i = 0, n = dest.length; i !== n; ++i) {
239
+ dest[i] = buffer[offset++];
240
+ }
241
+ }
242
+
243
+ _setValue_array_setNeedsUpdate(buffer, offset) {
244
+ const dest = this.resolvedProperty;
245
+
246
+ for (let i = 0, n = dest.length; i !== n; ++i) {
247
+ dest[i] = buffer[offset++];
248
+ }
249
+
250
+ this.targetObject.needsUpdate = true;
251
+ }
252
+
253
+ _setValue_array_setMatrixWorldNeedsUpdate(buffer, offset) {
254
+ const dest = this.resolvedProperty;
255
+
256
+ for (let i = 0, n = dest.length; i !== n; ++i) {
257
+ dest[i] = buffer[offset++];
258
+ }
259
+
260
+ this.targetObject.matrixWorldNeedsUpdate = true;
261
+ }
262
+
263
+ // ArrayElement
264
+
265
+ _setValue_arrayElement(buffer, offset) {
266
+ this.resolvedProperty[this.propertyIndex] = buffer[offset];
267
+ }
268
+
269
+ _setValue_arrayElement_setNeedsUpdate(buffer, offset) {
270
+ this.resolvedProperty[this.propertyIndex] = buffer[offset];
271
+ this.targetObject.needsUpdate = true;
272
+ }
273
+
274
+ _setValue_arrayElement_setMatrixWorldNeedsUpdate(buffer, offset) {
275
+ this.resolvedProperty[this.propertyIndex] = buffer[offset];
276
+ this.targetObject.matrixWorldNeedsUpdate = true;
277
+ }
278
+
279
+ // HasToFromArray
280
+
281
+ _setValue_fromArray(buffer, offset) {
282
+ this.resolvedProperty.fromArray(buffer, offset);
283
+ }
284
+
285
+ _setValue_fromArray_setNeedsUpdate(buffer, offset) {
286
+ this.resolvedProperty.fromArray(buffer, offset);
287
+ this.targetObject.needsUpdate = true;
288
+ }
289
+
290
+ _setValue_fromArray_setMatrixWorldNeedsUpdate(buffer, offset) {
291
+ this.resolvedProperty.fromArray(buffer, offset);
292
+ this.targetObject.matrixWorldNeedsUpdate = true;
293
+ }
294
+
295
+ _getValue_unbound(targetArray, offset) {
296
+ this.bind();
297
+ this.getValue(targetArray, offset);
298
+ }
299
+
300
+ _setValue_unbound(sourceArray, offset) {
301
+ this.bind();
302
+ this.setValue(sourceArray, offset);
303
+ }
304
+
305
+ // create getter / setter pair for a property in the scene graph
306
+ bind() {
307
+ let targetObject = this.node;
308
+ const parsedPath = this.parsedPath;
309
+
310
+ const objectName = parsedPath.objectName;
311
+ const propertyName = parsedPath.propertyName;
312
+ let propertyIndex = parsedPath.propertyIndex;
313
+
314
+ if (!targetObject) {
315
+ targetObject = PropertyBinding.findNode(this.rootNode, parsedPath.nodeName) || this.rootNode;
316
+
317
+ this.node = targetObject;
318
+ }
319
+
320
+ // set fail state so we can just 'return' on error
321
+ this.getValue = this._getValue_unavailable;
322
+ this.setValue = this._setValue_unavailable;
323
+
324
+ // ensure there is a value node
325
+ if (!targetObject) {
326
+ console.error('THREE.PropertyBinding: Trying to update node for track: ' + this.path + " but it wasn't found.");
327
+ return;
328
+ }
329
+
330
+ if (objectName) {
331
+ let objectIndex = parsedPath.objectIndex;
332
+
333
+ // special cases were we need to reach deeper into the hierarchy to get the face materials....
334
+ switch (objectName) {
335
+ case 'materials':
336
+ if (!targetObject.material) {
337
+ console.error('THREE.PropertyBinding: Can not bind to material as node does not have a material.', this);
338
+ return;
339
+ }
340
+
341
+ if (!targetObject.material.materials) {
342
+ console.error('THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this);
343
+ return;
344
+ }
345
+
346
+ targetObject = targetObject.material.materials;
347
+
348
+ break;
349
+
350
+ case 'bones':
351
+ if (!targetObject.skeleton) {
352
+ console.error('THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this);
353
+ return;
354
+ }
355
+
356
+ // potential future optimization: skip this if propertyIndex is already an integer
357
+ // and convert the integer string to a true integer.
358
+
359
+ targetObject = targetObject.skeleton.bones;
360
+
361
+ // support resolving morphTarget names into indices.
362
+ for (let i = 0; i < targetObject.length; i++) {
363
+ if (targetObject[i].name === objectIndex) {
364
+ objectIndex = i;
365
+ break;
366
+ }
367
+ }
368
+
369
+ break;
370
+
371
+ default:
372
+ if (targetObject[objectName] === undefined) {
373
+ console.error('THREE.PropertyBinding: Can not bind to objectName of node undefined.', this);
374
+ return;
375
+ }
376
+
377
+ targetObject = targetObject[objectName];
378
+ }
379
+
380
+ if (objectIndex !== undefined) {
381
+ if (targetObject[objectIndex] === undefined) {
382
+ console.error('THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject);
383
+ return;
384
+ }
385
+
386
+ targetObject = targetObject[objectIndex];
387
+ }
388
+ }
389
+
390
+ // resolve property
391
+ const nodeProperty = targetObject[propertyName];
392
+
393
+ if (nodeProperty === undefined) {
394
+ const nodeName = parsedPath.nodeName;
395
+
396
+ console.error(
397
+ 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + '.' + propertyName + " but it wasn't found.",
398
+ targetObject
399
+ );
400
+ return;
401
+ }
402
+
403
+ // determine versioning scheme
404
+ let versioning = this.Versioning.None;
405
+
406
+ this.targetObject = targetObject;
407
+
408
+ if (targetObject.needsUpdate !== undefined) {
409
+ // material
410
+
411
+ versioning = this.Versioning.NeedsUpdate;
412
+ } else if (targetObject.matrixWorldNeedsUpdate !== undefined) {
413
+ // node transform
414
+
415
+ versioning = this.Versioning.MatrixWorldNeedsUpdate;
416
+ }
417
+
418
+ // determine how the property gets bound
419
+ let bindingType = this.BindingType.Direct;
420
+
421
+ if (propertyIndex !== undefined) {
422
+ // access a sub element of the property array (only primitives are supported right now)
423
+
424
+ if (propertyName === 'morphTargetInfluences') {
425
+ // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
426
+
427
+ // support resolving morphTarget names into indices.
428
+ if (!targetObject.geometry) {
429
+ console.error('THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this);
430
+ return;
431
+ }
432
+
433
+ if (targetObject.geometry.isBufferGeometry) {
434
+ if (!targetObject.geometry.morphAttributes) {
435
+ console.error(
436
+ 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.',
437
+ this
438
+ );
439
+ return;
440
+ }
441
+
442
+ if (targetObject.morphTargetDictionary[propertyIndex] !== undefined) {
443
+ propertyIndex = targetObject.morphTargetDictionary[propertyIndex];
444
+ }
445
+ } else {
446
+ console.error('THREE.PropertyBinding: Can not bind to morphTargetInfluences on THREE.Geometry. Use THREE.BufferGeometry instead.', this);
447
+ return;
448
+ }
449
+ }
450
+
451
+ bindingType = this.BindingType.ArrayElement;
452
+
453
+ this.resolvedProperty = nodeProperty;
454
+ this.propertyIndex = propertyIndex;
455
+ } else if (nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined) {
456
+ // must use copy for Object3D.Euler/Quaternion
457
+
458
+ bindingType = this.BindingType.HasFromToArray;
459
+
460
+ this.resolvedProperty = nodeProperty;
461
+ } else if (Array.isArray(nodeProperty)) {
462
+ bindingType = this.BindingType.EntireArray;
463
+
464
+ this.resolvedProperty = nodeProperty;
465
+ } else {
466
+ this.propertyName = propertyName;
467
+ }
468
+
469
+ // select getter / setter
470
+ this.getValue = this.GetterByBindingType[bindingType];
471
+ this.setValue = this.SetterByBindingTypeAndVersioning[bindingType][versioning];
472
+ }
473
+
474
+ unbind() {
475
+ this.node = null;
476
+
477
+ // back to the prototype version of getValue / setValue
478
+ // note: avoiding to mutate the shape of 'this' via 'delete'
479
+ this.getValue = this._getValue_unbound;
480
+ this.setValue = this._setValue_unbound;
481
+ }
482
+ }
483
+
484
+ PropertyBinding.Composite = Composite;
485
+
486
+ PropertyBinding.prototype.BindingType = {
487
+ Direct: 0,
488
+ EntireArray: 1,
489
+ ArrayElement: 2,
490
+ HasFromToArray: 3,
491
+ };
492
+
493
+ PropertyBinding.prototype.Versioning = {
494
+ None: 0,
495
+ NeedsUpdate: 1,
496
+ MatrixWorldNeedsUpdate: 2,
497
+ };
498
+
499
+ PropertyBinding.prototype.GetterByBindingType = [
500
+ PropertyBinding.prototype._getValue_direct,
501
+ PropertyBinding.prototype._getValue_array,
502
+ PropertyBinding.prototype._getValue_arrayElement,
503
+ PropertyBinding.prototype._getValue_toArray,
504
+ ];
505
+
506
+ PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [
507
+ [
508
+ // Direct
509
+ PropertyBinding.prototype._setValue_direct,
510
+ PropertyBinding.prototype._setValue_direct_setNeedsUpdate,
511
+ PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate,
512
+ ],
513
+ [
514
+ // EntireArray
515
+
516
+ PropertyBinding.prototype._setValue_array,
517
+ PropertyBinding.prototype._setValue_array_setNeedsUpdate,
518
+ PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate,
519
+ ],
520
+ [
521
+ // ArrayElement
522
+ PropertyBinding.prototype._setValue_arrayElement,
523
+ PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate,
524
+ PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate,
525
+ ],
526
+ [
527
+ // HasToFromArray
528
+ PropertyBinding.prototype._setValue_fromArray,
529
+ PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate,
530
+ PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate,
531
+ ],
532
+ ];
533
+
534
+ export { PropertyBinding };
backend/libs/three/animation/PropertyMixer.d.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class PropertyMixer {
2
+ constructor(binding: any, typeName: string, valueSize: number);
3
+
4
+ binding: any;
5
+ valueSize: number;
6
+ buffer: any;
7
+ cumulativeWeight: number;
8
+ cumulativeWeightAdditive: number;
9
+ useCount: number;
10
+ referenceCount: number;
11
+
12
+ accumulate(accuIndex: number, weight: number): void;
13
+ accumulateAdditive(weight: number): void;
14
+ apply(accuIndex: number): void;
15
+ saveOriginalState(): void;
16
+ restoreOriginalState(): void;
17
+ }
backend/libs/three/animation/PropertyMixer.js ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Quaternion } from '../math/Quaternion.js';
2
+
3
+ class PropertyMixer {
4
+ constructor(binding, typeName, valueSize) {
5
+ this.binding = binding;
6
+ this.valueSize = valueSize;
7
+
8
+ let mixFunction, mixFunctionAdditive, setIdentity;
9
+
10
+ // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ]
11
+ //
12
+ // interpolators can use .buffer as their .result
13
+ // the data then goes to 'incoming'
14
+ //
15
+ // 'accu0' and 'accu1' are used frame-interleaved for
16
+ // the cumulative result and are compared to detect
17
+ // changes
18
+ //
19
+ // 'orig' stores the original state of the property
20
+ //
21
+ // 'add' is used for additive cumulative results
22
+ //
23
+ // 'work' is optional and is only present for quaternion types. It is used
24
+ // to store intermediate quaternion multiplication results
25
+
26
+ switch (typeName) {
27
+ case 'quaternion':
28
+ mixFunction = this._slerp;
29
+ mixFunctionAdditive = this._slerpAdditive;
30
+ setIdentity = this._setAdditiveIdentityQuaternion;
31
+
32
+ this.buffer = new Float64Array(valueSize * 6);
33
+ this._workIndex = 5;
34
+ break;
35
+
36
+ case 'string':
37
+ case 'bool':
38
+ mixFunction = this._select;
39
+
40
+ // Use the regular mix function and for additive on these types,
41
+ // additive is not relevant for non-numeric types
42
+ mixFunctionAdditive = this._select;
43
+
44
+ setIdentity = this._setAdditiveIdentityOther;
45
+
46
+ this.buffer = new Array(valueSize * 5);
47
+ break;
48
+
49
+ default:
50
+ mixFunction = this._lerp;
51
+ mixFunctionAdditive = this._lerpAdditive;
52
+ setIdentity = this._setAdditiveIdentityNumeric;
53
+
54
+ this.buffer = new Float64Array(valueSize * 5);
55
+ }
56
+
57
+ this._mixBufferRegion = mixFunction;
58
+ this._mixBufferRegionAdditive = mixFunctionAdditive;
59
+ this._setIdentity = setIdentity;
60
+ this._origIndex = 3;
61
+ this._addIndex = 4;
62
+
63
+ this.cumulativeWeight = 0;
64
+ this.cumulativeWeightAdditive = 0;
65
+
66
+ this.useCount = 0;
67
+ this.referenceCount = 0;
68
+ }
69
+
70
+ // accumulate data in the 'incoming' region into 'accu<i>'
71
+ accumulate(accuIndex, weight) {
72
+ // note: happily accumulating nothing when weight = 0, the caller knows
73
+ // the weight and shouldn't have made the call in the first place
74
+
75
+ const buffer = this.buffer,
76
+ stride = this.valueSize,
77
+ offset = accuIndex * stride + stride;
78
+
79
+ let currentWeight = this.cumulativeWeight;
80
+
81
+ if (currentWeight === 0) {
82
+ // accuN := incoming * weight
83
+
84
+ for (let i = 0; i !== stride; ++i) {
85
+ buffer[offset + i] = buffer[i];
86
+ }
87
+
88
+ currentWeight = weight;
89
+ } else {
90
+ // accuN := accuN + incoming * weight
91
+
92
+ currentWeight += weight;
93
+ const mix = weight / currentWeight;
94
+ this._mixBufferRegion(buffer, offset, 0, mix, stride);
95
+ }
96
+
97
+ this.cumulativeWeight = currentWeight;
98
+ }
99
+
100
+ // accumulate data in the 'incoming' region into 'add'
101
+ accumulateAdditive(weight) {
102
+ const buffer = this.buffer,
103
+ stride = this.valueSize,
104
+ offset = stride * this._addIndex;
105
+
106
+ if (this.cumulativeWeightAdditive === 0) {
107
+ // add = identity
108
+
109
+ this._setIdentity();
110
+ }
111
+
112
+ // add := add + incoming * weight
113
+
114
+ this._mixBufferRegionAdditive(buffer, offset, 0, weight, stride);
115
+ this.cumulativeWeightAdditive += weight;
116
+ }
117
+
118
+ // apply the state of 'accu<i>' to the binding when accus differ
119
+ apply(accuIndex) {
120
+ const stride = this.valueSize,
121
+ buffer = this.buffer,
122
+ offset = accuIndex * stride + stride,
123
+ weight = this.cumulativeWeight,
124
+ weightAdditive = this.cumulativeWeightAdditive,
125
+ binding = this.binding;
126
+
127
+ this.cumulativeWeight = 0;
128
+ this.cumulativeWeightAdditive = 0;
129
+
130
+ if (weight < 1) {
131
+ // accuN := accuN + original * ( 1 - cumulativeWeight )
132
+
133
+ const originalValueOffset = stride * this._origIndex;
134
+
135
+ this._mixBufferRegion(buffer, offset, originalValueOffset, 1 - weight, stride);
136
+ }
137
+
138
+ if (weightAdditive > 0) {
139
+ // accuN := accuN + additive accuN
140
+
141
+ this._mixBufferRegionAdditive(buffer, offset, this._addIndex * stride, 1, stride);
142
+ }
143
+
144
+ for (let i = stride, e = stride + stride; i !== e; ++i) {
145
+ if (buffer[i] !== buffer[i + stride]) {
146
+ // value has changed -> update scene graph
147
+
148
+ binding.setValue(buffer, offset);
149
+ break;
150
+ }
151
+ }
152
+ }
153
+
154
+ // remember the state of the bound property and copy it to both accus
155
+ saveOriginalState() {
156
+ const binding = this.binding;
157
+
158
+ const buffer = this.buffer,
159
+ stride = this.valueSize,
160
+ originalValueOffset = stride * this._origIndex;
161
+
162
+ binding.getValue(buffer, originalValueOffset);
163
+
164
+ // accu[0..1] := orig -- initially detect changes against the original
165
+ for (let i = stride, e = originalValueOffset; i !== e; ++i) {
166
+ buffer[i] = buffer[originalValueOffset + (i % stride)];
167
+ }
168
+
169
+ // Add to identity for additive
170
+ this._setIdentity();
171
+
172
+ this.cumulativeWeight = 0;
173
+ this.cumulativeWeightAdditive = 0;
174
+ }
175
+
176
+ // apply the state previously taken via 'saveOriginalState' to the binding
177
+ restoreOriginalState() {
178
+ const originalValueOffset = this.valueSize * 3;
179
+ this.binding.setValue(this.buffer, originalValueOffset);
180
+ }
181
+
182
+ _setAdditiveIdentityNumeric() {
183
+ const startIndex = this._addIndex * this.valueSize;
184
+ const endIndex = startIndex + this.valueSize;
185
+
186
+ for (let i = startIndex; i < endIndex; i++) {
187
+ this.buffer[i] = 0;
188
+ }
189
+ }
190
+
191
+ _setAdditiveIdentityQuaternion() {
192
+ this._setAdditiveIdentityNumeric();
193
+ this.buffer[this._addIndex * this.valueSize + 3] = 1;
194
+ }
195
+
196
+ _setAdditiveIdentityOther() {
197
+ const startIndex = this._origIndex * this.valueSize;
198
+ const targetIndex = this._addIndex * this.valueSize;
199
+
200
+ for (let i = 0; i < this.valueSize; i++) {
201
+ this.buffer[targetIndex + i] = this.buffer[startIndex + i];
202
+ }
203
+ }
204
+
205
+ // mix functions
206
+
207
+ _select(buffer, dstOffset, srcOffset, t, stride) {
208
+ if (t >= 0.5) {
209
+ for (let i = 0; i !== stride; ++i) {
210
+ buffer[dstOffset + i] = buffer[srcOffset + i];
211
+ }
212
+ }
213
+ }
214
+
215
+ _slerp(buffer, dstOffset, srcOffset, t) {
216
+ Quaternion.slerpFlat(buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t);
217
+ }
218
+
219
+ _slerpAdditive(buffer, dstOffset, srcOffset, t, stride) {
220
+ const workOffset = this._workIndex * stride;
221
+
222
+ // Store result in intermediate buffer offset
223
+ Quaternion.multiplyQuaternionsFlat(buffer, workOffset, buffer, dstOffset, buffer, srcOffset);
224
+
225
+ // Slerp to the intermediate result
226
+ Quaternion.slerpFlat(buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t);
227
+ }
228
+
229
+ _lerp(buffer, dstOffset, srcOffset, t, stride) {
230
+ const s = 1 - t;
231
+
232
+ for (let i = 0; i !== stride; ++i) {
233
+ const j = dstOffset + i;
234
+
235
+ buffer[j] = buffer[j] * s + buffer[srcOffset + i] * t;
236
+ }
237
+ }
238
+
239
+ _lerpAdditive(buffer, dstOffset, srcOffset, t, stride) {
240
+ for (let i = 0; i !== stride; ++i) {
241
+ const j = dstOffset + i;
242
+
243
+ buffer[j] = buffer[j] + buffer[srcOffset + i] * t;
244
+ }
245
+ }
246
+ }
247
+
248
+ export { PropertyMixer };
backend/libs/three/animation/tracks/BooleanKeyframeTrack.d.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from './../KeyframeTrack';
2
+
3
+ export class BooleanKeyframeTrack extends KeyframeTrack {
4
+ constructor(name: string, times: any[], values: any[]);
5
+
6
+ /**
7
+ * @default 'bool'
8
+ */
9
+ ValueTypeName: string;
10
+ }
backend/libs/three/animation/tracks/BooleanKeyframeTrack.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { InterpolateDiscrete } from '../../constants.js';
2
+ import { KeyframeTrack } from '../KeyframeTrack.js';
3
+
4
+ /**
5
+ * A Track of Boolean keyframe values.
6
+ */
7
+ class BooleanKeyframeTrack extends KeyframeTrack {}
8
+
9
+ BooleanKeyframeTrack.prototype.ValueTypeName = 'bool';
10
+ BooleanKeyframeTrack.prototype.ValueBufferType = Array;
11
+ BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete;
12
+ BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined;
13
+ BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
14
+
15
+ // Note: Actually this track could have a optimized / compressed
16
+ // representation of a single value and a custom interpolant that
17
+ // computes "firstValue ^ isOdd( index )".
18
+
19
+ export { BooleanKeyframeTrack };
backend/libs/three/animation/tracks/ColorKeyframeTrack.d.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from './../KeyframeTrack';
2
+ import { InterpolationModes } from '../../constants';
3
+
4
+ export class ColorKeyframeTrack extends KeyframeTrack {
5
+ constructor(name: string, times: any[], values: any[], interpolation?: InterpolationModes);
6
+
7
+ /**
8
+ * @default 'color'
9
+ */
10
+ ValueTypeName: string;
11
+ }
backend/libs/three/animation/tracks/ColorKeyframeTrack.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from '../KeyframeTrack.js';
2
+
3
+ /**
4
+ * A Track of keyframe values that represent color.
5
+ */
6
+ class ColorKeyframeTrack extends KeyframeTrack {}
7
+
8
+ ColorKeyframeTrack.prototype.ValueTypeName = 'color';
9
+ // ValueBufferType is inherited
10
+ // DefaultInterpolation is inherited
11
+
12
+ // Note: Very basic implementation and nothing special yet.
13
+ // However, this is the place for color space parameterization.
14
+
15
+ export { ColorKeyframeTrack };
backend/libs/three/animation/tracks/NumberKeyframeTrack.d.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from './../KeyframeTrack';
2
+ import { InterpolationModes } from '../../constants';
3
+
4
+ export class NumberKeyframeTrack extends KeyframeTrack {
5
+ constructor(name: string, times: any[], values: any[], interpolation?: InterpolationModes);
6
+
7
+ /**
8
+ * @default 'number'
9
+ */
10
+ ValueTypeName: string;
11
+ }
backend/libs/three/animation/tracks/NumberKeyframeTrack.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from '../KeyframeTrack.js';
2
+
3
+ /**
4
+ * A Track of numeric keyframe values.
5
+ */
6
+ class NumberKeyframeTrack extends KeyframeTrack {}
7
+
8
+ NumberKeyframeTrack.prototype.ValueTypeName = 'number';
9
+ // ValueBufferType is inherited
10
+ // DefaultInterpolation is inherited
11
+
12
+ export { NumberKeyframeTrack };
backend/libs/three/animation/tracks/QuaternionKeyframeTrack.d.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from './../KeyframeTrack';
2
+ import { InterpolationModes } from '../../constants';
3
+
4
+ export class QuaternionKeyframeTrack extends KeyframeTrack {
5
+ constructor(name: string, times: any[], values: any[], interpolation?: InterpolationModes);
6
+
7
+ /**
8
+ * @default 'quaternion'
9
+ */
10
+ ValueTypeName: string;
11
+ }
backend/libs/three/animation/tracks/QuaternionKeyframeTrack.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { InterpolateLinear } from '../../constants.js';
2
+ import { KeyframeTrack } from '../KeyframeTrack.js';
3
+ import { QuaternionLinearInterpolant } from '../../math/interpolants/QuaternionLinearInterpolant.js';
4
+
5
+ /**
6
+ * A Track of quaternion keyframe values.
7
+ */
8
+ class QuaternionKeyframeTrack extends KeyframeTrack {
9
+ InterpolantFactoryMethodLinear(result) {
10
+ return new QuaternionLinearInterpolant(this.times, this.values, this.getValueSize(), result);
11
+ }
12
+ }
13
+
14
+ QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion';
15
+ // ValueBufferType is inherited
16
+ QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear;
17
+ QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
18
+
19
+ export { QuaternionKeyframeTrack };
backend/libs/three/animation/tracks/StringKeyframeTrack.d.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from './../KeyframeTrack';
2
+ import { InterpolationModes } from '../../constants';
3
+
4
+ export class StringKeyframeTrack extends KeyframeTrack {
5
+ constructor(name: string, times: any[], values: any[], interpolation?: InterpolationModes);
6
+
7
+ /**
8
+ * @default 'string'
9
+ */
10
+ ValueTypeName: string;
11
+ }
backend/libs/three/animation/tracks/StringKeyframeTrack.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { InterpolateDiscrete } from '../../constants.js';
2
+ import { KeyframeTrack } from '../KeyframeTrack.js';
3
+
4
+ /**
5
+ * A Track that interpolates Strings
6
+ */
7
+ class StringKeyframeTrack extends KeyframeTrack {}
8
+
9
+ StringKeyframeTrack.prototype.ValueTypeName = 'string';
10
+ StringKeyframeTrack.prototype.ValueBufferType = Array;
11
+ StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete;
12
+ StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined;
13
+ StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
14
+
15
+ export { StringKeyframeTrack };
backend/libs/three/animation/tracks/VectorKeyframeTrack.d.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from './../KeyframeTrack';
2
+ import { InterpolationModes } from '../../constants';
3
+
4
+ export class VectorKeyframeTrack extends KeyframeTrack {
5
+ constructor(name: string, times: any[], values: any[], interpolation?: InterpolationModes);
6
+
7
+ /**
8
+ * @default 'vector'
9
+ */
10
+ ValueTypeName: string;
11
+ }
backend/libs/three/animation/tracks/VectorKeyframeTrack.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { KeyframeTrack } from '../KeyframeTrack.js';
2
+
3
+ /**
4
+ * A Track of vectored keyframe values.
5
+ */
6
+ class VectorKeyframeTrack extends KeyframeTrack {}
7
+
8
+ VectorKeyframeTrack.prototype.ValueTypeName = 'vector';
9
+ // ValueBufferType is inherited
10
+ // DefaultInterpolation is inherited
11
+
12
+ export { VectorKeyframeTrack };
backend/libs/three/audio/Audio.d.ts ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Object3D } from './../core/Object3D';
2
+ import { AudioListener } from './AudioListener';
3
+ import { AudioContext } from './AudioContext';
4
+
5
+ // Extras / Audio /////////////////////////////////////////////////////////////////////
6
+
7
+ export class Audio<NodeType extends AudioNode = GainNode> extends Object3D {
8
+ constructor(listener: AudioListener);
9
+ type: 'Audio';
10
+
11
+ listener: AudioListener;
12
+ context: AudioContext;
13
+ gain: GainNode;
14
+
15
+ /**
16
+ * @default false
17
+ */
18
+ autoplay: boolean;
19
+ buffer: null | AudioBuffer;
20
+
21
+ /**
22
+ * @default 0
23
+ */
24
+ detune: number;
25
+
26
+ /**
27
+ * @default false
28
+ */
29
+ loop: boolean;
30
+
31
+ /**
32
+ * @default 0
33
+ */
34
+ loopStart: number;
35
+
36
+ /**
37
+ * @default 0
38
+ */
39
+ loopEnd: number;
40
+
41
+ /**
42
+ * @default 0
43
+ */
44
+ offset: number;
45
+
46
+ /**
47
+ * @default undefined
48
+ */
49
+ duration: number | undefined;
50
+
51
+ /**
52
+ * @default 1
53
+ */
54
+ playbackRate: number;
55
+
56
+ /**
57
+ * @default false
58
+ */
59
+ isPlaying: boolean;
60
+
61
+ /**
62
+ * @default true
63
+ */
64
+ hasPlaybackControl: boolean;
65
+
66
+ /**
67
+ * @default 'empty'
68
+ */
69
+ sourceType: string;
70
+ source: null | AudioBufferSourceNode;
71
+
72
+ /**
73
+ * @default []
74
+ */
75
+ filters: AudioNode[];
76
+
77
+ getOutput(): NodeType;
78
+ setNodeSource(audioNode: AudioBufferSourceNode): this;
79
+ setMediaElementSource(mediaElement: HTMLMediaElement): this;
80
+ setMediaStreamSource(mediaStream: MediaStream): this;
81
+ setBuffer(audioBuffer: AudioBuffer): this;
82
+ play(delay?: number): this;
83
+ onEnded(): void;
84
+ pause(): this;
85
+ stop(): this;
86
+ connect(): this;
87
+ disconnect(): this;
88
+ setDetune(value: number): this;
89
+ getDetune(): number;
90
+ getFilters(): AudioNode[];
91
+ setFilters(value: AudioNode[]): this;
92
+ getFilter(): AudioNode;
93
+ setFilter(filter: AudioNode): this;
94
+ setPlaybackRate(value: number): this;
95
+ getPlaybackRate(): number;
96
+ getLoop(): boolean;
97
+ setLoop(value: boolean): this;
98
+ setLoopStart(value: number): this;
99
+ setLoopEnd(value: number): this;
100
+ getVolume(): number;
101
+ setVolume(value: number): this;
102
+ /**
103
+ * @deprecated Use {@link AudioLoader} instead.
104
+ */
105
+ load(file: string): Audio;
106
+ }
backend/libs/three/audio/Audio.js ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Object3D } from '../core/Object3D.js';
2
+
3
+ class Audio extends Object3D {
4
+ constructor(listener) {
5
+ super();
6
+
7
+ this.type = 'Audio';
8
+
9
+ this.listener = listener;
10
+ this.context = listener.context;
11
+
12
+ this.gain = this.context.createGain();
13
+ this.gain.connect(listener.getInput());
14
+
15
+ this.autoplay = false;
16
+
17
+ this.buffer = null;
18
+ this.detune = 0;
19
+ this.loop = false;
20
+ this.loopStart = 0;
21
+ this.loopEnd = 0;
22
+ this.offset = 0;
23
+ this.duration = undefined;
24
+ this.playbackRate = 1;
25
+ this.isPlaying = false;
26
+ this.hasPlaybackControl = true;
27
+ this.source = null;
28
+ this.sourceType = 'empty';
29
+
30
+ this._startedAt = 0;
31
+ this._progress = 0;
32
+ this._connected = false;
33
+
34
+ this.filters = [];
35
+ }
36
+
37
+ getOutput() {
38
+ return this.gain;
39
+ }
40
+
41
+ setNodeSource(audioNode) {
42
+ this.hasPlaybackControl = false;
43
+ this.sourceType = 'audioNode';
44
+ this.source = audioNode;
45
+ this.connect();
46
+
47
+ return this;
48
+ }
49
+
50
+ setMediaElementSource(mediaElement) {
51
+ this.hasPlaybackControl = false;
52
+ this.sourceType = 'mediaNode';
53
+ this.source = this.context.createMediaElementSource(mediaElement);
54
+ this.connect();
55
+
56
+ return this;
57
+ }
58
+
59
+ setMediaStreamSource(mediaStream) {
60
+ this.hasPlaybackControl = false;
61
+ this.sourceType = 'mediaStreamNode';
62
+ this.source = this.context.createMediaStreamSource(mediaStream);
63
+ this.connect();
64
+
65
+ return this;
66
+ }
67
+
68
+ setBuffer(audioBuffer) {
69
+ this.buffer = audioBuffer;
70
+ this.sourceType = 'buffer';
71
+
72
+ if (this.autoplay) this.play();
73
+
74
+ return this;
75
+ }
76
+
77
+ play(delay = 0) {
78
+ if (this.isPlaying === true) {
79
+ console.warn('THREE.Audio: Audio is already playing.');
80
+ return;
81
+ }
82
+
83
+ if (this.hasPlaybackControl === false) {
84
+ console.warn('THREE.Audio: this Audio has no playback control.');
85
+ return;
86
+ }
87
+
88
+ this._startedAt = this.context.currentTime + delay;
89
+
90
+ const source = this.context.createBufferSource();
91
+ source.buffer = this.buffer;
92
+ source.loop = this.loop;
93
+ source.loopStart = this.loopStart;
94
+ source.loopEnd = this.loopEnd;
95
+ source.onended = this.onEnded.bind(this);
96
+ source.start(this._startedAt, this._progress + this.offset, this.duration);
97
+
98
+ this.isPlaying = true;
99
+
100
+ this.source = source;
101
+
102
+ this.setDetune(this.detune);
103
+ this.setPlaybackRate(this.playbackRate);
104
+
105
+ return this.connect();
106
+ }
107
+
108
+ pause() {
109
+ if (this.hasPlaybackControl === false) {
110
+ console.warn('THREE.Audio: this Audio has no playback control.');
111
+ return;
112
+ }
113
+
114
+ if (this.isPlaying === true) {
115
+ // update current progress
116
+
117
+ this._progress += Math.max(this.context.currentTime - this._startedAt, 0) * this.playbackRate;
118
+
119
+ if (this.loop === true) {
120
+ // ensure _progress does not exceed duration with looped audios
121
+
122
+ this._progress = this._progress % (this.duration || this.buffer.duration);
123
+ }
124
+
125
+ this.source.stop();
126
+ this.source.onended = null;
127
+
128
+ this.isPlaying = false;
129
+ }
130
+
131
+ return this;
132
+ }
133
+
134
+ stop() {
135
+ if (this.hasPlaybackControl === false) {
136
+ console.warn('THREE.Audio: this Audio has no playback control.');
137
+ return;
138
+ }
139
+
140
+ this._progress = 0;
141
+
142
+ this.source.stop();
143
+ this.source.onended = null;
144
+ this.isPlaying = false;
145
+
146
+ return this;
147
+ }
148
+
149
+ connect() {
150
+ if (this.filters.length > 0) {
151
+ this.source.connect(this.filters[0]);
152
+
153
+ for (let i = 1, l = this.filters.length; i < l; i++) {
154
+ this.filters[i - 1].connect(this.filters[i]);
155
+ }
156
+
157
+ this.filters[this.filters.length - 1].connect(this.getOutput());
158
+ } else {
159
+ this.source.connect(this.getOutput());
160
+ }
161
+
162
+ this._connected = true;
163
+
164
+ return this;
165
+ }
166
+
167
+ disconnect() {
168
+ if (this.filters.length > 0) {
169
+ this.source.disconnect(this.filters[0]);
170
+
171
+ for (let i = 1, l = this.filters.length; i < l; i++) {
172
+ this.filters[i - 1].disconnect(this.filters[i]);
173
+ }
174
+
175
+ this.filters[this.filters.length - 1].disconnect(this.getOutput());
176
+ } else {
177
+ this.source.disconnect(this.getOutput());
178
+ }
179
+
180
+ this._connected = false;
181
+
182
+ return this;
183
+ }
184
+
185
+ getFilters() {
186
+ return this.filters;
187
+ }
188
+
189
+ setFilters(value) {
190
+ if (!value) value = [];
191
+
192
+ if (this._connected === true) {
193
+ this.disconnect();
194
+ this.filters = value.slice();
195
+ this.connect();
196
+ } else {
197
+ this.filters = value.slice();
198
+ }
199
+
200
+ return this;
201
+ }
202
+
203
+ setDetune(value) {
204
+ this.detune = value;
205
+
206
+ if (this.source.detune === undefined) return; // only set detune when available
207
+
208
+ if (this.isPlaying === true) {
209
+ this.source.detune.setTargetAtTime(this.detune, this.context.currentTime, 0.01);
210
+ }
211
+
212
+ return this;
213
+ }
214
+
215
+ getDetune() {
216
+ return this.detune;
217
+ }
218
+
219
+ getFilter() {
220
+ return this.getFilters()[0];
221
+ }
222
+
223
+ setFilter(filter) {
224
+ return this.setFilters(filter ? [filter] : []);
225
+ }
226
+
227
+ setPlaybackRate(value) {
228
+ if (this.hasPlaybackControl === false) {
229
+ console.warn('THREE.Audio: this Audio has no playback control.');
230
+ return;
231
+ }
232
+
233
+ this.playbackRate = value;
234
+
235
+ if (this.isPlaying === true) {
236
+ this.source.playbackRate.setTargetAtTime(this.playbackRate, this.context.currentTime, 0.01);
237
+ }
238
+
239
+ return this;
240
+ }
241
+
242
+ getPlaybackRate() {
243
+ return this.playbackRate;
244
+ }
245
+
246
+ onEnded() {
247
+ this.isPlaying = false;
248
+ }
249
+
250
+ getLoop() {
251
+ if (this.hasPlaybackControl === false) {
252
+ console.warn('THREE.Audio: this Audio has no playback control.');
253
+ return false;
254
+ }
255
+
256
+ return this.loop;
257
+ }
258
+
259
+ setLoop(value) {
260
+ if (this.hasPlaybackControl === false) {
261
+ console.warn('THREE.Audio: this Audio has no playback control.');
262
+ return;
263
+ }
264
+
265
+ this.loop = value;
266
+
267
+ if (this.isPlaying === true) {
268
+ this.source.loop = this.loop;
269
+ }
270
+
271
+ return this;
272
+ }
273
+
274
+ setLoopStart(value) {
275
+ this.loopStart = value;
276
+
277
+ return this;
278
+ }
279
+
280
+ setLoopEnd(value) {
281
+ this.loopEnd = value;
282
+
283
+ return this;
284
+ }
285
+
286
+ getVolume() {
287
+ return this.gain.gain.value;
288
+ }
289
+
290
+ setVolume(value) {
291
+ this.gain.gain.setTargetAtTime(value, this.context.currentTime, 0.01);
292
+
293
+ return this;
294
+ }
295
+ }
296
+
297
+ export { Audio };
backend/libs/three/audio/AudioAnalyser.d.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Audio } from './Audio';
2
+
3
+ export class AudioAnalyser {
4
+ /**
5
+ * @param audio
6
+ * @param [fftSize=2048]
7
+ */
8
+ constructor(audio: Audio<AudioNode>, fftSize?: number);
9
+
10
+ analyser: AnalyserNode;
11
+ data: Uint8Array;
12
+
13
+ getFrequencyData(): Uint8Array;
14
+ getAverageFrequency(): number;
15
+
16
+ /**
17
+ * @deprecated Use {@link AudioAnalyser#getFrequencyData .getFrequencyData()} instead.
18
+ */
19
+ getData(file: any): any;
20
+ }
backend/libs/three/audio/AudioAnalyser.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AudioAnalyser {
2
+ constructor(audio, fftSize = 2048) {
3
+ this.analyser = audio.context.createAnalyser();
4
+ this.analyser.fftSize = fftSize;
5
+
6
+ this.data = new Uint8Array(this.analyser.frequencyBinCount);
7
+
8
+ audio.getOutput().connect(this.analyser);
9
+ }
10
+
11
+ getFrequencyData() {
12
+ this.analyser.getByteFrequencyData(this.data);
13
+
14
+ return this.data;
15
+ }
16
+
17
+ getAverageFrequency() {
18
+ let value = 0;
19
+ const data = this.getFrequencyData();
20
+
21
+ for (let i = 0; i < data.length; i++) {
22
+ value += data[i];
23
+ }
24
+
25
+ return value / data.length;
26
+ }
27
+ }
28
+
29
+ export { AudioAnalyser };