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);
<!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>
{
"name": "ellipsoid",
"dependencies": {
"@giro3d/giro3d": "0.43.2"
},
"devDependencies": {
"vite": "^3.2.3"
},
"scripts": {
"start": "vite",
"build": "vite build"
}
}