@vscubing/cubing
Version:
A collection of JavaScript cubing libraries.
1,781 lines (1,766 loc) • 146 kB
JavaScript
import {
Move,
QuantumMove
} from "../chunks/chunk-T3WO4S5D.js";
// src/cubing/puzzle-geometry/colors.ts
function defaultPlatonicColorSchemes() {
return {
// the colors should use the same naming convention as the nets, above.
4: {
F: "#44ee00" /* Green */,
D: "#f4f400" /* Yellow */,
L: "#ff0000" /* Red */,
R: "#2266ff" /* Blue */
},
6: {
U: "#ffffff" /* White */,
F: "#44ee00" /* Green */,
R: "#ff0000" /* Red */,
D: "#f4f400" /* Yellow */,
B: "#2266ff" /* Blue */,
L: "#ff8000" /* Orange */
},
8: {
U: "#ffffff" /* White */,
F: "#44ee00" /* Green */,
R: "#ff0000" /* Red */,
D: "#f4f400" /* Yellow */,
BB: "#2266ff" /* Blue */,
L: "#8800dd" /* Purple */,
BL: "#ff8000" /* Orange */,
BR: "#888888" /* MediumGray */
},
12: {
U: "#ffffff" /* White */,
F: "#008800" /* DarkGreen */,
R: "#ff0000" /* Red */,
C: "#e8d0a0" /* Cream */,
A: "#3399ff" /* Aqua */,
L: "#8800dd" /* Purple */,
E: "#ff66cc" /* Pink */,
BF: "#99ff00" /* Lime */,
BR: "#0000ff" /* BoldBlue */,
BL: "#f4f400" /* Yellow */,
I: "#ff8000" /* Orange */,
D: "#888888" /* MediumGray */
},
20: {
R: "#f4f400" /* Yellow */,
C: "#d41f69" /* Cerise */,
F: "#008800" /* DarkGreen */,
E: "#5c5c5c" /* DarkGray */,
L: "#8800dd" /* Purple */,
U: "#ffffff" /* White */,
A: "#007a89" /* Teal */,
G: "#ff0000" /* Red */,
I: "#7d3b11" /* Brown */,
S: "#b9a1ff" /* Lavender */,
H: "#3399ff" /* Aqua */,
J: "#5ec4b6" /* SeaGreen */,
B: "#44ee00" /* Green */,
K: "#e8d0a0" /* Cream */,
D: "#aaaaaa" /* LightGray */,
M: "#ff66cc" /* Pink */,
O: "#292929" /* harcoal */,
P: "#ff8000" /* Orange */,
N: "#980000" /* Burgundy */,
Q: "#0000ff" /* BoldBlue */
}
};
}
// src/cubing/puzzle-geometry/FaceNameSwizzler.ts
var FaceNameSwizzler = class {
constructor(facenames, gripnames_arg) {
this.facenames = facenames;
if (gripnames_arg) {
this.gripnames = gripnames_arg;
}
for (let i = 0; this.prefixFree && i < facenames.length; i++) {
for (let j = 0; this.prefixFree && j < facenames.length; j++) {
if (i !== j && facenames[i].startsWith(facenames[j])) {
this.prefixFree = false;
}
}
}
}
prefixFree = true;
gripnames = [];
setGripNames(names) {
this.gripnames = names;
}
// split a string into face names and return a list of
// indices.
splitByFaceNames(s) {
const r = [];
let at = 0;
while (at < s.length) {
if (at > 0 && at < s.length && s[at] === "_") {
at++;
}
let currentMatch = -1;
for (let i = 0; i < this.facenames.length; i++) {
if (s.substr(at).startsWith(this.facenames[i]) && (currentMatch < 0 || this.facenames[i].length > this.facenames[currentMatch].length)) {
currentMatch = i;
}
}
if (currentMatch >= 0) {
r.push(currentMatch);
at += this.facenames[currentMatch].length;
} else {
throw new Error(`Could not split ${s} into face names.`);
}
}
return r;
}
// cons a grip from an array of numbers.
joinByFaceIndices(list) {
let sep = "";
const r = [];
for (let i = 0; i < list.length; i++) {
r.push(sep);
r.push(this.facenames[list[i]]);
if (!this.prefixFree) {
sep = "_";
}
}
return r.join("");
}
/*
* Try to match something the user gave us with some geometric
* feature. We used to have strict requirements:
*
* a) The set of face names are prefix free
* b) When specifying a corner, all coincident planes were
* specified
*
* But, to allow megaminx to have more reasonable and
* conventional names, and to permit shorter canonical
* names, we are relaxing these requirements and adding
* new syntax. Now:
*
* a) Face names need not be syntax free.
* b) When parsing a geometric name, we use greedy
* matching, so the longest name that matches the
* user string at the current position is the one
* assumed to match.
* c) Underscores are permitted to separate face names
* (both in user input and in geometric
* descriptions).
* d) Default names of corner moves where corners have
* more than three corners, need only include three
* of the corners.
*
* This code is not performance-sensitive so we can do it a
* slow and simple way.
*/
spinmatch(userinput, longname) {
if (userinput === longname) {
return true;
}
try {
const e1 = this.splitByFaceNames(userinput);
const e2 = this.splitByFaceNames(longname);
if (e1.length !== e2.length && e1.length < 3) {
return false;
}
for (let i = 0; i < e1.length; i++) {
for (let j = 0; j < i; j++) {
if (e1[i] === e1[j]) {
return false;
}
}
let found = false;
for (let j = 0; j < e2.length; j++) {
if (e1[i] === e2[j]) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
} catch {
return false;
}
}
/* same as above, but permit both to have v's on the end. */
spinmatchv(userinput, longname) {
if (userinput.endsWith("v") && longname.endsWith("v")) {
return this.spinmatch(
userinput.slice(0, userinput.length - 1),
longname.slice(0, longname.length - 1)
);
} else {
return this.spinmatch(userinput, longname);
}
}
unswizzle(s) {
if ((s.endsWith("v") || s.endsWith("w")) && s[0] <= "Z") {
s = s.slice(0, s.length - 1);
}
const upperCaseGrip = s.toUpperCase();
for (let i = 0; i < this.gripnames.length; i++) {
const g = this.gripnames[i];
if (this.spinmatch(upperCaseGrip, g)) {
return g;
}
}
return s;
}
};
// src/cubing/puzzle-geometry/notation-mapping/FaceRenamingMapper.ts
var FaceRenamingMapper = class {
constructor(internalNames, externalNames) {
this.internalNames = internalNames;
this.externalNames = externalNames;
}
// TODO: consider putting a cache in front of this
convertString(grip, a, b) {
let suffix = "";
if ((grip.endsWith("v") || grip.endsWith("v")) && grip <= "_") {
suffix = grip.slice(grip.length - 1);
grip = grip.slice(0, grip.length - 1);
}
const upper = grip.toUpperCase();
let isLowerCase = false;
if (grip !== upper) {
isLowerCase = true;
grip = upper;
}
grip = b.joinByFaceIndices(a.splitByFaceNames(grip));
if (isLowerCase) {
grip = grip.toLowerCase();
}
return grip + suffix;
}
convert(move, a, b) {
const grip = move.family;
const ngrip = this.convertString(grip, a, b);
if (grip === ngrip) {
return move;
} else {
return new Move(
new QuantumMove(ngrip, move.innerLayer, move.outerLayer),
move.amount
);
}
}
notationToInternal(move) {
const r = this.convert(move, this.externalNames, this.internalNames);
return r;
}
notationToExternal(move) {
return this.convert(move, this.internalNames, this.externalNames);
}
};
// src/cubing/puzzle-geometry/notation-mapping/FTONotationMapper.ts
var FTONotationMapper = class {
constructor(child, sw) {
this.child = child;
this.sw = sw;
}
notationToInternal(move) {
if (move.family === "T" && move.innerLayer === void 0 && move.outerLayer === void 0) {
return new Move(
new QuantumMove("FLRv", move.innerLayer, move.outerLayer),
move.amount
);
} else {
const r = this.child.notationToInternal(move);
return r;
}
}
// we never rewrite click moves to these moves.
notationToExternal(move) {
let fam = move.family;
if (fam.length > 0 && fam[fam.length - 1] === "v") {
fam = fam.substring(0, fam.length - 1);
}
if (this.sw.spinmatch(fam, "FLUR")) {
return new Move(
new QuantumMove("T", move.innerLayer, move.outerLayer),
move.amount
);
}
return this.child.notationToExternal(move);
}
};
// src/cubing/puzzle-geometry/notation-mapping/MegaminxScramblingNotationMapper.ts
var MegaminxScramblingNotationMapper = class {
constructor(child) {
this.child = child;
}
notationToInternal(move) {
if (move.innerLayer === void 0 && move.outerLayer === void 0) {
if (Math.abs(move.amount) === 1) {
if (move.family === "R++") {
return new Move(new QuantumMove("L", 3, 2), -2 * move.amount);
} else if (move.family === "R--") {
return new Move(new QuantumMove("L", 3, 2), 2 * move.amount);
} else if (move.family === "D++") {
return new Move(new QuantumMove("U", 3, 2), -2 * move.amount);
} else if (move.family === "D--") {
return new Move(new QuantumMove("U", 3, 2), 2 * move.amount);
}
if (move.family === "R_PLUSPLUS_") {
return new Move(new QuantumMove("L", 3, 2), -2 * move.amount);
} else if (move.family === "D_PLUSPLUS_") {
return new Move(new QuantumMove("U", 3, 2), -2 * move.amount);
}
}
if (move.family === "y") {
return new Move("Uv", move.amount);
}
if (move.family === "x" && Math.abs(move.amount) === 2) {
return new Move("ERv", move.amount / 2);
}
}
return this.child.notationToInternal(move);
}
// we never rewrite click moves to these moves.
notationToExternal(move) {
if (move.family === "ERv" && Math.abs(move.amount) === 1) {
return new Move(
new QuantumMove("x", move.innerLayer, move.outerLayer),
move.amount * 2
);
}
if (move.family === "ILv" && Math.abs(move.amount) === 1) {
return new Move(
new QuantumMove("x", move.innerLayer, move.outerLayer),
-move.amount * 2
);
}
if (move.family === "Uv") {
return new Move(
new QuantumMove("y", move.innerLayer, move.outerLayer),
move.amount
);
}
if (move.family === "Dv") {
return new Move("y", -move.amount);
}
return this.child.notationToExternal(move);
}
};
// src/cubing/puzzle-geometry/notation-mapping/NullMapper.ts
var NullMapper = class {
notationToInternal(move) {
return move;
}
notationToExternal(move) {
return move;
}
};
// src/cubing/puzzle-geometry/notation-mapping/NxNxNCubeMapper.ts
var NxNxNCubeMapper = class {
constructor(slices) {
this.slices = slices;
}
notationToInternal(move) {
const grip = move.family;
if (!(move.innerLayer || move.outerLayer)) {
if (grip === "x") {
move = new Move("Rv", move.amount);
} else if (grip === "y") {
move = new Move("Uv", move.amount);
} else if (grip === "z") {
move = new Move("Fv", move.amount);
}
if ((this.slices & 1) === 1) {
if (grip === "E") {
move = new Move(
new QuantumMove("D", (this.slices + 1) / 2),
move.amount
);
} else if (grip === "M") {
move = new Move(
new QuantumMove("L", (this.slices + 1) / 2),
move.amount
);
} else if (grip === "S") {
move = new Move(
new QuantumMove("F", (this.slices + 1) / 2),
move.amount
);
}
}
if (this.slices > 2) {
if (grip === "e") {
move = new Move(
new QuantumMove("D", this.slices - 1, 2),
move.amount
);
} else if (grip === "m") {
move = new Move(
new QuantumMove("L", this.slices - 1, 2),
move.amount
);
} else if (grip === "s") {
move = new Move(
new QuantumMove("F", this.slices - 1, 2),
move.amount
);
}
}
}
return move;
}
// do we want to map slice moves to E/M/S instead of 2U/etc.?
notationToExternal(move) {
const grip = move.family;
if (!(move.innerLayer || move.outerLayer)) {
if (grip === "Rv") {
return new Move("x", move.amount);
} else if (grip === "Uv") {
return new Move("y", move.amount);
} else if (grip === "Fv") {
return new Move("z", move.amount);
} else if (grip === "Lv") {
return new Move("x", -move.amount);
} else if (grip === "Dv") {
return new Move("y", -move.amount);
} else if (grip === "Bv") {
return new Move("z", -move.amount);
}
}
return move;
}
};
// src/cubing/puzzle-geometry/notation-mapping/PyraminxNotationMapper.ts
var pyraminxFamilyMap = {
U: "frl",
L: "fld",
R: "fdr",
B: "dlr",
u: "FRL",
l: "FLD",
r: "FDR",
b: "DLR",
Uv: "FRLv",
Lv: "FLDv",
Rv: "FDRv",
Bv: "DLRv",
D: "D",
F: "F",
BL: "L",
BR: "R"
};
var tetraminxFamilyMap = {
U: "FRL",
L: "FLD",
R: "FDR",
B: "DLR",
u: "frl",
l: "fld",
r: "fdr",
b: "dlr",
Uv: "FRLv",
Lv: "FLDv",
Rv: "FDRv",
Bv: "DLRv",
D: "D",
F: "F",
BL: "L",
BR: "R",
d: "d",
f: "f",
bl: "l",
br: "r"
};
var pyraminxFamilyMapWCA = {
U: "FRL",
L: "FLD",
R: "FDR",
B: "DLR"
};
var pyraminxExternalQuantumY = new QuantumMove("y");
var pyraminxInternalQuantumY = new QuantumMove("Dv");
var PyraminxNotationMapper = class {
constructor(child) {
this.child = child;
}
wcaHack = false;
map = pyraminxFamilyMap;
notationToInternal(move) {
if (this.wcaHack && move.innerLayer === 2 && move.outerLayer === null) {
const newFamilyWCA = pyraminxFamilyMapWCA[move.family];
if (newFamilyWCA) {
return new Move(
new QuantumMove(newFamilyWCA, move.innerLayer, move.outerLayer),
move.amount
);
}
}
const newFamily = this.map[move.family];
if (newFamily) {
return new Move(
new QuantumMove(newFamily, move.innerLayer, move.outerLayer),
move.amount
);
} else if (pyraminxExternalQuantumY.isIdentical(move.quantum)) {
return new Move(pyraminxInternalQuantumY, -move.amount);
} else {
return null;
}
}
// we never rewrite click moves to these moves.
notationToExternal(move) {
if (this.wcaHack && move.innerLayer === 2 && move.outerLayer === null) {
for (const [external, internal] of Object.entries(pyraminxFamilyMapWCA)) {
if (this.child.spinmatch(move.family, internal)) {
return new Move(
new QuantumMove(external, move.innerLayer, move.outerLayer),
move.amount
);
}
}
}
for (const [external, internal] of Object.entries(this.map)) {
if (this.child.spinmatch(move.family, internal)) {
return new Move(
new QuantumMove(external, move.innerLayer, move.outerLayer),
move.amount
);
}
}
if (pyraminxInternalQuantumY.isIdentical(move.quantum)) {
return new Move(pyraminxExternalQuantumY, -move.amount);
} else {
return null;
}
}
};
var TetraminxNotationMapper = class extends PyraminxNotationMapper {
wcaHack = true;
constructor(child) {
super(child);
this.map = tetraminxFamilyMap;
}
};
// src/cubing/puzzle-geometry/notation-mapping/SkewbNotationMapper.ts
var skewbFamilyMap = {
U: "UBL",
UL: "ULF",
F: "UFR",
UR: "URB",
B: "DBL",
D: "DFR",
L: "DLF",
R: "DRB",
Uv: "UBLv",
ULv: "ULFv",
Fv: "UFRv",
URv: "URBv",
Bv: "DBLv",
Dv: "DFRv",
Lv: "DLFv",
Rv: "DRBv"
};
var skewbExternalQuantumX = new QuantumMove("x");
var skewbInternalQuantumX = new QuantumMove("Rv");
var skewbInternalQuantumXPrime = new QuantumMove("Lv");
var skewbExternalQuantumY = new QuantumMove("y");
var skewbInternalQuantumY = new QuantumMove("Uv");
var skewbInternalQuantumYPrime = new QuantumMove("Dv");
var skewbExternalQuantumZ = new QuantumMove("z");
var skewbInternalQuantumZ = new QuantumMove("Fv");
var skewbInternalQuantumZPrime = new QuantumMove("Bv");
var SkewbNotationMapper = class {
constructor(child) {
this.child = child;
}
notationToInternal(move) {
if (move.innerLayer || move.outerLayer) {
return null;
}
const newFamily = skewbFamilyMap[move.family];
if (newFamily) {
return new Move(
new QuantumMove(newFamily, move.outerLayer, move.innerLayer),
move.amount
);
}
if (skewbExternalQuantumX.isIdentical(move.quantum)) {
return new Move(skewbInternalQuantumX, move.amount);
}
if (skewbExternalQuantumY.isIdentical(move.quantum)) {
return new Move(skewbInternalQuantumY, move.amount);
}
if (skewbExternalQuantumZ.isIdentical(move.quantum)) {
return new Move(skewbInternalQuantumZ, move.amount);
}
return null;
}
// we never rewrite click moves to these moves.
notationToExternal(move) {
for (const [external, internal] of Object.entries(skewbFamilyMap)) {
if (this.child.spinmatchv(move.family, internal)) {
return new Move(
new QuantumMove(external, move.innerLayer, move.outerLayer),
move.amount
);
}
}
if (skewbInternalQuantumX.isIdentical(move.quantum)) {
return new Move(skewbExternalQuantumX, move.amount);
}
if (skewbInternalQuantumXPrime.isIdentical(move.quantum)) {
return new Move(skewbExternalQuantumX, -move.amount);
}
if (skewbInternalQuantumY.isIdentical(move.quantum)) {
return new Move(skewbExternalQuantumY, move.amount);
}
if (skewbInternalQuantumYPrime.isIdentical(move.quantum)) {
return new Move(skewbExternalQuantumY, -move.amount);
}
if (skewbInternalQuantumZ.isIdentical(move.quantum)) {
return new Move(skewbExternalQuantumZ, move.amount);
}
if (skewbInternalQuantumZPrime.isIdentical(move.quantum)) {
return new Move(skewbExternalQuantumZ, -move.amount);
}
return null;
}
};
// src/cubing/puzzle-geometry/notation-mapping/NotationMapper.ts
function remapKPuzzleDefinition(internalDefinition, notationMapper) {
const externalDefinition = {
...internalDefinition,
moves: {}
};
for (const [internalMoveName, transformationData] of Object.entries(
internalDefinition.moves
)) {
let prefix = internalMoveName;
let suffix = "";
if (["v", "w"].includes(internalMoveName.at(-1))) {
prefix = internalMoveName.slice(0, -1);
suffix = internalMoveName.slice(-1);
}
const externalPrefix = notationMapper.notationToExternal(
Move.fromString(prefix)
);
if (!externalPrefix) {
continue;
}
const externalMoveName = externalPrefix + suffix;
if (!externalMoveName) {
throw new Error(
`Missing external move name for: ${internalMoveName.toString()}`
);
}
externalDefinition.moves[externalMoveName.toString()] = transformationData;
}
return externalDefinition;
}
// src/cubing/puzzle-geometry/Perm.ts
var zeroCache = [];
var iotaCache = [];
function zeros(n) {
if (!zeroCache[n]) {
const c = Array(n);
for (let i = 0; i < n; i++) {
c[i] = 0;
}
zeroCache[n] = c;
}
return zeroCache[n];
}
function iota(n) {
if (!iotaCache[n]) {
const c = Array(n);
for (let i = 0; i < n; i++) {
c[i] = i;
}
iotaCache[n] = c;
}
return iotaCache[n];
}
function identity(n) {
return new Perm(iota(n));
}
function factorial(a) {
let r = BigInt(1);
while (a > 1) {
r *= BigInt(a);
a--;
}
return r;
}
function gcd(a, b) {
if (a > b) {
const t = a;
a = b;
b = t;
}
while (a > 0) {
const m = b % a;
b = a;
a = m;
}
return b;
}
function lcm(a, b) {
return a / gcd(a, b) * b;
}
var Perm = class _Perm {
n;
// length
p;
// The permutation itself
constructor(a) {
this.n = a.length;
this.p = a;
}
toString() {
return `Perm[${this.p.join(" ")}]`;
}
mul(p2) {
const c = Array(this.n);
for (let i = 0; i < this.n; i++) {
c[i] = p2.p[this.p[i]];
}
return new _Perm(c);
}
rmul(p2) {
const c = Array(this.n);
for (let i = 0; i < this.n; i++) {
c[i] = this.p[p2.p[i]];
}
return new _Perm(c);
}
inv() {
const c = Array(this.n);
for (let i = 0; i < this.n; i++) {
c[this.p[i]] = i;
}
return new _Perm(c);
}
compareTo(p2) {
for (let i = 0; i < this.n; i++) {
if (this.p[i] !== p2.p[i]) {
return this.p[i] - p2.p[i];
}
}
return 0;
}
toGap() {
const cyc = [];
const seen = new Array(this.n);
for (let i = 0; i < this.p.length; i++) {
if (seen[i] || this.p[i] === i) {
continue;
}
const incyc = [];
for (let j = this.p[i]; !seen[j]; j = this.p[j]) {
incyc.push(1 + j);
seen[j] = true;
}
cyc.push(`(${incyc.reverse().join(",")})`);
}
return cyc.join("");
}
toMathematica() {
const cyc = [];
const seen = new Array(this.n);
for (let i = 0; i < this.p.length; i++) {
if (seen[i] || this.p[i] === i) {
continue;
}
const incyc = [];
for (let j = this.p[i]; !seen[j]; j = this.p[j]) {
incyc.push(1 + j);
seen[j] = true;
}
cyc.push(`{${incyc.reverse().join(",")}}`);
}
return `Cycles[{${cyc.join(",")}}]`;
}
order() {
let r = 1;
const seen = new Array(this.n);
for (let i = 0; i < this.p.length; i++) {
if (seen[i] || this.p[i] === i) {
continue;
}
let cs = 0;
for (let j = i; !seen[j]; j = this.p[j]) {
cs++;
seen[j] = true;
}
r = lcm(r, cs);
}
return r;
}
};
// src/cubing/puzzle-geometry/PermOriSet.ts
var PGOrbitDef = class {
constructor(size, mod) {
this.size = size;
this.mod = mod;
}
reassemblySize() {
return factorial(this.size) * BigInt(this.mod) ** BigInt(this.size);
}
};
var lastGlobalDefinitionCounter = 0;
function externalName(mapper, moveString) {
const mv = Move.fromString(moveString);
const mv2 = mapper.notationToExternal(mv);
if (mv2 === null || mv === mv2) {
return moveString;
}
return mv2.toString();
}
var PGOrbitsDef = class _PGOrbitsDef {
constructor(orbitnames, orbitdefs, solved, movenames, moveops, isRotation, forcenames) {
this.orbitnames = orbitnames;
this.orbitdefs = orbitdefs;
this.solved = solved;
this.movenames = movenames;
this.moveops = moveops;
this.isRotation = isRotation;
this.forcenames = forcenames;
}
toKTransformationData(t) {
const ktransformationData = {};
for (let i = 0; i < this.orbitnames.length; i++) {
ktransformationData[this.orbitnames[i]] = t.orbits[i].toKTransformationOrbitData();
}
return ktransformationData;
}
toKPatternData(t) {
const kpatternData = {};
for (let i = 0; i < this.orbitnames.length; i++) {
kpatternData[this.orbitnames[i]] = t.orbits[i].toKPatternOrbitData();
}
return kpatternData;
}
// TODO: remove this
static transformToKTransformationData(orbitnames, t) {
const mp = {};
for (let j = 0; j < orbitnames.length; j++) {
mp[orbitnames[j]] = t.orbits[j].toKTransformationOrbitData();
}
return mp;
}
describeSet(s, r, mapper) {
const n = this.orbitdefs[s].size;
const m = new Array(n);
for (let i = 0; i < n; i++) {
m[i] = [];
}
for (let i = 0; i < this.movenames.length; i++) {
if (this.isRotation[i]) {
continue;
}
let mvname = this.movenames[i];
if (!this.forcenames[i]) {
mvname = externalName(mapper, mvname);
if (mvname[mvname.length - 1] === "'") {
mvname = mvname.substring(0, mvname.length - 1);
}
}
const pd = this.moveops[i].orbits[s];
for (let j = 0; j < n; j++) {
if (pd.perm[j] !== j || pd.ori[j] !== 0) {
m[j].push(mvname);
}
}
}
for (let j = 0; j < n; j++) {
r.push(`# ${j + 1} ${m[j].join(" ")}`);
}
}
toKsolve(name, mapper = new NullMapper()) {
const result = [];
result.push(`Name ${name}`);
result.push("");
for (let i = 0; i < this.orbitnames.length; i++) {
result.push(
`Set ${this.orbitnames[i]} ${this.orbitdefs[i].size} ${this.orbitdefs[i].mod}`
);
this.describeSet(i, result, mapper);
}
result.push("");
result.push("Solved");
for (let i = 0; i < this.orbitnames.length; i++) {
this.solved.orbits[i].appendDefinition(
result,
this.orbitnames[i],
false,
false
);
}
result.push("End");
for (let i = 0; i < this.movenames.length; i++) {
result.push("");
let name2 = this.movenames[i];
if (!this.forcenames[i]) {
name2 = externalName(mapper, this.movenames[i]);
}
let doinv = false;
if (name2[name2.length - 1] === "'") {
doinv = true;
name2 = name2.substring(0, name2.length - 1);
}
result.push(`Move ${name2}`);
for (let j = 0; j < this.orbitnames.length; j++) {
if (doinv) {
this.moveops[i].orbits[j].inv().appendDefinition(result, this.orbitnames[j], true);
} else {
this.moveops[i].orbits[j].appendDefinition(
result,
this.orbitnames[j],
true
);
}
}
result.push("End");
}
return result;
}
// TODO: return type.
toKPuzzleDefinition(includemoves) {
const orbits = [];
const defaultPatternData = {};
for (let i = 0; i < this.orbitnames.length; i++) {
orbits.push({
orbitName: this.orbitnames[i],
numPieces: this.orbitdefs[i].size,
numOrientations: this.orbitdefs[i].mod
});
const defaultPatternFrom = this.solved.orbits[i].toKTransformationOrbitData();
defaultPatternData[this.orbitnames[i]] = {
pieces: defaultPatternFrom.permutation,
orientation: defaultPatternFrom.orientationDelta
};
}
const moves = {};
if (includemoves) {
for (let i = 0; i < this.movenames.length; i++) {
moves[this.movenames[i]] = this.toKTransformationData(this.moveops[i]);
}
}
return {
name: `PG3D #${++lastGlobalDefinitionCounter}`,
orbits,
defaultPattern: defaultPatternData,
moves
};
}
optimize() {
const neworbitnames = [];
const neworbitdefs = [];
const newsolved = [];
const newmoveops = [];
for (let j = 0; j < this.moveops.length; j++) {
newmoveops.push([]);
}
for (let i = 0; i < this.orbitdefs.length; i++) {
const om = this.orbitdefs[i].mod;
const n = this.orbitdefs[i].size;
const du = new DisjointUnion(n);
const changed = new Array(this.orbitdefs[i].size);
for (let k = 0; k < n; k++) {
changed[k] = false;
}
for (let j = 0; j < this.moveops.length; j++) {
for (let k = 0; k < n; k++) {
if (this.moveops[j].orbits[i].perm[k] !== k || this.moveops[j].orbits[i].ori[k] !== 0) {
if (!this.isRotation[j]) {
changed[k] = true;
}
du.union(k, this.moveops[j].orbits[i].perm[k]);
}
}
}
let keepori = true;
if (om > 1) {
keepori = false;
const duo = new DisjointUnion(this.orbitdefs[i].size * om);
for (let j = 0; j < this.moveops.length; j++) {
for (let k = 0; k < n; k++) {
if (this.moveops[j].orbits[i].perm[k] !== k || this.moveops[j].orbits[i].ori[k] !== 0) {
for (let o = 0; o < om; o++) {
duo.union(
k * om + o,
this.moveops[j].orbits[i].perm[k] * om + (o + this.moveops[j].orbits[i].ori[k]) % om
);
}
}
}
}
for (let j = 0; !keepori && j < n; j++) {
for (let o = 1; o < om; o++) {
if (duo.find(j * om) === duo.find(j * om + o)) {
keepori = true;
}
}
}
for (let j = 0; !keepori && j < n; j++) {
for (let k = 0; k < j; k++) {
if (this.solved.orbits[i].perm[j] === this.solved.orbits[i].perm[k]) {
keepori = true;
}
}
}
}
let nontriv = -1;
let multiple = false;
for (let j = 0; j < this.orbitdefs[i].size; j++) {
if (changed[j]) {
const h = du.find(j);
if (nontriv < 0) {
nontriv = h;
} else if (nontriv !== h) {
multiple = true;
}
}
}
for (let j = 0; j < this.orbitdefs[i].size; j++) {
if (!changed[j]) {
continue;
}
const h = du.find(j);
if (h !== j) {
continue;
}
const no = [];
const on = [];
let nv = 0;
for (let k = 0; k < this.orbitdefs[i].size; k++) {
if (du.find(k) === j) {
no[nv] = k;
on[k] = nv;
nv++;
}
}
if (multiple) {
neworbitnames.push(`${this.orbitnames[i]}_p${j}`);
} else {
neworbitnames.push(this.orbitnames[i]);
}
if (keepori) {
neworbitdefs.push(new PGOrbitDef(nv, this.orbitdefs[i].mod));
newsolved.push(this.solved.orbits[i].remapVS(no, nv));
for (let k = 0; k < this.moveops.length; k++) {
newmoveops[k].push(this.moveops[k].orbits[i].remap(no, on, nv));
}
} else {
neworbitdefs.push(new PGOrbitDef(nv, 1));
newsolved.push(this.solved.orbits[i].remapVS(no, nv).killOri());
for (let k = 0; k < this.moveops.length; k++) {
newmoveops[k].push(
this.moveops[k].orbits[i].remap(no, on, nv).killOri()
);
}
}
}
}
return new _PGOrbitsDef(
neworbitnames,
neworbitdefs,
new VisibleState(newsolved),
this.movenames,
newmoveops.map((_) => new PGTransform(_)),
this.isRotation,
this.forcenames
);
}
// replace the solved state with a new scrambled state.
scramble(n) {
this.solved = this.solved.mul(this.getScrambleTransformation(n));
}
// generate a new "random" position based on an entropy pool
// this should be significantly faster and more random than just
// doing a large number of random moves, especially on big puzzles.
getScrambleTransformation(n) {
if (n < 100) {
n = 100;
}
const pool = [];
for (let i = 0; i < this.moveops.length; i++) {
pool[i] = this.moveops[i];
}
for (let i = 0; i < pool.length; i++) {
const j = Math.floor(Math.random() * pool.length);
const t = pool[i];
pool[i] = pool[j];
pool[j] = t;
}
if (n < pool.length) {
n = pool.length;
}
for (let i = 0; i < n; i++) {
const ri = Math.floor(Math.random() * pool.length);
const rj = Math.floor(Math.random() * pool.length);
const rm = Math.floor(Math.random() * this.moveops.length);
pool[ri] = pool[ri].mul(pool[rj]).mul(this.moveops[rm]);
if (Math.random() < 0.1) {
pool[ri] = pool[ri].mul(this.moveops[rm]);
}
}
let s = pool[0];
for (let i = 1; i < pool.length; i++) {
s = s.mul(pool[i]);
}
return s;
}
reassemblySize() {
let n = BigInt(1);
for (let i = 0; i < this.orbitdefs.length; i++) {
n *= this.orbitdefs[i].reassemblySize();
}
return n;
}
};
var PGOrbit = class _PGOrbit {
constructor(perm, ori, orimod) {
this.perm = perm;
this.ori = ori;
this.orimod = orimod;
}
static ktransformationCache = [];
static e(n, mod) {
return new _PGOrbit(iota(n), zeros(n), mod);
}
mul(b) {
const n = this.perm.length;
const newPerm = new Array(n);
if (this.orimod === 1) {
for (let i = 0; i < n; i++) {
newPerm[i] = this.perm[b.perm[i]];
}
return new _PGOrbit(newPerm, this.ori, this.orimod);
} else {
const newOri = new Array(n);
for (let i = 0; i < n; i++) {
newPerm[i] = this.perm[b.perm[i]];
newOri[i] = (this.ori[b.perm[i]] + b.ori[i]) % this.orimod;
}
return new _PGOrbit(newPerm, newOri, this.orimod);
}
}
inv() {
const n = this.perm.length;
const newPerm = new Array(n);
const newOri = new Array(n);
for (let i = 0; i < n; i++) {
newPerm[this.perm[i]] = i;
newOri[this.perm[i]] = (this.orimod - this.ori[i]) % this.orimod;
}
return new _PGOrbit(newPerm, newOri, this.orimod);
}
equal(b) {
const n = this.perm.length;
for (let i = 0; i < n; i++) {
if (this.perm[i] !== b.perm[i] || this.ori[i] !== b.ori[i]) {
return false;
}
}
return true;
}
// in-place mutator
killOri() {
const n = this.perm.length;
for (let i = 0; i < n; i++) {
this.ori[i] = 0;
}
this.orimod = 1;
return this;
}
toPerm() {
const o = this.orimod;
if (o === 1) {
return new Perm(this.perm);
}
const n = this.perm.length;
const newPerm = new Array(n * o);
for (let i = 0; i < n; i++) {
for (let j = 0; j < o; j++) {
newPerm[i * o + j] = o * this.perm[i] + (this.ori[i] + j) % o;
}
}
return new Perm(newPerm);
}
// returns tuple of sets of identical pieces in this orbit
identicalPieces() {
const done = [];
const n = this.perm.length;
const r = [];
for (let i = 0; i < n; i++) {
const v = this.perm[i];
if (done[v] === void 0) {
const s = [i];
done[v] = true;
for (let j = i + 1; j < n; j++) {
if (this.perm[j] === v) {
s.push(j);
}
}
r.push(s);
}
}
return r;
}
order() {
return this.toPerm().order();
}
isIdentity() {
const n = this.perm.length;
if (this.perm === iota(n) && this.ori === zeros(n)) {
return true;
}
for (let i = 0; i < n; i++) {
if (this.perm[i] !== i || this.ori[i] !== 0) {
return false;
}
}
return true;
}
zeroOris() {
const n = this.perm.length;
if (this.ori === zeros(n)) {
return true;
}
for (let i = 0; i < n; i++) {
if (this.ori[i] !== 0) {
return false;
}
}
return true;
}
remap(no, on, nv) {
const newPerm = new Array(nv);
const newOri = new Array(nv);
for (let i = 0; i < nv; i++) {
newPerm[i] = on[this.perm[no[i]]];
newOri[i] = this.ori[no[i]];
}
return new _PGOrbit(newPerm, newOri, this.orimod);
}
remapVS(no, nv) {
const newPerm = new Array(nv);
const newOri = new Array(nv);
let nextNew = 0;
const reassign = [];
for (let i = 0; i < nv; i++) {
const ov = this.perm[no[i]];
if (reassign[ov] === void 0) {
reassign[ov] = nextNew++;
}
newPerm[i] = reassign[ov];
newOri[i] = this.ori[no[i]];
}
return new _PGOrbit(newPerm, newOri, this.orimod);
}
appendDefinition(result, name, useVS, concise = true) {
if (concise && this.isIdentity()) {
return;
}
result.push(name);
result.push(this.perm.map((_) => _ + 1).join(" "));
if (!this.zeroOris()) {
if (useVS) {
const newori = new Array(this.ori.length);
for (let i = 0; i < newori.length; i++) {
newori[this.perm[i]] = this.ori[i];
}
result.push(newori.join(" "));
} else {
result.push(this.ori.join(" "));
}
}
}
toKTransformationOrbitData() {
const n = this.perm.length;
if (this.isIdentity()) {
if (!_PGOrbit.ktransformationCache[n]) {
_PGOrbit.ktransformationCache[n] = {
permutation: iota(n),
orientationDelta: zeros(n)
};
}
return _PGOrbit.ktransformationCache[n];
} else {
return { permutation: this.perm, orientationDelta: this.ori };
}
}
toKPatternOrbitData() {
const n = this.perm.length;
return {
pieces: this.perm,
orientation: this.ori,
orientationMod: zeros(n)
};
}
};
var PGTransformBase = class {
constructor(orbits) {
this.orbits = orbits;
}
internalMul(b) {
const newOrbits = [];
for (let i = 0; i < this.orbits.length; i++) {
newOrbits.push(this.orbits[i].mul(b.orbits[i]));
}
return newOrbits;
}
internalInv() {
const newOrbits = [];
for (const orbit of this.orbits) {
newOrbits.push(orbit.inv());
}
return newOrbits;
}
equal(b) {
for (let i = 0; i < this.orbits.length; i++) {
if (!this.orbits[i].equal(b.orbits[i])) {
return false;
}
}
return true;
}
killOri() {
for (const orbit of this.orbits) {
orbit.killOri();
}
return this;
}
toPerm() {
const perms = [];
let n = 0;
for (const orbit of this.orbits) {
const p = orbit.toPerm();
perms.push(p);
n += p.n;
}
const newPerm = new Array(n);
n = 0;
for (const p of perms) {
for (let j = 0; j < p.n; j++) {
newPerm[n + j] = n + p.p[j];
}
n += p.n;
}
return new Perm(newPerm);
}
identicalPieces() {
const r = [];
let n = 0;
for (const orbit of this.orbits) {
const o = orbit.orimod;
const s = orbit.identicalPieces();
for (let j = 0; j < s.length; j++) {
r.push(s[j].map((_) => _ * o + n));
}
n += o * orbit.perm.length;
}
return r;
}
order() {
let r = 1;
for (const orbit of this.orbits) {
r = lcm(r, orbit.order());
}
return r;
}
};
var PGTransform = class _PGTransform extends PGTransformBase {
mul(b) {
return new _PGTransform(this.internalMul(b));
}
mulScalar(n) {
if (n === 0) {
return this.e();
}
let t = this;
if (n < 0) {
t = t.inv();
n = -n;
}
while ((n & 1) === 0) {
t = t.mul(t);
n >>= 1;
}
if (n === 1) {
return t;
}
let s = t;
let r = this.e();
while (n > 0) {
if (n & 1) {
r = r.mul(s);
}
if (n > 1) {
s = s.mul(s);
}
n >>= 1;
}
return r;
}
inv() {
return new _PGTransform(this.internalInv());
}
e() {
return new _PGTransform(
this.orbits.map((_) => PGOrbit.e(_.perm.length, _.orimod))
);
}
};
var VisibleState = class _VisibleState extends PGTransformBase {
mul(b) {
return new _VisibleState(this.internalMul(b));
}
};
var DisjointUnion = class {
constructor(n) {
this.n = n;
this.heads = new Array(n);
for (let i = 0; i < n; i++) {
this.heads[i] = i;
}
}
heads;
find(v) {
let h = this.heads[v];
if (this.heads[h] === h) {
return h;
}
h = this.find(this.heads[h]);
this.heads[v] = h;
return h;
}
union(a, b) {
const ah = this.find(a);
const bh = this.find(b);
if (ah < bh) {
this.heads[bh] = ah;
} else if (ah > bh) {
this.heads[ah] = bh;
}
}
};
function showcanon(g, disp) {
const n = g.moveops.length;
if (n > 30) {
throw new Error("Canon info too big for bitmask");
}
const orders = [];
const commutes = [];
for (let i = 0; i < n; i++) {
const permA = g.moveops[i];
orders.push(permA.order());
let bits = 0;
for (let j = 0; j < n; j++) {
if (j === i) {
continue;
}
const permB = g.moveops[j];
if (permA.mul(permB).equal(permB.mul(permA))) {
bits |= 1 << j;
}
}
commutes.push(bits);
}
let curlev = {};
curlev[0] = 1;
for (let d = 0; d < 100; d++) {
let sum = 0;
const nextlev = {};
let uniq = 0;
for (const sti in curlev) {
const st = +sti;
const cnt = curlev[st];
sum += cnt;
uniq++;
for (let mv = 0; mv < orders.length; mv++) {
if ((st >> mv & 1) === 0 && (st & commutes[mv] & (1 << mv) - 1) === 0) {
const nst = st & commutes[mv] | 1 << mv;
if (nextlev[nst] === void 0) {
nextlev[nst] = 0;
}
nextlev[nst] += (orders[mv] - 1) * cnt;
}
}
}
disp(`${d}: canonseq ${sum} states ${uniq}`);
curlev = nextlev;
}
}
// src/cubing/puzzle-geometry/PGPuzzles.ts
var PGPuzzles = {
"2x2x2": "c f 0",
"3x3x3": "c f 0.333333333333333",
"4x4x4": "c f 0.5 f 0",
"5x5x5": "c f 0.6 f 0.2",
"6x6x6": "c f 0.666666666666667 f 0.333333333333333 f 0",
"7x7x7": "c f 0.714285714285714 f 0.428571428571429 f 0.142857142857143",
"8x8x8": "c f 0.75 f 0.5 f 0.25 f 0",
"9x9x9": "c f 0.777777777777778 f 0.555555555555556 f 0.333333333333333 f 0.111111111111111",
"10x10x10": "c f 0.8 f 0.6 f 0.4 f 0.2 f 0",
"11x11x11": "c f 0.818181818181818 f 0.636363636363636 f 0.454545454545455 f 0.272727272727273 f 0.0909090909090909",
"12x12x12": "c f 0.833333333333333 f 0.666666666666667 f 0.5 f 0.333333333333333 f 0.166666666666667 f 0",
"13x13x13": "c f 0.846153846153846 f 0.692307692307692 f 0.538461538461538 f 0.384615384615385 f 0.230769230769231 f 0.0769230769230769",
"20x20x20": "c f 0 f .1 f .2 f .3 f .4 f .5 f .6 f .7 f .8 f .9",
"30x30x30": "c f 0 f .066667 f .133333 f .2 f .266667 f .333333 f .4 f .466667 f .533333 f .6 f .666667 f .733333 f .8 f .866667 f .933333",
"40x40x40": "c f 0 f .05 f .1 f .15 f .2 f .25 f .3 f .35 f .4 f .45 f .5 f .55 f .6 f .65 f .7 f .75 f .8 f .85 f .9 f .95",
skewb: "c v 0",
"master skewb": "c v 0.275",
"professor skewb": "c v 0 v 0.38",
"compy cube": "c v 0.915641442663986",
helicopter: "c e 0.707106781186547",
"curvy copter": "c e 0.83",
dino: "c v 0.577350269189626",
"little chop": "c e 0",
pyramorphix: "t e 0",
mastermorphix: "t e 0.346184634065199",
pyraminx: "t v 0.333333333333333 v 1.66666666666667",
tetraminx: "t v 0.333333333333333",
"master pyraminx": "t v 0 v 1 v 2",
"master tetraminx": "t v 0 v 1",
"professor pyraminx": "t v -0.2 v 0.6 v 1.4 v 2.2",
"professor tetraminx": "t v -0.2 v 0.6 v 1.4",
"royal pyraminx": "t v -0.333333333333333 v 0.333333333333333 v 1 v 1.66666666666667 v 2.33333333333333",
"royal tetraminx": "t v -0.333333333333333 v 0.333333333333333 v 1 v 1.66666666666667",
"emperor pyraminx": "t v -0.428571428571429 v 0.142857142857143 v 0.714285714285714 v 1.28571428571429 v 1.85714285714286 v 2.42857142857143",
"emperor tetraminx": "t v -0.428571428571429 v 0.142857142857143 v 0.714285714285714 v 1.28571428571429 v 1.85714285714286",
"Jing pyraminx": "t f 0",
"master pyramorphix": "t e 0.866025403784437",
megaminx: "d f 0.7",
gigaminx: "d f 0.64 f 0.82",
teraminx: "d f 0.64 f 0.76 f 0.88",
petaminx: "d f 0.64 f 0.73 f 0.82 f 0.91",
examinx: "d f 0.64 f 0.712 f 0.784 f 0.856 f 0.928",
zetaminx: "d f 0.64 f 0.7 f 0.76 f 0.82 f 0.88 f 0.94",
yottaminx: "d f 0.64 f 0.6914 f 0.7429 f 0.7943 f 0.8457 f 0.8971 f 0.9486",
pentultimate: "d f 0",
"master pentultimate": "d f 0.1",
"elite pentultimate": "d f 0 f 0.145905",
// exact value for starminx is sqrt(5(5-2 sqrt(5))/3)
starminx: "d v 0.937962370425399",
"starminx 2": "d f 0.23606797749979",
"pyraminx crystal": "d f 0.447213595499989",
chopasaurus: "d v 0",
"big chop": "d e 0",
"skewb diamond": "o f 0",
FTO: "o f 0.333333333333333",
"master FTO": "o f 0.5 f 0",
"Christopher's jewel": "o v 0.577350269189626",
octastar: "o e 0",
"Trajber's octahedron": "o v 0.433012701892219",
"radio chop": "i f 0",
icosamate: "i v 0",
"Regular Astrominx": "i v 0.18759247376021",
"Regular Astrominx + Big Chop": "i v 0.18759247376021 e 0",
Redicosahedron: "i v 0.794654472291766",
"Redicosahedron with centers": "i v 0.84",
Icosaminx: "i v 0.73",
"Eitan's star": "i f 0.61803398874989",
"2x2x2 + dino": "c f 0 v 0.577350269189626",
"2x2x2 + little chop": "c f 0 e 0",
"dino + little chop": "c v 0.577350269189626 e 0",
"2x2x2 + dino + little chop": "c f 0 v 0.577350269189626 e 0",
"megaminx + chopasaurus": "d f 0.61803398875 v 0",
"starminx combo": "d f 0.23606797749979 v 0.937962370425399"
};
// src/cubing/puzzle-geometry/Quat.ts
var eps = 1e-9;
function centermassface(face) {
let s = new Quat(0, 0, 0, 0);
for (let i = 0; i < face.length; i++) {
s = s.sum(face[i]);
}
return s.smul(1 / face.length);
}
function solvethreeplanes(p1, p2, p3, planes) {
const p = planes[p1].intersect3(planes[p2], planes[p3]);
if (!p) {
return p;
}
for (let i = 0; i < planes.length; i++) {
if (i !== p1 && i !== p2 && i !== p3) {
const dt = planes[i].b * p.b + planes[i].c * p.c + planes[i].d * p.d;
if (planes[i].a > 0 && dt > planes[i].a || planes[i].a < 0 && dt < planes[i].a) {
return false;
}
}
}
return p;
}
var Quat = class _Quat {
constructor(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
mul(q) {
return new _Quat(
this.a * q.a - this.b * q.b - this.c * q.c - this.d * q.d,
this.a * q.b + this.b * q.a + this.c * q.d - this.d * q.c,
this.a * q.c - this.b * q.d + this.c * q.a + this.d * q.b,
this.a * q.d + this.b * q.c - this.c * q.b + this.d * q.a
);
}
toString() {
return `Q[${this.a},${this.b},${this.c},${this.d}]`;
}
dist(q) {
return Math.hypot(this.a - q.a, this.b - q.b, this.c - q.c, this.d - q.d);
}
len() {
return Math.hypot(this.a, this.b, this.c, this.d);
}
cross(q) {
return new _Quat(
0,
this.c * q.d - this.d * q.c,
this.d * q.b - this.b * q.d,
this.b * q.c - this.c * q.b
);
}
dot(q) {
return this.b * q.b + this.c * q.c + this.d * q.d;
}
normalize() {
const d = Math.sqrt(this.dot(this));
return new _Quat(this.a / d, this.b / d, this.c / d, this.d / d);
}
makenormal() {
return new _Quat(0, this.b, this.c, this.d).normalize();
}
normalizeplane() {
const d = Math.hypot(this.b, this.c, this.d);
return new _Quat(this.a / d, this.b / d, this.c / d, this.d / d);
}
smul(m) {
return new _Quat(this.a * m, this.b * m, this.c * m, this.d * m);
}
sum(q) {
return new _Quat(this.a + q.a, this.b + q.b, this.c + q.c, this.d + q.d);
}
sub(q) {
return new _Quat(this.a - q.a, this.b - q.b, this.c - q.c, this.d - q.d);
}
angle() {
return 2 * Math.acos(this.a);
}
invrot() {
return new _Quat(this.a, -this.b, -this.c, -this.d);
}
det3x3(a00, a01, a02, a10, a11, a12, a20, a21, a22) {
return a00 * (a11 * a22 - a12 * a21) + a01 * (a12 * a20 - a10 * a22) + a02 * (a10 * a21 - a11 * a20);
}
rotateplane(q) {
const t = q.mul(new _Quat(0, this.b, this.c, this.d)).mul(q.invrot());
t.a = this.a;
return t;
}
// return any vector orthogonal to the given one. Find the smallest
// component (in absolute value) and return the cross product of that
// axis with the given vector.
orthogonal() {
const ab = Math.abs(this.b);
const ac = Math.abs(this.c);
const ad = Math.abs(this.d);
if (ab < ac && ab < ad) {
return this.cross(new _Quat(0, 1, 0, 0)).normalize();
} else if (ac < ab && ac < ad) {
return this.cross(new _Quat(0, 0, 1, 0)).normalize();
} else {
return this.cross(new _Quat(0, 0, 0, 1)).normalize();
}
}
// return the Quaternion that will rotate the this vector
// to the b vector through rotatepoint.
pointrotation(b) {
const a = this.normalize();
b = b.normalize();
if (a.sub(b).len() < eps) {
return new _Quat(1, 0, 0, 0);
}
let h = a.sum(b);
if (h.len() < eps) {
h = h.orthogonal();
} else {
h = h.normalize();
}
const r = a.cross(h);
r.a = a.dot(h);
return r;
}
// given two vectors, return the portion of the first that
// is not in the direction of the second.
unproject(b) {
return this.sum(b.smul(-this.dot(b) / (this.len() * b.len())));
}
rotatepoint(q) {
return q.mul(this).mul(q.invrot());
}
rotateface(face) {
return face.map((_) => _.rotatepoint(this));
}
intersect3(p2, p3) {
const det = this.det3x3(
this.b,
this.c,
this.d,
p2.b,
p2.c,
p2.d,
p3.b,
p3.c,
p3.d
);
if (Math.abs(det) < eps) {
return false;
}
return new _Quat(
0,
this.det3x3(this.a, this.c, this.d, p2.a, p2.c, p2.d, p3.a, p3.c, p3.d) / det,
this.det3x3(this.b, this.a, this.d, p2.b, p2.a, p2.d, p3.b, p3.a, p3.d) / det,
this.det3x3(this.b, this.c, this.a, p2.b, p2.c, p2.a, p3.b, p3.c, p3.a) / det
);
}
side(x) {
if (x > eps) {
return 1;
}
if (x < -eps) {
return -1;
}
return 0;
}
/**
* Cuts a face by this plane, or returns null if there
* is no intersection.
* @param face The face to cut.
*/
cutface(face) {
const d = this.a;
let seen = 0;
let r = null;
for (let i = 0; i < face.length; i++) {
seen |= 1 << this.side(face[i].dot(this) - d) + 1;
}
if ((seen & 5) === 5) {
r = [];
const inout = face.map((_) => this.side(_.dot(this) - d));
for (let s = -1; s <= 1; s += 2) {
const nface = [];
for (let k = 0; k < face.length; k++) {
if (inout[k] === s || inout[k] === 0) {
nface.push(face[k]);
}
const kk = (k + 1) % face.length;
if (inout[k] + inout[kk] === 0 && inout[k] !== 0) {
const vk = face[k].dot(this) - d;
const vkk = face[kk].dot(this) - d;
const r2 = vk / (vk - vkk);
const pt = face[k].smul(1 - r2).sum(face[kk].smul(r2));
nface.push(pt);