Spaces:
Running
Running
Commit
·
2b7aae2
1
Parent(s):
a523941
feat: add Python ML services (CPU mode) with model download
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- Dockerfile +32 -4
- backend/libs/PyProcessor.ts +58 -0
- backend/libs/ZeroClient.ts +85 -0
- backend/libs/adminSolutionStore.ts +95 -0
- backend/libs/async-queue.ts +72 -0
- backend/libs/beadPicker.ts +22 -0
- backend/libs/browserComponents.ts +2 -0
- backend/libs/gauge-renderer-three.ts +161 -0
- backend/libs/predictJianpuPages.ts +443 -0
- backend/libs/predictPages.ts +887 -0
- backend/libs/predictors.ts +139 -0
- backend/libs/regulation.ts +350 -0
- backend/libs/regulationBead.ts +372 -0
- backend/libs/store.ts +42 -0
- backend/libs/three/Three.Legacy.d.ts +1 -0
- backend/libs/three/Three.Legacy.js +1444 -0
- backend/libs/three/Three.d.ts +234 -0
- backend/libs/three/Three.js +172 -0
- backend/libs/three/animation/AnimationAction.d.ts +86 -0
- backend/libs/three/animation/AnimationAction.js +553 -0
- backend/libs/three/animation/AnimationClip.d.ts +42 -0
- backend/libs/three/animation/AnimationClip.js +350 -0
- backend/libs/three/animation/AnimationMixer.d.ts +30 -0
- backend/libs/three/animation/AnimationMixer.js +590 -0
- backend/libs/three/animation/AnimationObjectGroup.d.ts +17 -0
- backend/libs/three/animation/AnimationObjectGroup.js +327 -0
- backend/libs/three/animation/AnimationUtils.d.ts +27 -0
- backend/libs/three/animation/AnimationUtils.js +267 -0
- backend/libs/three/animation/KeyframeTrack.d.ts +45 -0
- backend/libs/three/animation/KeyframeTrack.js +337 -0
- backend/libs/three/animation/PropertyBinding.d.ts +43 -0
- backend/libs/three/animation/PropertyBinding.js +534 -0
- backend/libs/three/animation/PropertyMixer.d.ts +17 -0
- backend/libs/three/animation/PropertyMixer.js +248 -0
- backend/libs/three/animation/tracks/BooleanKeyframeTrack.d.ts +10 -0
- backend/libs/three/animation/tracks/BooleanKeyframeTrack.js +19 -0
- backend/libs/three/animation/tracks/ColorKeyframeTrack.d.ts +11 -0
- backend/libs/three/animation/tracks/ColorKeyframeTrack.js +15 -0
- backend/libs/three/animation/tracks/NumberKeyframeTrack.d.ts +11 -0
- backend/libs/three/animation/tracks/NumberKeyframeTrack.js +12 -0
- backend/libs/three/animation/tracks/QuaternionKeyframeTrack.d.ts +11 -0
- backend/libs/three/animation/tracks/QuaternionKeyframeTrack.js +19 -0
- backend/libs/three/animation/tracks/StringKeyframeTrack.d.ts +11 -0
- backend/libs/three/animation/tracks/StringKeyframeTrack.js +15 -0
- backend/libs/three/animation/tracks/VectorKeyframeTrack.d.ts +11 -0
- backend/libs/three/animation/tracks/VectorKeyframeTrack.js +12 -0
- backend/libs/three/audio/Audio.d.ts +106 -0
- backend/libs/three/audio/Audio.js +297 -0
- backend/libs/three/audio/AudioAnalyser.d.ts +20 -0
- backend/libs/three/audio/AudioAnalyser.js +29 -0
Dockerfile
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
###############################################################################
|
| 2 |
-
# STARRY HuggingFace Space —
|
| 3 |
# nginx (reverse proxy) + omr-service (Fastify) + cluster-server (NestJS)
|
| 4 |
-
#
|
|
|
|
| 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
|
|
|
|
| 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 };
|