'use strict'; const ringBufferTs = require('ring-buffer-ts'); var __defProp$1 = Object.defineProperty; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$1 = (obj, key, value) => { __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class Vault { /** * Vault for storing N last snapshots * @param size Vault's elements maximum amount * @default 100 */ constructor(size = 100) { __publicField$1(this, "buffer"); /** Buffer is sorted in a reverse order for faster lookups */ __publicField$1(this, "sortedBuffer", []); this.buffer = new ringBufferTs.RingBuffer(size); } add(snapshot) { this.buffer.add(snapshot); this.sortBuffer(); } unsafe_addWithoutSort(snapshot) { this.buffer.add(snapshot); this.sortedBuffer = this.buffer.toArray(); } setMaxSize(newSize) { this.buffer.resize(newSize); } getMaxSize() { return this.buffer.getSize(); } get size() { return this.buffer.getBufferLength(); } getAroundTimestamp(timestamp) { const index = this.binarySearch( this.sortedBuffer, (cur) => cur.timestamp - timestamp, true ); return { older: this.sortedBuffer[index + 1], newer: this.sortedBuffer[index] }; } getLast() { return this.sortedBuffer[0]; } getClosest(timestamp) { const snapshots = this.getAroundTimestamp(timestamp); if (!snapshots) return; const older = snapshots.older ? Math.abs(timestamp - snapshots.older.timestamp) : -1; const newer = snapshots.newer ? Math.abs(timestamp - snapshots.newer.timestamp) : NaN; if (isNaN(newer)) return snapshots.older; else if (newer > older) return snapshots.newer; else return snapshots.older; } getByFrame(frame) { const index = this.binarySearch( this.sortedBuffer, (cur) => cur.frame - frame ); return this.sortedBuffer[index]; } clear() { this.buffer.clear(); this.sortedBuffer = []; return; } /** * Removes one or more items form the buffer. * @param index Start index of the item to remove. * @param count The count of items to remove. (default: 1) */ remove(index, count = 1) { const removedItems = this.buffer.remove(index, count); this.sortBuffer(); return removedItems; } /** * Removes the first item. Like #remove(0). */ removeFirst() { const removedItem = this.buffer.removeFirst(); this.sortBuffer(); return removedItem; } /** * Removes the last item. Like #remove(-1). */ removeLast() { const removedItem = this.buffer.removeLast(); this.sortBuffer(); return removedItem; } isEmpty() { return this.buffer.isEmpty(); } isFull() { return this.buffer.isFull(); } toArray() { return this.sortedBuffer; } sortBuffer() { this.sortedBuffer = this.buffer.toArray().sort((a, b) => b.timestamp - a.timestamp); return this.sortedBuffer; } binarySearch(array, compare, findClosestValue) { let start = 0; let end = array.length - 1; while (start <= end) { const middle = start + end >> 1; const cmp = compare(array[middle]); if (cmp > 0) { start = middle + 1; } else if (cmp < 0) { end = middle - 1; } else { return middle; } } return findClosestValue ? start - 1 : -1; } } const PI = 3.14159265359; const PI_TIMES_TWO = 6.28318530718; const lerp = (a, b, alpha) => { return a + (b - a) * alpha; }; const degreeLerp = (a, b, alpha) => { let result; const diff = b - a; if (diff < -180) { b += 360; result = lerp(a, b, alpha); if (result >= 360) { result -= 360; } } else if (diff > 180) { b -= 360; result = lerp(a, b, alpha); if (result < 0) { result += 360; } } else { result = lerp(a, b, alpha); } return result; }; const radianLerp = (a, b, alpha) => { let result; const diff = b - a; if (diff < -PI) { b += PI_TIMES_TWO; result = lerp(a, b, alpha); if (result >= PI_TIMES_TWO) { result -= PI_TIMES_TWO; } } else if (diff > PI) { b -= PI_TIMES_TWO; result = lerp(a, b, alpha); if (result < 0) { result += PI_TIMES_TWO; } } else { result = lerp(a, b, alpha); } return result; }; const quatSlerp = (qa, qb, t) => { if (t === 0) return qa; if (t === 1) return qb; let x0 = qa.x; let y0 = qa.y; let z0 = qa.z; let w0 = qa.w; const x1 = qb.x; const y1 = qb.y; const z1 = qb.z; const w1 = qb.w; if (w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1) { let s = 1 - t; const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1; const dir = cos >= 0 ? 1 : -1; const sqrSin = 1 - cos * cos; if (sqrSin > 1e-3) { const sin = Math.sqrt(sqrSin); const len = Math.atan2(sin, cos * dir); s = Math.sin(s * len) / sin; t = Math.sin(t * len) / sin; } const tDir = t * dir; x0 = x0 * s + x1 * tDir; y0 = y0 * s + y1 * tDir; z0 = z0 * s + z1 * tDir; w0 = w0 * s + w1 * tDir; if (s === 1 - t) { const f = 1 / Math.sqrt(x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0); x0 *= f; y0 *= f; z0 *= f; w0 *= f; } } return { x: x0, y: y0, z: z0, w: w0 }; }; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; const _SnapshotInterpolation = class _SnapshotInterpolation { constructor(props) { __publicField(this, "vault"); __publicField(this, "serverTime", -1); /** * Round-trip time between client and server * Works only in a case when client and server times are in sync */ __publicField(this, "_timeOffset", -1); __publicField(this, "_interpolationBuffer", 1e3); this.vault = new Vault(props?.vaultSize); this._interpolationBuffer = 1e3 / (props?.serverFPS || _SnapshotInterpolation.DEFAULT_SERVER_FPS) * _SnapshotInterpolation.DEFAULT_INTERPOLATION_BUFFER_FRAMES; } get interpolationBuffer() { return this._interpolationBuffer; } set interpolationBuffer(ms) { this._interpolationBuffer = ms; } get timeOffset() { return this._timeOffset; } updateTimeOffsetBySnapshot(snapshot) { const timeNow = _SnapshotInterpolation.Now(); const timeSnapshot = snapshot.timestamp; const timeOffset = timeNow - timeSnapshot; this._timeOffset = timeOffset; } addSnapshot(snapshot) { this.updateTimeOffsetBySnapshot(snapshot); this.vault.add(snapshot); } unsafe_addSnapshotWithoutSort(snapshot) { this.updateTimeOffsetBySnapshot(snapshot); this.vault.unsafe_addWithoutSort(snapshot); } calcInterpolation(parameters, deep) { const serverTime = _SnapshotInterpolation.Now() - this.timeOffset - this.interpolationBuffer; const shots = this.vault.getAroundTimestamp(serverTime); if (!shots) return; const { older, newer } = shots; if (!older || !newer) return; return this.interpolate(newer, older, serverTime, parameters, deep); } interpolate(snapshotA, snapshotB, timeOrPercentage, fields, deep) { const sorted = [snapshotA, snapshotB].sort( (a, b) => b.timestamp - a.timestamp ); const newer = sorted[0]; const older = sorted[1]; const t0 = newer.timestamp; const t1 = older.timestamp; const zeroPercent = timeOrPercentage - t1; const hundredPercent = t0 - t1; const percentage = timeOrPercentage <= 1 ? timeOrPercentage : zeroPercent / hundredPercent; this.serverTime = lerp(t1, t0, percentage); const newerState = newer.state[deep]; const olderState = older.state[deep]; const state = [...newerState]; for (const index in newerState) { const olderEntity = olderState[index]; const newerEntity = newerState[index]; Object.entries(fields).forEach(([field, method]) => { if (!method) { throw new Error(`No method specified for field "${field}"`); } const newerValue = newerEntity[field]; const olderValue = olderEntity[field]; const interpolatedValue = this.lerpByMethod( method, olderValue, newerValue, percentage ); state[index][field] = interpolatedValue; }); } return { state, percentage, newer: newer.frame, older: older.frame }; } lerpByMethod(method, start, end, alpha) { if (typeof start === "undefined" || typeof end === "undefined") return; if (typeof start === "string" || typeof end === "string") throw new Error( `SnapshotInterpolation: cannot interpolate string, got ${{ start, end }}` ); if (typeof start === "number" && typeof end === "number") { if (method === "linear") return lerp(start, end, alpha); else if (method === "deg") return degreeLerp(start, end, alpha); else if (method === "rad") return radianLerp(start, end, alpha); } if (typeof start === "object" && typeof end === "object") { if (method === "quat") return quatSlerp(start, end, alpha); } throw new Error(`No lerp method "${method}" found!`); } static Now() { return Date.now(); } }; __publicField(_SnapshotInterpolation, "DEFAULT_INTERPOLATION_BUFFER_FRAMES", 3); __publicField(_SnapshotInterpolation, "DEFAULT_SERVER_FPS", 60); let SnapshotInterpolation = _SnapshotInterpolation; exports.SnapshotInterpolation = SnapshotInterpolation; exports.Vault = Vault; exports.degreeLerp = degreeLerp; exports.lerp = lerp; exports.quatSlerp = quatSlerp; exports.radianLerp = radianLerp;