start reworking platform axis labels
This commit is contained in:
parent
27293271de
commit
fc6ea03f8d
2 changed files with 181 additions and 26 deletions
|
|
@ -1230,7 +1230,7 @@ self.kiri.load(api => {
|
||||||
if (!bound) {
|
if (!bound) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (devsel.extras?.bbl && !api.ui.deviceSave.disabled) {
|
if (devsel.extras?.bbl && !api.ui.devices.save.disabled) {
|
||||||
device = devsel;
|
device = devsel;
|
||||||
printers = devsel.extras.bbl;
|
printers = devsel.extras.bbl;
|
||||||
bound.bblman.classList.remove('hide');
|
bound.bblman.classList.remove('hide');
|
||||||
|
|
|
||||||
|
|
@ -444,6 +444,129 @@ function canvasInMesh(w, h, textAlign, textBaseline, color, size) {
|
||||||
return { context, mesh };
|
return { context, mesh };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Font atlas for 3D text labels.
|
||||||
|
* Generates a texture atlas with characters 0-9, -, X, Y at startup.
|
||||||
|
*/
|
||||||
|
let fontAtlas = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create bitmap font atlas for grid labels.
|
||||||
|
* Generates texture with characters: 0-9, -, X, Y
|
||||||
|
* @returns {object} Atlas data with texture and UV mapping
|
||||||
|
*/
|
||||||
|
function createFontAtlas() {
|
||||||
|
const chars = '0123456789-XY';
|
||||||
|
const canvas = DOC.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const charSize = 64;
|
||||||
|
|
||||||
|
canvas.width = charSize * chars.length;
|
||||||
|
canvas.height = charSize;
|
||||||
|
|
||||||
|
// Clear to transparent
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Load Russo One font or fallback to sans-serif
|
||||||
|
ctx.font = `bold ${charSize * 0.8}px 'Russo One', sans-serif`;
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
|
const uvMap = {};
|
||||||
|
for (let i = 0; i < chars.length; i++) {
|
||||||
|
ctx.fillText(chars[i], i * charSize + charSize/2, charSize/2);
|
||||||
|
uvMap[chars[i]] = {
|
||||||
|
uStart: i / chars.length,
|
||||||
|
uEnd: (i + 1) / chars.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
texture.minFilter = THREE.LinearFilter;
|
||||||
|
texture.magFilter = THREE.LinearFilter;
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
|
||||||
|
console.log('Font atlas created', { width: canvas.width, height: canvas.height, chars: chars.length });
|
||||||
|
|
||||||
|
return { texture, uvMap, charSize };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create 3D text label using bitmap font atlas.
|
||||||
|
* @param {string} text - Text to render
|
||||||
|
* @param {number} size - Character size
|
||||||
|
* @param {string|number} color - Text color (CSS string or hex number)
|
||||||
|
* @param {string} align - Horizontal alignment ('left', 'center', 'right')
|
||||||
|
* @returns {THREE.Group} Group containing character meshes
|
||||||
|
*/
|
||||||
|
function create3DTextLabel(text, size, color, align = 'center') {
|
||||||
|
if (!fontAtlas) {
|
||||||
|
fontAtlas = createFontAtlas();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { texture, uvMap, charSize } = fontAtlas;
|
||||||
|
const group = new THREE.Group();
|
||||||
|
const charWidth = size * 0.6; // Narrower spacing for better appearance
|
||||||
|
const totalWidth = text.length * charWidth;
|
||||||
|
|
||||||
|
// Convert CSS color string to THREE.Color if needed
|
||||||
|
let threeColor = 0x333333; // default gray
|
||||||
|
if (color) {
|
||||||
|
if (typeof color === 'string') {
|
||||||
|
threeColor = new THREE.Color(color);
|
||||||
|
} else {
|
||||||
|
threeColor = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate starting X based on alignment
|
||||||
|
let startX = 0;
|
||||||
|
if (align === 'center') {
|
||||||
|
startX = -totalWidth / 2;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
startX = -totalWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a mesh for each character
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const char = text[i];
|
||||||
|
const uvData = uvMap[char];
|
||||||
|
|
||||||
|
if (!uvData) {
|
||||||
|
console.warn('Character not in atlas:', char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create plane geometry for this character
|
||||||
|
const geometry = new THREE.PlaneGeometry(charWidth, size);
|
||||||
|
const uvAttr = geometry.attributes.uv;
|
||||||
|
|
||||||
|
// Set UV coordinates to show only this character from atlas
|
||||||
|
// PlaneGeometry UVs: bottom-left, bottom-right, top-left, top-right
|
||||||
|
uvAttr.setXY(0, uvData.uStart, 0); // bottom-left
|
||||||
|
uvAttr.setXY(1, uvData.uEnd, 0); // bottom-right
|
||||||
|
uvAttr.setXY(2, uvData.uStart, 1); // top-left
|
||||||
|
uvAttr.setXY(3, uvData.uEnd, 1); // top-right
|
||||||
|
|
||||||
|
const material = new THREE.MeshBasicMaterial({
|
||||||
|
map: texture,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.9,
|
||||||
|
color: threeColor,
|
||||||
|
depthWrite: false,
|
||||||
|
side: THREE.DoubleSide
|
||||||
|
});
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.position.x = startX + i * charWidth + charWidth / 2;
|
||||||
|
|
||||||
|
group.add(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function setRulers(
|
function setRulers(
|
||||||
xon = ruler.xon,
|
xon = ruler.xon,
|
||||||
|
|
@ -475,50 +598,82 @@ function updateRulers() {
|
||||||
oldView = ruler.view,
|
oldView = ruler.view,
|
||||||
view = ruler.view = new THREE.Group();
|
view = ruler.view = new THREE.Group();
|
||||||
|
|
||||||
if (xon && axesOn) {
|
console.log('updateRulers called', {
|
||||||
let xPadding = labelSize * 4,
|
xon, yon, axesOn, labelSize, rulerColor, factor,
|
||||||
canvas = canvasInMesh(x + xPadding, labelSize * 3, 'center', 'top', rulerColor, labelSize),
|
x1: ruler.x1, x2: ruler.x2, y1: ruler.y1, y2: ruler.y2,
|
||||||
context = canvas.context,
|
xo: ruler.xo, yo: ruler.yo,
|
||||||
mesh = canvas.mesh;
|
originX: grid.origin.x, originY: grid.origin.y
|
||||||
if (!(context && mesh)) return;
|
});
|
||||||
|
|
||||||
|
if (xon && axesOn) {
|
||||||
|
// Create X-axis numeric labels
|
||||||
|
// Original canvas code draws at canvas X: ruler.xo + i + xPadding/2
|
||||||
|
// Canvas is centered at world X=0, canvas width = x + xPadding
|
||||||
|
// So world X = (canvasX - canvasWidth/2) = (ruler.xo + i + xPadding/2) - (x + xPadding)/2
|
||||||
|
// = ruler.xo + i - x/2 = ruler.xo + i - w
|
||||||
if (platformBelt) {
|
if (platformBelt) {
|
||||||
for (let i = 0; i <= ruler.x2; i += grid.unitMajor) {
|
for (let i = 0; i <= ruler.x2; i += grid.unitMajor) {
|
||||||
context.fillText((i * factor).round(1).toString(), ruler.x2 - i + xPadding / 2, 0);
|
const value = (i * factor).round(1).toString();
|
||||||
|
const label = create3DTextLabel(value, labelSize, rulerColor, 'center');
|
||||||
|
|
||||||
|
label.position.set(ruler.x2 - i, -h - labelSize * 2, zp);
|
||||||
|
label.rotation.x = Math.PI;
|
||||||
|
view.add(label);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i >= ruler.x1; i -= grid.unitMajor) {
|
for (let i = 0; i >= ruler.x1; i -= grid.unitMajor) {
|
||||||
context.fillText((i * factor).round(1).toString(), ruler.xo + i + xPadding / 2, 0);
|
const value = (i * factor).round(1).toString();
|
||||||
|
const label = create3DTextLabel(value, labelSize, rulerColor, 'center');
|
||||||
|
|
||||||
|
label.position.set(ruler.xo + i - w, -h - labelSize * 2, zp);
|
||||||
|
label.rotation.x = Math.PI;
|
||||||
|
view.add(label);
|
||||||
}
|
}
|
||||||
for (let i = 0; i <= ruler.x2; i += grid.unitMajor) {
|
for (let i = 0; i <= ruler.x2; i += grid.unitMajor) {
|
||||||
context.fillText((i * factor).round(1).toString(), ruler.xo + i + xPadding / 2, 0);
|
const value = (i * factor).round(1).toString();
|
||||||
|
const label = create3DTextLabel(value, labelSize, rulerColor, 'center');
|
||||||
|
|
||||||
|
label.position.set(ruler.xo + i - w, -h - labelSize * 2, zp);
|
||||||
|
label.rotation.x = Math.PI;
|
||||||
|
view.add(label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.font = (labelSize * 0.75) + 'px sans-serif';
|
// Create X-axis label
|
||||||
context.fillText(xlabel, (x + xPadding) / 2, labelSize * 1.5);
|
const xLabel = create3DTextLabel(xlabel, labelSize * 0.75, rulerColor, 'center');
|
||||||
mesh.position.set(0, - h - labelSize * 2, zp);
|
xLabel.position.set(0, -h - labelSize * 3.5, zp);
|
||||||
view.add(mesh);
|
xLabel.rotation.x = Math.PI;
|
||||||
|
view.add(xLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (yon && axesOn) {
|
if (yon && axesOn) {
|
||||||
let yPadding = labelSize,
|
// Create Y-axis numeric labels
|
||||||
canvas = canvasInMesh(labelSize * 4, y + yPadding, 'end', 'middle', rulerColor, labelSize),
|
// Original canvas code draws at canvas Y: y - (ruler.yo + i) + yPadding/2
|
||||||
context = canvas.context,
|
// Canvas is centered at world Y=0, canvas height = y + yPadding
|
||||||
mesh = canvas.mesh;
|
// So world Y = (canvasY - canvasHeight/2) = [y - (ruler.yo + i) + yPadding/2] - (y + yPadding)/2
|
||||||
if (!(context && mesh)) return;
|
// = y/2 - ruler.yo - i = h - ruler.yo - i
|
||||||
|
|
||||||
for (let i = 0; i >= ruler.y1; i -= grid.unitMajor) {
|
for (let i = 0; i >= ruler.y1; i -= grid.unitMajor) {
|
||||||
context.fillText((i * factor).round(1), labelSize * 4, y - (ruler.yo + i) + yPadding / 2);
|
const value = (i * factor).round(1).toString();
|
||||||
|
const label = create3DTextLabel(value, labelSize, rulerColor, 'right');
|
||||||
|
|
||||||
|
label.position.set(-w - labelSize * 2 - 5, h - ruler.yo - i, zp);
|
||||||
|
label.rotation.x = Math.PI;
|
||||||
|
view.add(label);
|
||||||
}
|
}
|
||||||
for (let i = 0; i <= ruler.y2; i += grid.unitMajor) {
|
for (let i = 0; i <= ruler.y2; i += grid.unitMajor) {
|
||||||
context.fillText((i * factor).round(1), labelSize * 4, y - (ruler.yo + i) + yPadding / 2);
|
const value = (i * factor).round(1).toString();
|
||||||
|
const label = create3DTextLabel(value, labelSize, rulerColor, 'right');
|
||||||
|
|
||||||
|
label.position.set(-w - labelSize * 2 - 5, h - ruler.yo - i, zp);
|
||||||
|
label.rotation.x = Math.PI;
|
||||||
|
view.add(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.font = (labelSize * 0.75) + 'px sans-serif';
|
// Create Y-axis label
|
||||||
context.fillText(ylabel, labelSize * 1.25, (y + yPadding) / 2);
|
const yLabel = create3DTextLabel(ylabel, labelSize * 0.75, rulerColor, 'center');
|
||||||
mesh.position.set(-w - labelSize * 2 - 5, 0, zp);
|
yLabel.position.set(-w - labelSize * 5, 0, zp);
|
||||||
view.add(mesh);
|
yLabel.rotation.x = Math.PI;
|
||||||
|
view.add(yLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
Space.scene.remove(oldView);
|
Space.scene.remove(oldView);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue