Skip to content

MeshDesc

MeshDesc is the base class for all mesh Descriptors. It provides common configuration properties, transform composition, and picking support. Every mesh Descriptor — both built-in and custom — inherits from this class, so the features described here are available on all mesh Descriptors.

PropertyTypeDefaultDescription
idstringAuto-generatedUnique identifier for the object
visiblebooleantrueToggle visibility of the object
position{ x: number, y: number, z: number }-Position (ECEF), or local offset when matrix/matrixWorld is set
rotation{ x: number, y: number, z: number }-Rotation (Euler angles, radians), or local offset when matrix/matrixWorld is set
scale{ x: number, y: number, z: number }-Scale, or local offset when matrix/matrixWorld is set
matrixMatrix4-Local transform matrix. When set, position/rotation/scale become offsets within this frame
matrixWorldMatrix4-World transform matrix. When set, position/rotation/scale become offsets within this frame
pickablebooleanfalseEnable GPU-based click picking. Defined per Descriptor by picking-capable mesh Descriptors, not on the base config

MeshDesc supports three transform modes.

When neither matrix nor matrixWorld is set, position, rotation, and scale are applied directly to the Three.js object in the ECEF coordinate system — the same way standard Three.js transforms work.

When matrix is set, Three.js matrixAutoUpdate is disabled and the final local matrix is computed as:

effective = matrix · T(position) · R(rotation) · S(scale)

This lets you supply a base frame and then express offsets within that frame.

When matrixWorld is set, both matrixAutoUpdate and matrixWorldAutoUpdate are disabled, and the final world matrix is computed as:

effective = matrixWorld · T(position) · R(rotation) · S(scale)

This is the most common mode for geographic placement. You supply a world-space reference frame (e.g., an ENU tangent frame from eastNorthUpToFixedFrame()) and express local offsets within that frame. This eliminates the need to manually compose frame matrices when positioning meshes on the globe.

import ThreeView, {
Color,
geodeticToVector3,
eastNorthUpToFixedFrame,
degreeToRadian,
} from "@navaramap/three";
import { BoxMeshDesc } from "@navaramap/three_default_descs";
const view = new ThreeView();
view.registerMesh("box", BoxMeshDesc);
await view.init();
// Compute the ENU frame at a geographic origin
const origin = geodeticToVector3({
lat: degreeToRadian(35.681236),
lng: degreeToRadian(139.767125),
height: 0,
});
const enuFrame = eastNorthUpToFixedFrame(origin);
// Place a box 200m east and 50m above the origin
const box1 = view.addMesh<BoxMeshDesc>({
box: { width: 50, height: 100, depth: 50, color: new Color().setHex(0xff0000) },
matrixWorld: enuFrame,
position: { x: 200, y: 50, z: 0 },
});
// Place another box 100m north
const box2 = view.addMesh<BoxMeshDesc>({
box: { width: 50, height: 80, depth: 50, color: new Color().setHex(0x00ff00) },
matrixWorld: enuFrame,
position: { x: 0, y: 40, z: 100 },
});

Mesh Descriptors can opt into GPU-based click picking by setting pickable: true in the Descriptor config. The picking system renders pickable meshes into a dedicated single-pixel render target with each mesh’s batch ID encoded as an RGB color, reads back the pixel, and emits a "pick" event identifying which mesh was clicked.

import ThreeView, { Color } from "@navaramap/three";
import { BoxMeshDesc } from "@navaramap/three_default_descs";
const view = new ThreeView({ picking: true });
view.registerMesh("box", BoxMeshDesc);
await view.init();
const boxDesc = view.addMesh<BoxMeshDesc>({
box: {
width: 100,
height: 100,
depth: 100,
color: new Color().setHex(0xff0000),
},
position: { x: 0, y: 0, z: 1000 },
pickable: true,
});
view.on("pick", (info) => {
if (info) {
console.log("Picked layer:", info.layerId);
console.log("Batch ID:", info.batchId);
}
});

The batch ID is a unique 24-bit integer assigned to each pickable mesh (or each instance in an instanced mesh Descriptor). You can read it from the Descriptor reference to determine which mesh was clicked:

// Single mesh Descriptor
const batchId = boxDesc.ref.batchId;
// Instanced mesh Descriptor — one batch ID per instance
const batchIds = instancedDesc.ref.batchIds;
view.on("pick", (info) => {
if (info && info.batchId === boxDesc.ref.batchId) {
// Highlight the selected box
boxDesc.update({ box: { color: new Color().setHex(0xffff00) } });
}
});
type PickedFeature = {
batchId: number; // 24-bit encoded ID
properties?: Record<string, unknown>; // Feature properties (for GIS layers)
layerId?: string; // Layer identifier
};

For implementing picking in custom Descriptors, see Custom Descriptor — Implementing Picking.

The position property uses the ECEF (Earth-Centered, Earth-Fixed) coordinate system. To convert from latitude/longitude/altitude (geodetic coordinates) to ECEF, use the geodeticToVector3() function.

import ThreeView, {
Color,
geodeticToVector3,
degreeToRadian,
} from "@navaramap/three";
import { SphereMeshDesc } from "@navaramap/three_default_descs";
const view = new ThreeView();
view.registerMesh("sphere", SphereMeshDesc);
await view.init();
// Convert from latitude/longitude/altitude to ECEF coordinates
const position = geodeticToVector3({
lat: degreeToRadian(35.681236), // Latitude (radians)
lng: degreeToRadian(139.767125), // Longitude (radians)
height: 200, // Altitude (meters)
});
// Add a mesh Descriptor with the converted coordinates
const sphereDesc = view.addMesh<SphereMeshDesc>({
sphere: {
radius: 100,
color: new Color().setHex(0x00aaff),
},
position: {
x: position.x,
y: position.y,
z: position.z,
},
});

Using a Local Tangent Frame (ENU and others)

Section titled “Using a Local Tangent Frame (ENU and others)”

The position property is Cartesian ECEF by default, so a bare position will not stand a mesh upright at a given longitude/latitude. For geographic placement, compute a local tangent frame at the origin and pass it as matrixWorld; position/rotation/scale are then interpreted as offsets within that frame.

Choose the frame function that matches the axis orientation your mesh expects. All take an ECEF origin (Vector3) and return a Matrix4, and all are exported from @navaramap/three:

FunctionLocal axes (x, y, z)
eastNorthUpToFixedFrame()East, North, Up
northEastDownToFixedFrame()North, East, Down
northUpEastToFixedFrame()North, Up, East
northWestUpToFixedFrame()North, West, Up

The most common choice is ENU (eastNorthUpToFixedFrame()):

import {
geodeticToVector3,
eastNorthUpToFixedFrame,
degreeToRadian,
} from "@navaramap/three";
import { GLTFModelDesc } from "@navaramap/three_default_descs";
// GLTFModelDesc must be registered
// Compute position
const origin = geodeticToVector3({
lat: degreeToRadian(35.681236),
lng: degreeToRadian(139.767125),
height: 0,
});
const enuFrame = eastNorthUpToFixedFrame(origin);
// Place the model along the Earth's surface
const modelDesc = view.addMesh<GLTFModelDesc>({
gltfModel: {
url: "/models/building.gltf",
},
matrixWorld: enuFrame,
});
FunctionDescription
geodeticToVector3()Converts geodetic coordinates (latitude/longitude/altitude) to ECEF coordinates (Vector3)
vector3ToGeodetic()Converts ECEF coordinates (Vector3) to geodetic coordinates
degreeToRadian()Converts degrees to radians
radianToDegree()Converts radians to degrees
geodeticSurfaceNormal()Gets the Earth’s surface normal vector at the specified position
eastNorthUpToFixedFrame()Gets the ENU (East-North-Up) tangent-frame matrix at the origin
northEastDownToFixedFrame()Gets the NED (North-East-Down) tangent-frame matrix at the origin
northUpEastToFixedFrame()Gets the NUE (North-Up-East) tangent-frame matrix at the origin
northWestUpToFixedFrame()Gets the NWU (North-West-Up) tangent-frame matrix at the origin

For details, see navara_three_api.