-package eu.svjatoslav.aukio.e3d2demos;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import eu.svjatoslav.aukio.e3d2.kernel.GuiStore;
-import eu.svjatoslav.aukio.e3d2.kernel.Pass;
-import eu.svjatoslav.aukio.e3d2.kernel.SdfFontStore;
-import eu.svjatoslav.aukio.e3d2.kernel.Substrate;
-import eu.svjatoslav.aukio.e3d2.systems.gui.GuiRenderPass;
-
-/**
- * Forward-oriented text labels anchored to 3D points — the e3d2 answer
- * to legacy ForwardOrientedTextBlock: instead of text GEOMETRY (a
- * plaque quad facing the start pose), each label is screen-space SDF
- * text anchored to a projected 3D point. Always faces the viewer,
- * never mirrored, scales with distance like world geometry (UNCLAMPED
- * — user decision 2026-10-13), hides when the anchor is behind the
- * camera.
- *
- * Demo-local on purpose: generic 3D-anchored labels become an ENGINE
- * feature when a consumer outside demos exists.
- */
-public final class LabelPass implements Pass {
-
- /** World size the label emulates at the anchor's depth. */
- private static final double WORLD_SIZE = 30;
- /** Hide closer than this camera-space z (near-plane margin). */
- private static final double HIDE_Z = 5;
-
- private static final class Entry {
- double x, y, z;
- String text;
- int argb;
- GuiStore.Label label;
- }
-
- private final List<Entry> entries = new ArrayList<>();
-
- /** Adds a label anchored at world (x,y,z). Returns this (fluent). */
- public LabelPass add(final double x, final double y, final double z,
- final String text, final int argb) {
- final Entry e = new Entry();
- e.x = x;
- e.y = y;
- e.z = z;
- e.text = text;
- e.argb = argb;
- entries.add(e);
- return this;
- }
-
- @Override
- public int order() {
- return 249; // with diag-hud, just before gui-render (250)
- }
-
- @Override
- public String name() {
- return "labels";
- }
-
- @Override
- public void apply(final Substrate data) {
- if (data.guiFont == null || data.image == null) {
- return;
- }
- for (final Entry e : entries) {
- if (e.label == null) {
- e.label = new GuiStore.Label(0, 0, 0.2, e.argb, e.text);
- data.gui.labels.add(e.label);
- }
- place(data, e);
- }
- }
-
- /** Projects the anchor with exactly TransformPass's math, then
- * moves the label's screen position/scale. */
- private void place(final Substrate data, final Entry e) {
- final double cosY = Math.cos(data.camera.yaw);
- final double sinY = Math.sin(data.camera.yaw);
- final double cosP = Math.cos(data.camera.pitch);
- final double sinP = Math.sin(data.camera.pitch);
- final double dx = e.x - data.camera.x;
- final double dy = e.y - data.camera.y;
- final double dz = e.z - data.camera.z;
- // inverse yaw about +Y, then pitch about +X (TransformPass)
- final double x1 = dx * cosY - dz * sinY;
- final double z1 = dx * sinY + dz * cosY;
- final double cy1 = dy * cosP - z1 * sinP;
- final double cz1 = dy * sinP + z1 * cosP;
- if (cz1 < HIDE_Z) {
- e.label.text = ""; // behind the near plane: hide
- return;
- }
- e.label.text = e.text;
- final double sx = data.image.width / 2.0 + (x1 / cz1) * data.camera.scale;
- final double sy = data.image.height / 2.0 - (cy1 / cz1) * data.camera.scale;
- // perspective scale, UNCLAMPED (user decision 2026-10-13):
- // labels shrink to specks far away and blow up point-blank,
- // exactly like world geometry
- final double s = WORLD_SIZE * data.camera.scale
- / (cz1 * SdfFontStore.CELL);
- e.label.scale = s;
- e.label.x = (int) (sx - GuiRenderPass.textWidth(data.guiFont, e.text, s) / 2.0);
- e.label.y = (int) (sy + SdfFontStore.CELL * s * 0.25); // baseline ~centered
- }
-}