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 | 39x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 16x 16x 16x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 48x 144x 144x 96x 96x 96x 96x 48x 48x 48x 48x 16x 28x 84x 84x 252x 252x 168x 168x 84x 84x | 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);
}
}
|