Visualize an ellipsoid.

Parameters
Semi-major axis m
Semi-minor axis m

Shows the local tangent plane coordinates (LTP) frame at the mouse location.

100%

Use the EllipsoidHelper to visualize an ellipsoid.

index.js
import { AxesHelper, Matrix4, Raycaster, Vector2, Vector3 } from "three";

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

import Coordinates from "@giro3d/giro3d/core/geographic/Coordinates";
import Ellipsoid from "@giro3d/giro3d/core/geographic/Ellipsoid";
import Instance from "@giro3d/giro3d/core/Instance.js";
import Inspector from "@giro3d/giro3d/gui/Inspector";
import EllipsoidHelper from "@giro3d/giro3d/helpers/EllipsoidHelper";

import { bindButton } from "./widgets/bindButton";
import { bindNumberInput } from "./widgets/bindNumberInput";
import { bindToggle } from "./widgets/bindToggle";

const instance = new Instance({
  target: "view",
  crs: "EPSG:4978",
  backgroundColor: "black",
});

const DEFAULT_PARAMS = {
  observer: new Coordinates("EPSG:4326", 30, 40, 36_000_000),
  semiMajorAxis: Ellipsoid.WGS84.semiMajorAxis,
  semiMinorAxis: Ellipsoid.WGS84.semiMinorAxis,
  ellipsoid: Ellipsoid.WGS84,
  showEnuFrame: true,
  showLines: true,
  showLabels: true,
  showAxes: true,
  showNormals: false,
};

let params = { ...DEFAULT_PARAMS };

let helper;

function createHelper() {
  if (helper) {
    helper.dispose();
    helper.removeFromParent();
  }

  const ellipsoid = new Ellipsoid({
    semiMajorAxis: params.semiMajorAxis,
    semiMinorAxis: params.semiMinorAxis,
  });

  params.ellipsoid = ellipsoid;

  helper = new EllipsoidHelper({ ellipsoid });

  instance.threeObjects.add(helper);

  helper.showAxes = params.showAxes;
  helper.showLabels = params.showLabels;
  helper.showLines = params.showLines;
  helper.showNormals = params.showNormals;

  instance.notifyChange();
}

const [setSemiMajorAxis] = bindNumberInput("semi-major-axis", (val) => {
  params.semiMajorAxis = val;

  createHelper();
});
const [setSemiMinorAxis] = bindNumberInput("semi-minor-axis", (val) => {
  params.semiMinorAxis = val;

  createHelper();
});
const [showLabels] = bindToggle("show-labels", (show) => {
  params.showLabels = show;
  helper.showLabels = show;
  instance.notifyChange();
});
const [showAxes] = bindToggle("show-axes", (show) => {
  helper.showAxes = show;
  params.showAxes = show;
  instance.notifyChange();
});
const [showLines] = bindToggle("show-lines", (show) => {
  params.showLines = show;
  helper.showLines = show;
  instance.notifyChange();
});
const [showNormals] = bindToggle("show-normals", (show) => {
  params.showNormals = show;
  helper.showNormals = show;
  instance.notifyChange();
});
const [showEnuFrame] = bindToggle("show-enu-frame", (show) => {
  params.showEnuFrame = show;
  instance.notifyChange();
});

createHelper();

const camera = instance.view.camera;

function updateCamera() {
  const { observer, ellipsoid } = params;

  const position = ellipsoid.toCartesian(
    observer.latitude,
    observer.longitude,
    observer.altitude,
  );

  camera.position.set(position.x, position.y, position.z);

  camera.lookAt(0, 0, 0);

  camera.updateMatrixWorld(true);

  instance.notifyChange(camera);
}

updateCamera();

function reset() {
  params = { ...DEFAULT_PARAMS };

  setSemiMajorAxis(params.semiMajorAxis);
  setSemiMinorAxis(params.semiMinorAxis);
  showLabels(params.showLabels);
  showLines(params.showLines);
  showAxes(params.showAxes);
  showNormals(params.showNormals);
  showEnuFrame(params.showEnuFrame);

  updateCamera();
  createHelper();
}

bindButton("reset", reset);

reset();

const controls = new OrbitControls(instance.view.camera, instance.domElement);
controls.target.set(0, 0, 0);
instance.view.setControls(controls);

const enuMatrix = new Matrix4();
const raycaster = new Raycaster();
const intersection = new Vector3();
const axes = new AxesHelper(params.ellipsoid.semiMajorAxis * 0.25);
instance.scene.add(axes);
axes.visible = false;

const onMouseMove = (event) => {
  if (!params.showEnuFrame) {
    if (axes.visible) {
      axes.visible = false;
      instance.notifyChange();
    }
    return;
  }

  const ndc = instance.eventToNormalizedCoords(event, new Vector2());
  raycaster.setFromCamera(ndc, instance.view.camera);
  const ray = raycaster.ray;

  const point = params.ellipsoid.intersectRay(ray, intersection);

  axes.visible = point != null;

  if (point) {
    axes.position.copy(point);

    const enu = params.ellipsoid.getEastNorthUpMatrixFromCartesian(
      point,
      enuMatrix,
    );

    axes.setRotationFromMatrix(enu);

    axes.updateMatrixWorld(true);
  }

  instance.notifyChange();
};

instance.domElement.addEventListener("mousemove", onMouseMove);

Inspector.attach("inspector", instance);
index.html
<!doctype html>
<html lang="en">
  <head>
    <title>Ellipsoid</title>
    <meta charset="UTF-8" />
    <meta name="name" content="ellipsoid" />
    <meta name="description" content="Visualize an ellipsoid." />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="icon" href="https://giro3d.org/images/favicon.svg" />
    <link
      href="https://giro3d.org/assets/bootstrap-custom.css"
      rel="stylesheet"
    />
    <script src="https://giro3d.org/assets/bootstrap.bundle.min.js"></script>
    <link
      rel="stylesheet"
      type="text/css"
      href="https://giro3d.org/latest/examples/css/example.css"
    />
  </head>

  <body>
    <div id="view" class="m-0 p-0 w-100 h-100"></div>
    <div
      id="inspector"
      class="position-absolute top-0 start-0 mh-100 overflow-auto"
    ></div>

    <div class="side-pane-with-status-bar" style="width: 22rem">
      <!--Parameters -->
      <div class="card">
        <div class="card-header">
          Parameters
          <button
            type="button"
            id="reset"
            class="btn btn-sm btn-primary rounded float-end"
          >
            reset
          </button>
        </div>

        <div class="card-body">
          <!-- Semi-major axis -->
          <div class="input-group mb-2">
            <span class="input-group-text">Semi-major axis</span>
            <input
              type="number"
              min="1"
              step="1"
              value="6378137"
              class="form-control"
              id="semi-major-axis"
              autocomplete="off"
            />
            <span class="input-group-text">m</span>
          </div>

          <!-- Semi-minor axis -->
          <div class="input-group mb-3">
            <span class="input-group-text">Semi-minor axis</span>
            <input
              type="number"
              min="1"
              step="1"
              value="6356752"
              class="form-control"
              id="semi-minor-axis"
              autocomplete="off"
            />
            <span class="input-group-text">m</span>
          </div>

          <!-- Toggle labels -->
          <div class="form-check form-switch">
            <input
              class="form-check-input"
              checked
              type="checkbox"
              role="switch"
              id="show-labels"
              autocomplete="off"
            />
            <label class="form-check-label" for="show-labels"
              >Show labels</label
            >
          </div>

          <!-- Toggle labels -->
          <div class="form-check form-switch">
            <input
              class="form-check-input"
              checked
              type="checkbox"
              role="switch"
              id="show-normals"
              autocomplete="off"
            />
            <label class="form-check-label" for="show-normals"
              >Show normal vectors</label
            >
          </div>

          <!-- Toggle axes -->
          <div class="form-check form-switch">
            <input
              class="form-check-input"
              checked
              type="checkbox"
              role="switch"
              id="show-axes"
              autocomplete="off"
            />
            <label class="form-check-label" for="show-axes">Show axes</label>
          </div>

          <!-- Toggle ENU frame -->
          <div class="form-check form-switch">
            <input
              class="form-check-input"
              checked
              type="checkbox"
              role="switch"
              id="show-enu-frame"
              autocomplete="off"
            />
            <label class="form-check-label" for="show-enu-frame"
              >Show ENU frame
              <span
                class="text-secondary"
                data-bs-toggle="popover"
                data-bs-content="help-enu-frame"
                ><i class="bi bi-question-circle"></i>
              </span>
            </label>

            <p class="card-text d-none" id="help-enu-frame">
              Shows the local tangent plane coordinates (LTP) frame at the mouse
              location.
            </p>
          </div>

          <!-- Toggle lines -->
          <div class="form-check form-switch">
            <input
              class="form-check-input"
              checked
              type="checkbox"
              role="switch"
              id="show-lines"
              autocomplete="off"
            />
            <label class="form-check-label" for="show-lines">Show lines</label>
          </div>
        </div>
      </div>
    </div>

    <script type="module" src="index.js"></script>
    <script>
      /* activate popovers */
      const popoverTriggerList = [].slice.call(
        document.querySelectorAll('[data-bs-toggle="popover"]'),
      );
      popoverTriggerList.map(
        // bootstrap is used as script in the template, disable warning about undef
        // eslint-disable-next-line no-undef
        (popoverTriggerEl) =>
          new bootstrap.Popover(popoverTriggerEl, {
            trigger: "hover",
            placement: "left",
            content: document.getElementById(
              popoverTriggerEl.getAttribute("data-bs-content"),
            ).innerHTML,
            html: true,
          }),
      );
    </script>
  </body>
</html>
package.json
{
    "name": "ellipsoid",
    "dependencies": {
        "@giro3d/giro3d": "0.43.4"
    },
    "devDependencies": {
        "vite": "^3.2.3"
    },
    "scripts": {
        "start": "vite",
        "build": "vite build"
    }
}