@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
711 lines • 31.4 kB
JavaScript
import { Matrix, Vector3 } from "../Maths/math.vector.js";
// This implementation was based on the original MIT-licensed TRACE repository
// from https://github.com/septagon/TRACE.
/**
* Generic implementation of Levenshtein distance.
*/
var Levenshtein;
(function (Levenshtein) {
/**
* Alphabet from which to construct sequences to be compared using Levenshtein
* distance.
*/
class Alphabet {
/**
* Serialize the Alphabet to JSON string.
* @returns JSON serialization
*/
serialize() {
const jsonObject = {};
const characters = new Array(this._characterToIdx.size);
this._characterToIdx.forEach((v, k) => {
characters[v] = k;
});
jsonObject["characters"] = characters;
jsonObject["insertionCosts"] = this._insertionCosts;
jsonObject["deletionCosts"] = this._deletionCosts;
jsonObject["substitutionCosts"] = this._substitutionCosts;
return JSON.stringify(jsonObject);
}
/**
* Parse an Alphabet from a JSON serialization.
* @param json JSON string to deserialize
* @returns deserialized Alphabet
*/
static Deserialize(json) {
const jsonObject = JSON.parse(json);
const alphabet = new Alphabet(jsonObject["characters"]);
alphabet._insertionCosts = jsonObject["insertionCosts"];
alphabet._deletionCosts = jsonObject["deletionCosts"];
alphabet._substitutionCosts = jsonObject["substitutionCosts"];
return alphabet;
}
/**
* Create a new Alphabet.
* @param characters characters of the alphabet
* @param charToInsertionCost function mapping characters to insertion costs
* @param charToDeletionCost function mapping characters to deletion costs
* @param charsToSubstitutionCost function mapping character pairs to substitution costs
*/
constructor(characters, charToInsertionCost = null, charToDeletionCost = null, charsToSubstitutionCost = null) {
charToInsertionCost = charToInsertionCost ?? (() => 1);
charToDeletionCost = charToDeletionCost ?? (() => 1);
charsToSubstitutionCost = charsToSubstitutionCost ?? ((a, b) => (a === b ? 0 : 1));
this._characterToIdx = new Map();
this._insertionCosts = new Array(characters.length);
this._deletionCosts = new Array(characters.length);
this._substitutionCosts = new Array(characters.length);
let c;
for (let outerIdx = 0; outerIdx < characters.length; ++outerIdx) {
c = characters[outerIdx];
this._characterToIdx.set(c, outerIdx);
this._insertionCosts[outerIdx] = charToInsertionCost(c);
this._deletionCosts[outerIdx] = charToDeletionCost(c);
this._substitutionCosts[outerIdx] = new Array(characters.length);
for (let innerIdx = outerIdx; innerIdx < characters.length; ++innerIdx) {
this._substitutionCosts[outerIdx][innerIdx] = charsToSubstitutionCost(c, characters[innerIdx]);
}
}
}
/**
* Get the index (internally-assigned number) for a character.
* @param char character
* @returns index
*/
getCharacterIdx(char) {
return this._characterToIdx.get(char);
}
/**
* Get the insertion cost of a character from its index.
* @param idx character index
* @returns insertion cost
*/
getInsertionCost(idx) {
return this._insertionCosts[idx];
}
/**
* Get the deletion cost of a character from its index.
* @param idx character index
* @returns deletion cost
*/
getDeletionCost(idx) {
return this._deletionCosts[idx];
}
/**
* Gets the cost to substitute two characters. NOTE: this cost is
* required to be bi-directional, meaning it cannot matter which of
* the provided characters is being removed and which is being inserted.
* @param idx1 the first character index
* @param idx2 the second character index
* @returns substitution cost
*/
getSubstitutionCost(idx1, idx2) {
const min = Math.min(idx1, idx2);
const max = Math.max(idx1, idx2);
return this._substitutionCosts[min][max];
}
}
Levenshtein.Alphabet = Alphabet;
/**
* Character sequence intended to be compared against other Sequences created
* with the same Alphabet in order to compute Levenshtein distance.
*/
class Sequence {
/**
* Serialize to JSON string. JSON representation does NOT include the Alphabet
* from which this Sequence was created; Alphabet must be independently
* serialized.
* @returns JSON string
*/
serialize() {
return JSON.stringify(this._characters);
}
/**
* Deserialize from JSON string and Alphabet. This should be the same Alphabet
* from which the Sequence was originally created, which must be serialized and
* deserialized independently so that it can be passed in here.
* @param json JSON string representation of Sequence
* @param alphabet Alphabet from which Sequence was originally created
* @returns Sequence
*/
static Deserialize(json, alphabet) {
const sequence = new Sequence([], alphabet);
sequence._characters = JSON.parse(json);
return sequence;
}
/**
* Create a new Sequence.
* @param characters characters in the new Sequence
* @param alphabet Alphabet, which must include all used characters
*/
constructor(characters, alphabet) {
if (characters.length > Sequence._MAX_SEQUENCE_LENGTH) {
throw new Error("Sequences longer than " + Sequence._MAX_SEQUENCE_LENGTH + " not supported.");
}
this._alphabet = alphabet;
this._characters = characters.map((c) => this._alphabet.getCharacterIdx(c));
}
/**
* Get the distance between this Sequence and another.
* @param other sequence to compare to
* @returns Levenshtein distance
*/
distance(other) {
return Sequence._Distance(this, other);
}
/**
* Compute the Levenshtein distance between two Sequences.
* @param a first Sequence
* @param b second Sequence
* @returns Levenshtein distance
*/
static _Distance(a, b) {
const alphabet = a._alphabet;
if (alphabet !== b._alphabet) {
throw new Error("Cannot Levenshtein compare Sequences built from different alphabets.");
}
const aChars = a._characters;
const bChars = b._characters;
const aLength = aChars.length;
const bLength = bChars.length;
const costMatrix = Sequence._CostMatrix;
costMatrix[0][0] = 0;
for (let idx = 0; idx < aLength; ++idx) {
costMatrix[idx + 1][0] = costMatrix[idx][0] + alphabet.getInsertionCost(aChars[idx]);
}
for (let idx = 0; idx < bLength; ++idx) {
costMatrix[0][idx + 1] = costMatrix[0][idx] + alphabet.getInsertionCost(bChars[idx]);
}
for (let aIdx = 0; aIdx < aLength; ++aIdx) {
for (let bIdx = 0; bIdx < bLength; ++bIdx) {
Sequence._InsertionCost = costMatrix[aIdx + 1][bIdx] + alphabet.getInsertionCost(bChars[bIdx]);
Sequence._DeletionCost = costMatrix[aIdx][bIdx + 1] + alphabet.getDeletionCost(aChars[aIdx]);
Sequence._SubstitutionCost = costMatrix[aIdx][bIdx] + alphabet.getSubstitutionCost(aChars[aIdx], bChars[bIdx]);
costMatrix[aIdx + 1][bIdx + 1] = Math.min(Sequence._InsertionCost, Sequence._DeletionCost, Sequence._SubstitutionCost);
}
}
return costMatrix[aLength][bLength];
}
}
// Scratch values
Sequence._MAX_SEQUENCE_LENGTH = 256;
Sequence._CostMatrix = [...Array(Sequence._MAX_SEQUENCE_LENGTH + 1)].map(() => new Array(Sequence._MAX_SEQUENCE_LENGTH + 1));
Levenshtein.Sequence = Sequence;
})(Levenshtein || (Levenshtein = {}));
/**
* A 3D trajectory consisting of an order list of vectors describing a
* path of motion through 3D space.
*/
export class Trajectory {
/**
* Serialize to JSON.
* @returns serialized JSON string
*/
serialize() {
return JSON.stringify(this);
}
/**
* Deserialize from JSON.
* @param json serialized JSON string
* @returns deserialized Trajectory
*/
static Deserialize(json) {
const jsonObject = JSON.parse(json);
const trajectory = new Trajectory(jsonObject["_segmentLength"]);
trajectory._points = jsonObject["_points"].map((pt) => {
return new Vector3(pt["_x"], pt["_y"], pt["_z"]);
});
return trajectory;
}
/**
* Create a new empty Trajectory.
* @param segmentLength radius of discretization for Trajectory points
*/
constructor(segmentLength = 0.01) {
this._points = [];
this._segmentLength = segmentLength;
}
/**
* Get the length of the Trajectory.
* @returns length of the Trajectory
*/
getLength() {
return this._points.length * this._segmentLength;
}
/**
* Append a new point to the Trajectory.
* NOTE: This implementation has many allocations.
* @param point point to append to the Trajectory
*/
add(point) {
let numPoints = this._points.length;
if (numPoints === 0) {
this._points.push(point.clone());
}
else {
const getT = () => this._segmentLength / Vector3.Distance(this._points[numPoints - 1], point);
for (let t = getT(); t <= 1.0; t = getT()) {
const newPoint = this._points[numPoints - 1].scale(1.0 - t);
point.scaleAndAddToRef(t, newPoint);
this._points.push(newPoint);
++numPoints;
}
}
}
/**
* Create a new Trajectory with a segment length chosen to make it
* probable that the new Trajectory will have a specified number of
* segments. This operation is imprecise.
* @param targetResolution number of segments desired
* @returns new Trajectory with approximately the requested number of segments
*/
resampleAtTargetResolution(targetResolution) {
const resampled = new Trajectory(this.getLength() / targetResolution);
for (const pt of this._points) {
resampled.add(pt);
}
return resampled;
}
/**
* Convert Trajectory segments into tokenized representation. This
* representation is an array of numbers where each nth number is the
* index of the token which is most similar to the nth segment of the
* Trajectory.
* @param tokens list of vectors which serve as discrete tokens
* @returns list of indices of most similar token per segment
*/
tokenize(tokens) {
const tokenization = [];
const segmentDir = new Vector3();
for (let idx = 2; idx < this._points.length; ++idx) {
if (Trajectory._TransformSegmentDirToRef(this._points[idx - 2], this._points[idx - 1], this._points[idx], segmentDir)) {
tokenization.push(Trajectory._TokenizeSegment(segmentDir, tokens));
}
}
return tokenization;
}
/**
* Transform the rotation (i.e., direction) of a segment to isolate
* the relative transformation represented by the segment. This operation
* may or may not succeed due to singularities in the equations that define
* motion relativity in this context.
* @param priorVec the origin of the prior segment
* @param fromVec the origin of the current segment
* @param toVec the destination of the current segment
* @param result reference to output variable
* @returns whether or not transformation was successful
*/
static _TransformSegmentDirToRef(priorVec, fromVec, toVec, result) {
const DOT_PRODUCT_SAMPLE_REJECTION_THRESHOLD = 0.98;
fromVec.subtractToRef(priorVec, Trajectory._ForwardDir);
Trajectory._ForwardDir.normalize();
fromVec.scaleToRef(-1, Trajectory._InverseFromVec);
Trajectory._InverseFromVec.normalize();
if (Math.abs(Vector3.Dot(Trajectory._ForwardDir, Trajectory._InverseFromVec)) > DOT_PRODUCT_SAMPLE_REJECTION_THRESHOLD) {
return false;
}
Vector3.CrossToRef(Trajectory._ForwardDir, Trajectory._InverseFromVec, Trajectory._UpDir);
Trajectory._UpDir.normalize();
Matrix.LookAtLHToRef(priorVec, fromVec, Trajectory._UpDir, Trajectory._LookMatrix);
toVec.subtractToRef(fromVec, Trajectory._FromToVec);
Trajectory._FromToVec.normalize();
Vector3.TransformNormalToRef(Trajectory._FromToVec, Trajectory._LookMatrix, result);
return true;
}
/**
* Determine which token vector is most similar to the
* segment vector.
* @param segment segment vector
* @param tokens token vector list
* @returns index of the most similar token to the segment
*/
static _TokenizeSegment(segment, tokens) {
Trajectory._BestMatch = 0;
Trajectory._Score = Vector3.Dot(segment, tokens[0]);
Trajectory._BestScore = Trajectory._Score;
for (let idx = 1; idx < tokens.length; ++idx) {
Trajectory._Score = Vector3.Dot(segment, tokens[idx]);
if (Trajectory._Score > Trajectory._BestScore) {
Trajectory._BestMatch = idx;
Trajectory._BestScore = Trajectory._Score;
}
}
return Trajectory._BestMatch;
}
}
Trajectory._ForwardDir = new Vector3();
Trajectory._InverseFromVec = new Vector3();
Trajectory._UpDir = new Vector3();
Trajectory._FromToVec = new Vector3();
Trajectory._LookMatrix = new Matrix();
/**
* Collection of vectors intended to be used as the basis of Trajectory
* tokenization for Levenshtein distance comparison. Canonically, a
* Vector3Alphabet will resemble a "spikeball" of vectors distributed
* roughly evenly over the surface of the unit sphere.
*/
class Vector3Alphabet {
/**
* Helper method to create new "spikeball" Vector3Alphabets. Uses a naive
* optimize-from-random strategy to space points around the unit sphere
* surface as a simple alternative to really doing the math to tile the
* sphere.
* @param alphabetSize size of the desired alphabet
* @param iterations number of iterations over which to optimize the "spikeball"
* @param startingStepSize distance factor to move points in early optimization iterations
* @param endingStepSize distance factor to move points in late optimization iterations
* @param fixedValues alphabet "characters" that are required and cannot be moved by optimization
* @returns a new randomly generated and optimized Vector3Alphabet of the specified size
*/
static Generate(alphabetSize = 64, iterations = 256, startingStepSize = 0.1, endingStepSize = 0.001, fixedValues = []) {
const EPSILON = 0.001;
const EPSILON_SQUARED = EPSILON * EPSILON;
const alphabet = new Vector3Alphabet(alphabetSize);
for (let idx = 0; idx < alphabetSize; ++idx) {
alphabet.chars[idx] = new Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5);
alphabet.chars[idx].normalize();
}
for (let idx = 0; idx < fixedValues.length; ++idx) {
alphabet.chars[idx].copyFrom(fixedValues[idx]);
}
let stepSize;
let distSq;
const force = new Vector3();
const scratch = new Vector3();
const lerp = (l, r, t) => (1.0 - t) * l + t * r;
for (let iteration = 0; iteration < iterations; ++iteration) {
stepSize = lerp(startingStepSize, endingStepSize, iteration / (iterations - 1));
for (let idx = fixedValues.length; idx < alphabet.chars.length; ++idx) {
force.copyFromFloats(0, 0, 0);
for (const pt of alphabet.chars) {
alphabet.chars[idx].subtractToRef(pt, scratch);
distSq = scratch.lengthSquared();
if (distSq > EPSILON_SQUARED) {
scratch.scaleAndAddToRef(1 / (scratch.lengthSquared() * distSq), force);
}
}
force.scaleInPlace(stepSize);
alphabet.chars[idx].addInPlace(force);
alphabet.chars[idx].normalize();
}
}
return alphabet;
}
/**
* Serialize to JSON.
* @returns JSON serialization
*/
serialize() {
return JSON.stringify(this.chars);
}
/**
* Deserialize from JSON.
* @param json JSON serialization
* @returns deserialized Vector3Alphabet
*/
static Deserialize(json) {
const jsonObject = JSON.parse(json);
const alphabet = new Vector3Alphabet(jsonObject.length);
for (let idx = 0; idx < jsonObject.length; ++idx) {
alphabet.chars[idx] = new Vector3(jsonObject[idx]["_x"], jsonObject[idx]["_y"], jsonObject[idx]["_z"]);
}
return alphabet;
}
constructor(size) {
this.chars = new Array(size);
}
}
/**
* Class which formalizes the manner in which a Vector3Alphabet is used to tokenize and
* describe a Trajectory. This class houses the functionality which determines what
* attributes of Trajectories are and are not considered important, such as scale.
*/
class TrajectoryDescriptor {
/**
* Serialize to JSON.
* @returns JSON serialization
*/
serialize() {
return JSON.stringify(this._sequences.map((sequence) => sequence.serialize()));
}
/**
* Deserialize from JSON string and Alphabet. This should be the same Alphabet
* from which the descriptor was originally created, which must be serialized and
* deserialized independently so that it can be passed in here.
* @param json JSON serialization
* @param alphabet Alphabet from which descriptor was originally created
* @returns deserialized TrajectoryDescriptor
*/
static Deserialize(json, alphabet) {
const descriptor = new TrajectoryDescriptor();
descriptor._sequences = JSON.parse(json).map((s) => Levenshtein.Sequence.Deserialize(s, alphabet));
return descriptor;
}
/**
* Create a new TrajectoryDescriptor to describe a provided Trajectory according
* to the provided alphabets.
* @param trajectory Trajectory to be described
* @param vector3Alphabet Vector3Alphabet to be used to tokenize the Trajectory
* @param levenshteinAlphabet Levenshtein.Alphabet to be used as basis for comparison with other descriptors
* @returns TrajectoryDescriptor describing provided Trajectory
*/
static CreateFromTrajectory(trajectory, vector3Alphabet, levenshteinAlphabet) {
return TrajectoryDescriptor.CreateFromTokenizationPyramid(TrajectoryDescriptor._GetTokenizationPyramid(trajectory, vector3Alphabet), levenshteinAlphabet);
}
/**
* Create a new TrajectoryDescriptor from a pre-existing pyramid of tokens.
* NOTE: This function exists to support an outdated serialization mechanism and should
* be deleted if it is no longer useful.
* @param pyramid tokenization pyramid
* @param levenshteinAlphabet Levenshtein.Alphabet to be uses as basis for comparison with other descriptors
* @returns TrajectoryDescriptor describing the Trajectory from which the pyramid was built
*/
static CreateFromTokenizationPyramid(pyramid, levenshteinAlphabet) {
const descriptor = new TrajectoryDescriptor();
descriptor._sequences = pyramid.map((tokens) => new Levenshtein.Sequence(tokens, levenshteinAlphabet));
return descriptor;
}
constructor() {
this._sequences = [];
}
/**
* Create the tokenization pyramid for the provided Trajectory according to the given
* Vector3Alphabet.
* @param trajectory Trajectory to be tokenized
* @param alphabet Vector3Alphabet containing tokens
* @param targetResolution finest resolution of descriptor
* @returns tokenization pyramid for Trajectory
*/
static _GetTokenizationPyramid(trajectory, alphabet, targetResolution = TrajectoryDescriptor._FINEST_DESCRIPTOR_RESOLUTION) {
const pyramid = [];
for (let res = targetResolution; res > 4; res = Math.floor(res / 2)) {
pyramid.push(trajectory.resampleAtTargetResolution(res).tokenize(alphabet.chars));
}
return pyramid;
}
/**
* Calculate a distance metric between this TrajectoryDescriptor and another. This is
* essentially a similarity score and does not directly represent Euclidean distance,
* edit distance, or any other formal distance metric.
* @param other TrajectoryDescriptor from which to determine distance
* @returns distance, a nonnegative similarity score where larger values indicate dissimilarity
*/
distance(other) {
let totalDistance = 0;
let weight;
for (let idx = 0; idx < this._sequences.length; ++idx) {
weight = Math.pow(2, idx);
totalDistance += weight * this._sequences[idx].distance(other._sequences[idx]);
}
return totalDistance;
}
}
TrajectoryDescriptor._FINEST_DESCRIPTOR_RESOLUTION = 32;
/**
* A set of TrajectoryDescriptors defined to be "the same." This is essentially a helper
* class to facilitate methods of Trajectory clustering.
*/
class TrajectoryClass {
/**
* Serialize to JSON.
* @returns JSON serialization
*/
serialize() {
const jsonObject = {};
jsonObject.descriptors = this._descriptors.map((desc) => desc.serialize());
jsonObject.centroidIdx = this._centroidIdx;
jsonObject.averageDistance = this._averageDistance;
return JSON.stringify(jsonObject);
}
/**
* Deserialize from JSON string and Alphabet. This should be the same Alphabet
* from which the descriptors were originally created, which must be serialized and
* deserialized independently so that it can be passed in here.
* @param json JSON string representation
* @param alphabet Alphabet from which TrajectoryDescriptors were originally created
* @returns deserialized TrajectoryDescriptor
*/
static Deserialize(json, alphabet) {
const jsonObject = JSON.parse(json);
const described = new TrajectoryClass();
described._descriptors = jsonObject.descriptors.map((s) => TrajectoryDescriptor.Deserialize(s, alphabet));
described._centroidIdx = jsonObject.centroidIdx;
described._averageDistance = jsonObject.averageDistance;
return described;
}
/**
* Create a new DescribedTrajectory.
* @param descriptors currently-known TrajectoryDescriptors, if any
*/
constructor(descriptors = []) {
this._descriptors = descriptors;
this._centroidIdx = -1;
this._averageDistance = 0;
this._refreshDescription();
}
/**
* Add a new TrajectoryDescriptor to the list of descriptors known to describe
* this same DescribedTrajectory.
* @param descriptor descriptor to be added
*/
add(descriptor) {
this._descriptors.push(descriptor);
this._refreshDescription();
}
/**
* Compute the cost, which is inversely related to the likelihood that the provided
* TrajectoryDescriptor describes a Trajectory that is considered to be the same as
* the class represented by this DescribedTrajectory.
* @param descriptor the descriptor to be costed
* @returns cost of the match, which is a nonnegative similarity metric where larger values indicate dissimilarity
*/
getMatchCost(descriptor) {
return descriptor.distance(this._descriptors[this._centroidIdx]) / this._averageDistance;
}
/**
* Compute the minimum distance between the queried TrajectoryDescriptor and a
* descriptor which is a member of this collection. This is an alternative way of
* conceptualizing match cost from getMatchCost(), and it serves a different function.
* @param descriptor the descriptor to find the minimum distance to
* @returns minimum descriptor distance to a member descriptor of this DescribedTrajectory
*/
getMatchMinimumDistance(descriptor) {
return Math.min(...this._descriptors.map((desc) => desc.distance(descriptor)));
}
/**
* Refreshes the internal representation of this DescribedTrajectory.
*/
_refreshDescription() {
this._centroidIdx = -1;
let sum;
const distances = this._descriptors.map((a) => {
sum = 0;
for (const b of this._descriptors) {
sum += a.distance(b);
}
return sum;
});
for (let idx = 0; idx < distances.length; ++idx) {
if (this._centroidIdx < 0 || distances[idx] < distances[this._centroidIdx]) {
this._centroidIdx = idx;
}
}
this._averageDistance = 0;
for (const desc of this._descriptors) {
this._averageDistance += desc.distance(this._descriptors[this._centroidIdx]);
}
if (this._descriptors.length > 0) {
this._averageDistance = Math.max(this._averageDistance / this._descriptors.length, TrajectoryClass._MIN_AVERAGE_DISTANCE);
}
}
}
TrajectoryClass._MIN_AVERAGE_DISTANCE = 1;
/**
* Class representing a set of known, named trajectories to which Trajectories can be
* added and using which Trajectories can be recognized.
*/
export class TrajectoryClassifier {
/**
* Serialize to JSON.
* @returns JSON serialization
*/
serialize() {
const jsonObject = {};
jsonObject.maximumAllowableMatchCost = this._maximumAllowableMatchCost;
jsonObject.vector3Alphabet = this._vector3Alphabet.serialize();
jsonObject.levenshteinAlphabet = this._levenshteinAlphabet.serialize();
jsonObject.nameToDescribedTrajectory = [];
this._nameToDescribedTrajectory.forEach((described, name) => {
jsonObject.nameToDescribedTrajectory.push(name);
jsonObject.nameToDescribedTrajectory.push(described.serialize());
});
return JSON.stringify(jsonObject);
}
/**
* Deserialize from JSON.
* @param json JSON serialization
* @returns deserialized TrajectorySet
*/
static Deserialize(json) {
const jsonObject = JSON.parse(json);
const classifier = new TrajectoryClassifier();
classifier._maximumAllowableMatchCost = jsonObject.maximumAllowableMatchCost;
classifier._vector3Alphabet = Vector3Alphabet.Deserialize(jsonObject.vector3Alphabet);
classifier._levenshteinAlphabet = Levenshtein.Alphabet.Deserialize(jsonObject.levenshteinAlphabet);
for (let idx = 0; idx < jsonObject.nameToDescribedTrajectory.length; idx += 2) {
classifier._nameToDescribedTrajectory.set(jsonObject.nameToDescribedTrajectory[idx], TrajectoryClass.Deserialize(jsonObject.nameToDescribedTrajectory[idx + 1], classifier._levenshteinAlphabet));
}
return classifier;
}
/**
* Initialize a new empty TrajectorySet with auto-generated Alphabets.
* VERY naive, need to be generating these things from known
* sets. Better version later, probably eliminating this one.
* @returns auto-generated TrajectorySet
*/
static Generate() {
const vecs = Vector3Alphabet.Generate(64, 256, 0.1, 0.001, [Vector3.Forward()]);
const charIdxs = new Array(vecs.chars.length);
for (let idx = 0; idx < charIdxs.length; ++idx) {
charIdxs[idx] = idx;
}
const alphabet = new Levenshtein.Alphabet(charIdxs, (idx) => (idx === 0 ? 0 : 1), (idx) => (idx === 0 ? 0 : 1), (a, b) => Math.min(1 - Vector3.Dot(vecs.chars[a], vecs.chars[b]), 1));
const trajectorySet = new TrajectoryClassifier();
trajectorySet._vector3Alphabet = vecs;
trajectorySet._levenshteinAlphabet = alphabet;
return trajectorySet;
}
constructor() {
this._maximumAllowableMatchCost = 4;
this._nameToDescribedTrajectory = new Map();
}
/**
* Add a new Trajectory to the set with a given name.
* @param trajectory new Trajectory to be added
* @param classification name to which to add the Trajectory
*/
addTrajectoryToClassification(trajectory, classification) {
if (!this._nameToDescribedTrajectory.has(classification)) {
this._nameToDescribedTrajectory.set(classification, new TrajectoryClass());
}
this._nameToDescribedTrajectory.get(classification).add(TrajectoryDescriptor.CreateFromTrajectory(trajectory, this._vector3Alphabet, this._levenshteinAlphabet));
}
/**
* Remove a known named trajectory and all Trajectories associated with it.
* @param classification name to remove
* @returns whether anything was removed
*/
deleteClassification(classification) {
return this._nameToDescribedTrajectory.delete(classification);
}
/**
* Attempt to recognize a Trajectory from among all the classifications
* already known to the classifier.
* @param trajectory Trajectory to be recognized
* @returns classification of Trajectory if recognized, null otherwise
*/
classifyTrajectory(trajectory) {
const descriptor = TrajectoryDescriptor.CreateFromTrajectory(trajectory, this._vector3Alphabet, this._levenshteinAlphabet);
const allowableMatches = [];
this._nameToDescribedTrajectory.forEach((trajectoryClass, classification) => {
if (trajectoryClass.getMatchCost(descriptor) < this._maximumAllowableMatchCost) {
allowableMatches.push(classification);
}
});
if (allowableMatches.length === 0) {
return null;
}
let bestIdx = 0;
let bestMatch = this._nameToDescribedTrajectory.get(allowableMatches[bestIdx]).getMatchMinimumDistance(descriptor);
let match;
for (let idx = 0; idx < allowableMatches.length; ++idx) {
match = this._nameToDescribedTrajectory.get(allowableMatches[idx]).getMatchMinimumDistance(descriptor);
if (match < bestMatch) {
bestMatch = match;
bestIdx = idx;
}
}
return allowableMatches[bestIdx];
}
}
//# sourceMappingURL=trajectoryClassifier.js.map