331 lines
8.8 KiB
JavaScript
331 lines
8.8 KiB
JavaScript
|
|
(function () {
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
function createCubeMathHelpers(dependencies) {
|
||
|
|
const {
|
||
|
|
state,
|
||
|
|
CUBE_VERTICES,
|
||
|
|
FACE_GEOMETRY,
|
||
|
|
EDGE_GEOMETRY,
|
||
|
|
EDGE_GEOMETRY_KEYS,
|
||
|
|
CUBE_VIEW_CENTER,
|
||
|
|
WALL_FRONT_ROTATIONS,
|
||
|
|
LOCAL_DIRECTION_VIEW_MAP,
|
||
|
|
normalizeId,
|
||
|
|
normalizeLetterKey,
|
||
|
|
normalizeEdgeId,
|
||
|
|
formatDirectionName,
|
||
|
|
getEdgesForWall,
|
||
|
|
getEdgePathEntry,
|
||
|
|
getEdgeLetterId,
|
||
|
|
getCubeCenterData
|
||
|
|
} = dependencies || {};
|
||
|
|
|
||
|
|
function normalizeAngle(angle) {
|
||
|
|
let next = angle;
|
||
|
|
while (next > 180) {
|
||
|
|
next -= 360;
|
||
|
|
}
|
||
|
|
while (next <= -180) {
|
||
|
|
next += 360;
|
||
|
|
}
|
||
|
|
return next;
|
||
|
|
}
|
||
|
|
|
||
|
|
function setRotation(nextX, nextY) {
|
||
|
|
state.rotationX = normalizeAngle(nextX);
|
||
|
|
state.rotationY = normalizeAngle(nextY);
|
||
|
|
}
|
||
|
|
|
||
|
|
function snapRotationToWall(wallId) {
|
||
|
|
const target = WALL_FRONT_ROTATIONS[normalizeId(wallId)];
|
||
|
|
if (!target) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setRotation(target.x, target.y);
|
||
|
|
}
|
||
|
|
|
||
|
|
function facePoint(quad, u, v) {
|
||
|
|
const weight0 = ((1 - u) * (1 - v)) / 4;
|
||
|
|
const weight1 = ((1 + u) * (1 - v)) / 4;
|
||
|
|
const weight2 = ((1 + u) * (1 + v)) / 4;
|
||
|
|
const weight3 = ((1 - u) * (1 + v)) / 4;
|
||
|
|
|
||
|
|
return {
|
||
|
|
x: quad[0].x * weight0 + quad[1].x * weight1 + quad[2].x * weight2 + quad[3].x * weight3,
|
||
|
|
y: quad[0].y * weight0 + quad[1].y * weight1 + quad[2].y * weight2 + quad[3].y * weight3
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function projectVerticesForRotation(rotationX, rotationY) {
|
||
|
|
const yaw = (rotationY * Math.PI) / 180;
|
||
|
|
const pitch = (rotationX * Math.PI) / 180;
|
||
|
|
|
||
|
|
const cosY = Math.cos(yaw);
|
||
|
|
const sinY = Math.sin(yaw);
|
||
|
|
const cosX = Math.cos(pitch);
|
||
|
|
const sinX = Math.sin(pitch);
|
||
|
|
|
||
|
|
const centerX = CUBE_VIEW_CENTER.x;
|
||
|
|
const centerY = CUBE_VIEW_CENTER.y;
|
||
|
|
const scale = 54;
|
||
|
|
const camera = 4.6;
|
||
|
|
|
||
|
|
return CUBE_VERTICES.map(([x, y, z]) => {
|
||
|
|
const x1 = x * cosY + z * sinY;
|
||
|
|
const z1 = -x * sinY + z * cosY;
|
||
|
|
|
||
|
|
const y2 = y * cosX - z1 * sinX;
|
||
|
|
const z2 = y * sinX + z1 * cosX;
|
||
|
|
|
||
|
|
const perspective = camera / (camera - z2);
|
||
|
|
|
||
|
|
return {
|
||
|
|
x: centerX + x1 * scale * perspective,
|
||
|
|
y: centerY + y2 * scale * perspective,
|
||
|
|
z: z2
|
||
|
|
};
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function projectVertices() {
|
||
|
|
return projectVerticesForRotation(state.rotationX, state.rotationY);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getEdgeGeometryById(edgeId) {
|
||
|
|
const canonicalId = normalizeEdgeId(edgeId);
|
||
|
|
const geometryIndex = EDGE_GEOMETRY_KEYS.indexOf(canonicalId);
|
||
|
|
if (geometryIndex < 0) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return EDGE_GEOMETRY[geometryIndex] || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getWallEdgeDirections(wallOrWallId) {
|
||
|
|
const wallId = normalizeId(typeof wallOrWallId === "string" ? wallOrWallId : wallOrWallId?.id);
|
||
|
|
const faceIndices = FACE_GEOMETRY[wallId];
|
||
|
|
if (!Array.isArray(faceIndices) || faceIndices.length !== 4) {
|
||
|
|
return new Map();
|
||
|
|
}
|
||
|
|
|
||
|
|
const frontRotation = WALL_FRONT_ROTATIONS[wallId] || {
|
||
|
|
x: state.rotationX,
|
||
|
|
y: state.rotationY
|
||
|
|
};
|
||
|
|
const projectedVertices = projectVerticesForRotation(frontRotation.x, frontRotation.y);
|
||
|
|
const quad = faceIndices.map((index) => projectedVertices[index]);
|
||
|
|
const center = facePoint(quad, 0, 0);
|
||
|
|
const directionsByEdgeId = new Map();
|
||
|
|
|
||
|
|
getEdgesForWall(wallId).forEach((edge) => {
|
||
|
|
const geometry = getEdgeGeometryById(edge?.id);
|
||
|
|
if (!geometry) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const [fromIndex, toIndex] = geometry;
|
||
|
|
const from = projectedVertices[fromIndex];
|
||
|
|
const to = projectedVertices[toIndex];
|
||
|
|
if (!from || !to) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const midpointX = (from.x + to.x) / 2;
|
||
|
|
const midpointY = (from.y + to.y) / 2;
|
||
|
|
const dx = midpointX - center.x;
|
||
|
|
const dy = midpointY - center.y;
|
||
|
|
|
||
|
|
const directionByPosition = Math.abs(dx) >= Math.abs(dy)
|
||
|
|
? (dx >= 0 ? "east" : "west")
|
||
|
|
: (dy >= 0 ? "south" : "north");
|
||
|
|
const direction = LOCAL_DIRECTION_VIEW_MAP[directionByPosition] || directionByPosition;
|
||
|
|
|
||
|
|
directionsByEdgeId.set(normalizeEdgeId(edge?.id), direction);
|
||
|
|
});
|
||
|
|
|
||
|
|
return directionsByEdgeId;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getEdgeDirectionForWall(wallId, edgeId) {
|
||
|
|
const wallKey = normalizeId(wallId);
|
||
|
|
const edgeKey = normalizeEdgeId(edgeId);
|
||
|
|
if (!wallKey || !edgeKey) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
const directions = getWallEdgeDirections(wallKey);
|
||
|
|
return directions.get(edgeKey) || "";
|
||
|
|
}
|
||
|
|
|
||
|
|
function getEdgeDirectionLabelForWall(wallId, edgeId) {
|
||
|
|
return formatDirectionName(getEdgeDirectionForWall(wallId, edgeId));
|
||
|
|
}
|
||
|
|
|
||
|
|
function getHebrewLetterSymbol(hebrewLetterId) {
|
||
|
|
const id = normalizeLetterKey(hebrewLetterId);
|
||
|
|
if (!id || !state.hebrewLetters) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
const entry = state.hebrewLetters[id];
|
||
|
|
if (!entry || typeof entry !== "object") {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
const symbol = String(
|
||
|
|
entry?.letter?.he || entry?.he || entry?.glyph || entry?.symbol || ""
|
||
|
|
).trim();
|
||
|
|
|
||
|
|
return symbol;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getHebrewLetterName(hebrewLetterId) {
|
||
|
|
const id = normalizeLetterKey(hebrewLetterId);
|
||
|
|
if (!id || !state.hebrewLetters) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
const entry = state.hebrewLetters[id];
|
||
|
|
if (!entry || typeof entry !== "object") {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
const name = String(entry?.letter?.name || entry?.name || "").trim();
|
||
|
|
return name;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAstrologySymbol(type, name) {
|
||
|
|
const normalizedType = normalizeId(type);
|
||
|
|
const normalizedName = normalizeId(name);
|
||
|
|
|
||
|
|
const planetSymbols = {
|
||
|
|
mercury: "☿︎",
|
||
|
|
venus: "♀︎",
|
||
|
|
mars: "♂︎",
|
||
|
|
jupiter: "♃︎",
|
||
|
|
saturn: "♄︎",
|
||
|
|
sol: "☉︎",
|
||
|
|
sun: "☉︎",
|
||
|
|
luna: "☾︎",
|
||
|
|
moon: "☾︎",
|
||
|
|
earth: "⊕",
|
||
|
|
uranus: "♅︎",
|
||
|
|
neptune: "♆︎",
|
||
|
|
pluto: "♇︎"
|
||
|
|
};
|
||
|
|
|
||
|
|
const zodiacSymbols = {
|
||
|
|
aries: "♈︎",
|
||
|
|
taurus: "♉︎",
|
||
|
|
gemini: "♊︎",
|
||
|
|
cancer: "♋︎",
|
||
|
|
leo: "♌︎",
|
||
|
|
virgo: "♍︎",
|
||
|
|
libra: "♎︎",
|
||
|
|
scorpio: "♏︎",
|
||
|
|
sagittarius: "♐︎",
|
||
|
|
capricorn: "♑︎",
|
||
|
|
aquarius: "♒︎",
|
||
|
|
pisces: "♓︎"
|
||
|
|
};
|
||
|
|
|
||
|
|
const elementSymbols = {
|
||
|
|
fire: "🜂",
|
||
|
|
water: "🜄",
|
||
|
|
air: "🜁",
|
||
|
|
earth: "🜃",
|
||
|
|
spirit: "🜀"
|
||
|
|
};
|
||
|
|
|
||
|
|
if (normalizedType === "planet") {
|
||
|
|
return planetSymbols[normalizedName] || "";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (normalizedType === "zodiac") {
|
||
|
|
return zodiacSymbols[normalizedName] || "";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (normalizedType === "element") {
|
||
|
|
return elementSymbols[normalizedName] || "";
|
||
|
|
}
|
||
|
|
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
function getCenterLetterId(center = null) {
|
||
|
|
const entry = center || getCubeCenterData();
|
||
|
|
return normalizeLetterKey(entry?.hebrewLetterId || entry?.associations?.hebrewLetterId || entry?.letter);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getCenterLetterSymbol(center = null) {
|
||
|
|
const centerLetterId = getCenterLetterId(center);
|
||
|
|
if (!centerLetterId) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
return getHebrewLetterSymbol(centerLetterId);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getEdgeAstrologySymbol(edge) {
|
||
|
|
const pathEntry = getEdgePathEntry(edge);
|
||
|
|
const astrology = pathEntry?.astrology || {};
|
||
|
|
return getAstrologySymbol(astrology.type, astrology.name);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getEdgeLetter(edge) {
|
||
|
|
const hebrewLetterId = getEdgeLetterId(edge);
|
||
|
|
if (!hebrewLetterId) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
return getHebrewLetterSymbol(hebrewLetterId);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getEdgeMarkerDisplay(edge) {
|
||
|
|
const letter = getEdgeLetter(edge);
|
||
|
|
const astro = getEdgeAstrologySymbol(edge);
|
||
|
|
|
||
|
|
if (state.markerDisplayMode === "letter") {
|
||
|
|
return letter
|
||
|
|
? { text: letter, isMissing: false }
|
||
|
|
: { text: "!", isMissing: true };
|
||
|
|
}
|
||
|
|
|
||
|
|
if (state.markerDisplayMode === "astro") {
|
||
|
|
return astro
|
||
|
|
? { text: astro, isMissing: false }
|
||
|
|
: { text: "!", isMissing: true };
|
||
|
|
}
|
||
|
|
|
||
|
|
if (letter && astro) {
|
||
|
|
return { text: `${letter} ${astro}`, isMissing: false };
|
||
|
|
}
|
||
|
|
|
||
|
|
return { text: "!", isMissing: true };
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
normalizeAngle,
|
||
|
|
setRotation,
|
||
|
|
snapRotationToWall,
|
||
|
|
facePoint,
|
||
|
|
projectVerticesForRotation,
|
||
|
|
projectVertices,
|
||
|
|
getEdgeGeometryById,
|
||
|
|
getWallEdgeDirections,
|
||
|
|
getEdgeDirectionForWall,
|
||
|
|
getEdgeDirectionLabelForWall,
|
||
|
|
getHebrewLetterSymbol,
|
||
|
|
getHebrewLetterName,
|
||
|
|
getAstrologySymbol,
|
||
|
|
getCenterLetterId,
|
||
|
|
getCenterLetterSymbol,
|
||
|
|
getEdgeAstrologySymbol,
|
||
|
|
getEdgeMarkerDisplay,
|
||
|
|
getEdgeLetter
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
window.CubeMathHelpers = {
|
||
|
|
createCubeMathHelpers
|
||
|
|
};
|
||
|
|
})();
|