37 lines
1.6 KiB
JavaScript
37 lines
1.6 KiB
JavaScript
// PROCITY core canvas-text helpers — all in-world text is CanvasTexture (no font files).
|
|
// NOTE for Lane B: per-sign planes are fine for one-offs (tooltips, plates); street signage at
|
|
// scale must go through a per-chunk atlas (CITY_SPEC perf law) — build that in world/, not here.
|
|
|
|
import * as THREE from 'three';
|
|
|
|
// Draw text into a canvas → { canvas, w, h } (px). Multi-line via \n.
|
|
export function textCanvas(text, { font = 'bold 48px Arial', fg = '#222', bg = null,
|
|
padX = 24, padY = 16, lineGap = 1.15 } = {}) {
|
|
const lines = String(text).split('\n');
|
|
const c = document.createElement('canvas');
|
|
const x = c.getContext('2d');
|
|
x.font = font;
|
|
const lineH = parseInt(font.match(/(\d+)px/)?.[1] || 48, 10) * lineGap;
|
|
const w = Math.ceil(Math.max(...lines.map(l => x.measureText(l).width)) + padX * 2);
|
|
const h = Math.ceil(lineH * lines.length + padY * 2);
|
|
c.width = w; c.height = h;
|
|
if (bg) { x.fillStyle = bg; x.fillRect(0, 0, w, h); }
|
|
x.font = font; x.fillStyle = fg; x.textBaseline = 'top';
|
|
lines.forEach((l, i) => x.fillText(l, padX, padY + i * lineH));
|
|
return { canvas: c, w, h };
|
|
}
|
|
|
|
// One-off text plane mesh, height in metres, width follows aspect.
|
|
export function textPlane(text, heightM = 0.4, opts = {}) {
|
|
const { canvas, w, h } = textCanvas(text, opts);
|
|
const tex = new THREE.CanvasTexture(canvas);
|
|
tex.colorSpace = THREE.SRGBColorSpace;
|
|
tex.anisotropy = 4;
|
|
const mesh = new THREE.Mesh(
|
|
new THREE.PlaneGeometry(heightM * (w / h), heightM),
|
|
new THREE.MeshBasicMaterial({ map: tex, transparent: !opts.bg })
|
|
);
|
|
mesh.userData.isTextPlane = true;
|
|
return mesh;
|
|
}
|