From: Svjatoslav Agejenko Date: Wed, 9 Sep 2026 19:07:48 +0000 (+0300) Subject: feat(head-tracking-demo): add compass-ring head tracking demo X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=2cb433f52f163e1127e6193feb0d4773ed9d2e9a;p=sixth-3d-demos.git feat(head-tracking-demo): add compass-ring head tracking demo - HeadTrackingDemo: eight direction labels surrounding the camera so head yaw is immediately visible, plus a floor grid and a hint panel (plug in glasses, Scroll Lock recenters) - register the demo in the launcher list - document head tracking controls and hot-plug behavior in doc/index.org --- diff --git a/doc/index.org b/doc/index.org index 9779076..d45c8c1 100644 --- a/doc/index.org +++ b/doc/index.org @@ -171,11 +171,22 @@ You should see this: | Arrow Right | Move right (strafe) | | Mouse drag | Look around (rotate camera) | | Mouse scroll wheel | Move up / down | +| Scroll Lock | Recenter head tracking | Movement uses physics-based acceleration for smooth, natural motion. The faster you're moving, the more acceleration builds up, creating an intuitive flying experience. +*Head tracking:* when RayNeo XR glasses are plugged in (USB), every +demo automatically rotates the camera with your head — the virtual +world stays fixed in space while you look around. Hot-plug works at +runtime; plugging mid-demo starts tracking within a couple of seconds, +unplugging returns to mouse/keyboard control. Hold still for a second +after plug-in while the gyro calibrates, and press *Scroll Lock* to +recenter if the view drifts off-center. Disable with +=-De3d.headtrack=false=. See the *Head tracking* demo for a compass +scene built to show this off. + Press *F12* in any demo to open the [[https://www3.svjatoslav.eu/projects/sixth-3d/#developer-tools][Developer Tools panel]]. This debugging interface provides real-time insight into the rendering pipeline: diagnostic toggles, camera position, frustum culling diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/head_tracking_demo/HeadTrackingDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/head_tracking_demo/HeadTrackingDemo.java new file mode 100644 index 0000000..f7d7cab --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/head_tracking_demo/HeadTrackingDemo.java @@ -0,0 +1,110 @@ +/* + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + */ +package eu.svjatoslav.sixth.e3d.examples.head_tracking_demo; + +import eu.svjatoslav.sixth.e3d.gui.TextPointer; +import eu.svjatoslav.sixth.e3d.gui.ViewFrame; +import eu.svjatoslav.sixth.e3d.gui.ViewPanel; +import eu.svjatoslav.sixth.e3d.math.Transform; +import eu.svjatoslav.sixth.e3d.renderer.raster.Color; +import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas; + +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; + +/** + * Head tracking demo for RayNeo XR glasses. + * + *

The camera stands at the center of a compass ring: eight direction + * labels surround you at every azimuth. With glasses plugged in, turn + * your head — the world stays fixed in space and the labels pan by. + * Without glasses, drag the mouse / use arrow keys instead.

+ * + *

Head tracking is automatic in every engine app (including this + * one): the glasses are hot-plug detected, no setup code needed. + * Press Scroll Lock to recenter the view to your current head + * pose. Disable with {@code -De3d.headtrack=false}.

+ */ +public class HeadTrackingDemo { + + private static final String[] DIRECTIONS = + {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}; + + private static final double RING_RADIUS = 400; + + public static void main(final String[] args) { + new HeadTrackingDemo().run(); + } + + private void run() { + final ViewFrame viewFrame = new ViewFrame("Head Tracking Demo"); + final ViewPanel viewPanel = viewFrame.getViewPanel(); + final ShapeCollection scene = viewPanel.getRootShapeCollection(); + + // camera at the center of the compass ring, looking north (+Z) + viewPanel.getCamera().getTransform().setTranslation( + point(0, 0, 0)); + + addCompassRing(scene); + addFloorGrid(scene); + addHint(scene); + + viewPanel.repaintDuringNextViewUpdate(); + } + + /** + * Eight direction labels around the camera, facing inward. + */ + private static void addCompassRing(final ShapeCollection scene) { + final Color[] colors = { + Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, + Color.BLUE, Color.MAGENTA, new Color(255, 165, 0), + Color.WHITE, + }; + for (int i = 0; i < DIRECTIONS.length; i++) { + final double angle = i * Math.PI / 4; + final double x = Math.sin(angle) * RING_RADIUS; + final double z = Math.cos(angle) * RING_RADIUS; + + final String label = " " + DIRECTIONS[i] + " "; + final Transform transform = new Transform(point(x, 0, z)); + // yaw the label so its readable face points at the origin; + // yaw=angle+PI would show the back face (mirrored text) + transform.set(x, 0, z, angle, 0, 0); + final TextCanvas canvas = new TextCanvas(transform, + new TextPointer(2, label.length()), + colors[i], new Color(0, 0, 16)); + canvas.setText(label + "\n" + " ".repeat(label.length())); + scene.addShape(canvas); + } + } + + private static void addFloorGrid(final ShapeCollection scene) { + final LineAppearance appearance = + new LineAppearance(1, new Color(40, 40, 40)); + final double size = 1000; + final double step = 100; + for (double x = -size; x <= size; x += step) + scene.addShape(appearance.getLine( + point(x, 200, -size), point(x, 200, size))); + for (double z = -size; z <= size; z += step) + scene.addShape(appearance.getLine( + point(-size, 200, z), point(size, 200, z))); + } + + private static void addHint(final ShapeCollection scene) { + final String[] lines = { + " Plug in RayNeo glasses and look around ", + " Scroll Lock = recenter view ", + }; + final Transform transform = new Transform(point(0, -80, 150)); + final TextCanvas hint = new TextCanvas(transform, + new TextPointer(lines.length, lines[0].length()), + new Color(200, 200, 200), new Color(0, 0, 0)); + hint.setText(String.join("\n", lines)); + scene.addShape(hint); + } +} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java index 28673e4..22852a1 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java @@ -67,6 +67,10 @@ class ApplicationListPanel extends JPanel { new DemoEntry("Stereoscopic 3D", "Side-by-side stereo rendering for XR glasses — press SHIFT+F11", new ShowStereoDemo()), + + new DemoEntry("Head tracking", + "Compass ring around you — plug in RayNeo glasses and look around", + new ShowHeadTrackingDemo()), }; private static final DemoEntry[] OTHER_DEMOS = { @@ -418,4 +422,16 @@ class ApplicationListPanel extends JPanel { eu.svjatoslav.sixth.e3d.examples.stereo_demo.StereoDemo.main(null); } } + + private static class ShowHeadTrackingDemo extends AbstractAction { + ShowHeadTrackingDemo() { + putValue(NAME, "Head tracking"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + eu.svjatoslav.sixth.e3d.examples.head_tracking_demo + .HeadTrackingDemo.main(null); + } + } } \ No newline at end of file