Source: label.js

// @ts-check

/**
 * CS559 3D World Framework Code
 *
 * Helpers for creating text sprite labels and attaching them to GrObjects.
 *
 * @module label
 */

import * as T from "three";
import { GrObject } from "./GrObject.js";

/**
 * Build a sprite label by drawing text to a canvas and using it as a texture.
 *
 * @param {string} text
 * @returns {T.Sprite}
 */
export function makeLabel(text) {
  const canvas = document.createElement("canvas");
  canvas.width = 256;
  canvas.height = 64;

  const ctx = canvas.getContext("2d");
  if (!ctx) {
    throw new Error("Unable to create 2D context for label sprite.");
  }

  ctx.fillStyle = "rgba(255,255,255,0.85)";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.strokeStyle = "#666";
  ctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
  ctx.fillStyle = "#222";
  ctx.font = "28px sans-serif";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(text, canvas.width / 2, canvas.height / 2);

  const tex = new T.CanvasTexture(canvas);
  tex.colorSpace = T.SRGBColorSpace;
  const mat = new T.SpriteMaterial({ map: tex, transparent: true });
  const sprite = new T.Sprite(mat);
  sprite.scale.set(1.85, 0.46, 1);
  return sprite;
}

/**
 * Create and add a label above a GrObject.
 *
 * If `offset` is not provided, the y-offset is estimated from the object's
 * bounding-box height.
 *
 * @param {GrObject} grObject
 * @param {string} labelText
 * @param {T.Vector3 | number} [offset]
 * @returns {T.Sprite}
 */
export function addLabelToGrObject(grObject, labelText, offset) {
  if (!grObject.objects.length) {
    throw new Error("Cannot add label: GrObject has no root objects.");
  }

  const bbox = new T.Box3();
  grObject.objects.forEach((obj) => bbox.expandByObject(obj));

  const center = bbox.getCenter(new T.Vector3());
  const size = bbox.getSize(new T.Vector3());

  let offsetVec;
  if (offset instanceof T.Vector3) {
    offsetVec = offset.clone();
  } else if (typeof offset === "number") {
    offsetVec = new T.Vector3(0, offset, 0);
  } else {
    offsetVec = new T.Vector3(0, Math.max(0.25, size.y * 0.15), 0);
  }

  const label = makeLabel(labelText);
  label.position.set(
    center.x + offsetVec.x,
    bbox.max.y + offsetVec.y,
    center.z + offsetVec.z
  );

  grObject.objects.push(label);
  return label;
}