Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | 38x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 13x 13x 13x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 117x 117x 78x 78x 78x 78x 39x 39x 39x 39x 13x 24x 72x 72x 216x 216x 144x 144x 72x 72x | import * as THREE from 'three';
type ColorMaterial = THREE.Material & { color?: THREE.Color };
const axes = [
// In the cube's right-handed local frame, +X/+Y/+Z point through R/U/F.
{
name: 'x',
direction: new THREE.Vector3(1, 0, 0),
labelOffset: new THREE.Vector3(0, 0.13, -0.1),
color: 0xd9493f,
},
{
name: 'y',
direction: new THREE.Vector3(0, 1, 0),
labelOffset: new THREE.Vector3(0.13, 0, -0.1),
color: 0xf4f4f4,
},
{
name: 'z',
direction: new THREE.Vector3(0, 0, 1),
labelOffset: new THREE.Vector3(-0.2, 0.14, 0),
color: 0x4caf67,
},
] as const;
export type OrientationIndicatorColors = Record<(typeof axes)[number]['name'], number>;
function label(axis: string, color: number, position: THREE.Vector3): THREE.Sprite | undefined {
if (typeof document === 'undefined') return undefined;
const canvas = document.createElement('canvas');
canvas.width = 96;
canvas.height = 96;
const context = canvas.getContext('2d');
if (!context) return undefined;
context.strokeStyle = '#10192c';
context.lineWidth = 8;
context.fillStyle = '#ffffff';
context.font = 'bold 54px Arial';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.strokeText(axis.toUpperCase(), 48, 50);
context.fillText(axis.toUpperCase(), 48, 50);
const sprite = new THREE.Sprite(
new THREE.SpriteMaterial({
map: new THREE.CanvasTexture(canvas),
color,
depthTest: false,
depthWrite: false,
}),
);
sprite.name = `orientation-label-${axis}`;
sprite.position.copy(position);
sprite.scale.setScalar(0.34);
sprite.renderOrder = 1001;
return sprite;
}
/** A compact, labelled X/Y/Z triad in cube-local coordinates. */
export function createOrientationIndicator(includeLabels = true): THREE.Group {
const indicator = new THREE.Group();
indicator.name = 'orientation-indicator';
for (const axis of axes) {
// LineBasicMaterial width is ignored by WebGL on most platforms. Model
// each arrow with meshes so all three axes stay legible at any DPI.
const arrow = new THREE.Group();
arrow.name = `orientation-axis-${axis.name}`;
const material = new THREE.MeshBasicMaterial({
color: axis.color,
depthTest: false,
depthWrite: false,
});
const shaft = new THREE.Mesh(new THREE.CylinderGeometry(0.032, 0.032, 0.62, 10), material);
const tip = new THREE.Mesh(new THREE.ConeGeometry(0.1, 0.2, 10), material);
const rotation = new THREE.Quaternion().setFromUnitVectors(
new THREE.Vector3(0, 1, 0),
axis.direction,
);
shaft.quaternion.copy(rotation);
tip.quaternion.copy(rotation);
shaft.position.copy(axis.direction).multiplyScalar(0.31);
tip.position.copy(axis.direction).multiplyScalar(0.69);
shaft.renderOrder = 1000;
tip.renderOrder = 1000;
arrow.add(shaft, tip);
arrow.traverse((object) => {
const material = (object as THREE.Mesh | THREE.Line).material as
ColorMaterial | ColorMaterial[];
if (material) {
const materials = Array.isArray(material) ? material : [material];
materials.forEach((value) => {
value.depthTest = false;
value.depthWrite = false;
});
}
});
indicator.add(arrow);
if (includeLabels) {
const axisLabel = label(
axis.name,
axis.color,
axis.direction.clone().multiplyScalar(0.98).add(axis.labelOffset),
);
if (axisLabel) indicator.add(axisLabel);
}
}
return indicator;
}
/** Recolor logical X/Y/Z axes to match their current physical R/U/F facelets. */
export function setOrientationIndicatorColors(
indicator: THREE.Group,
colors: OrientationIndicatorColors,
): void {
for (const axis of axes) {
const color = colors[axis.name];
indicator.getObjectByName(`orientation-axis-${axis.name}`)?.traverse((object) => {
const material = (object as THREE.Mesh | THREE.Line).material as
ColorMaterial | ColorMaterial[];
if (!material) return;
(Array.isArray(material) ? material : [material]).forEach((value) =>
value.color?.setHex(color),
);
});
const axisLabel = indicator.getObjectByName(`orientation-label-${axis.name}`);
if (axisLabel instanceof THREE.Sprite) axisLabel.material.color.setHex(color);
}
}
|