@aredtech/build-forge
v0.1.0
Published
Framework-agnostic Three.js building adapter for place, floor, feature, and marker data.
Readme
Three.js Building Adapter
Framework-agnostic TypeScript utilities for turning place, building, floor, feature, and marker data into Three.js-compatible building objects.
Installation
npm install @aredtech/build-forge threethree is a peer dependency so host applications can control the renderer version they already use.
Core Usage
import { createBuilding3D } from "@aredtech/build-forge";
const building = createBuilding3D({ place });
const local = building.adapter.toLocalPosition({ lat: 55.7175129, lon: 13.2137595 });
scene.add(building.group);
console.log(building.floors.size);Host applications own scene, camera, renderer, controls, data fetching, and framework lifecycle. The package returns a root THREE.Group, named child groups, generated object maps, adapter methods, metadata, and structured warnings without taking over rendering.
Coordinates
The default local coordinate system maps east to positive x, north to positive z, and height to positive y. Inputs can use { lat, lon }, { lat, lng }, { latitude, longitude }, [lat, lon], or [lat, lon, extra]. Local coordinates are meter-like values relative to a resolved origin.
React Helper
React helpers are optional subpath imports. They are not exported from the package root, so core consumers do not need React.
import { useEffect } from "react";
import { useBuilding3D } from "@aredtech/build-forge/react";
function BuildingLayer({ place, scene }) {
const building = useBuilding3D({ place });
useEffect(() => {
if (!building.result) {
return undefined;
}
scene.add(building.result.group);
return () => {
scene.remove(building.result.group);
};
}, [building.result, scene]);
return null;
}The hook creates core adapter output from place and options. It does not create a Three.js scene, camera, renderer, controls, canvas, data fetcher, or render loop.
Angular Helper
Angular helpers are optional subpath imports. They are not exported from the package root, so core consumers do not need Angular.
import type { Scene } from "three";
import { Building3DService } from "@aredtech/build-forge/angular";
import type { PlaceInfo } from "@aredtech/build-forge";
export class BuildingLayerController {
constructor(private readonly building3D: Building3DService) {}
attachBuilding(place: PlaceInfo, scene: Scene): void {
const result = this.building3D.create({ place });
scene.add(result.group);
}
}The service delegates to createBuilding3D and returns the same core result. Angular applications own components, scene, camera, renderer, controls, routing, data fetching, and lifecycle.
Floors and levels
The adapter builds floor meshes from building.floors[].floorShape[].nodes and adds them to building.group. The root group contains named child groups:
build-forge:floorsbuild-forge:featuresbuild-forge:markers
The same objects are available through building.groups.floors, building.groups.features, and building.groups.markers. Public maps remain available as building.floors, building.features, and building.markers.
The default floor render mode is flat, which creates a polygon surface on the XZ plane. Callers can request slab mode with options.floors.renderMode; the default slab thickness is 0.2m.
The default level resolver is auto: use floorLabel when present, otherwise floorIndex. Callers can choose floorLabel, floorIndex, or a custom resolver function through options.floors.levelResolver.
Floor y positions use this height fallback order: floor.floorHeight, building.calculatedFloorHeight, options.floors.defaultFloorHeight, then 3m. The default stacking strategy is index-based, with cumulative available through options.floors.stacking.
Generated building metadata is available at building.metadata and includes building id/name, origin, coordinate system, units, resolved levels, local bounds, and the source place object. Generated Three.js objects store source metadata under object.userData.buildForge; floors include sourceType, id, level, floor metadata, and the original source floor reference.
Feature Model
Feature data is read from place.elements by default. The feature model accepts OSM-like node, way, and relation elements:
nodeelements provide geographic coordinates for ways.- open
wayelements render as thin unlit feature lines. - Closed ways are preserved as metadata-only wrappers in Phase 4.
relationgeometry rendering is deferred; relations are preserved as metadata-only wrappers.
Renderable feature ways need tags.level; the value resolves through the same generated floor level registry used by floors and markers. Invalid geometry, missing node references, or unresolved levels append warnings and skip rendering by default. In strict mode, validation-style feature errors throw.
const building = createBuilding3D({
place: {
...place,
elements: [
{ id: "n1", type: "node", lat: 55.69945, lon: 13.0498 },
{ id: "n2", type: "node", lat: 55.69946, lon: 13.04981 },
{ id: "feature-open", type: "way", nodes: ["n1", "n2"], tags: { level: "G", highway: "footway" } }
]
}
});
const feature = building.adapter.getFeature("feature-open");
console.log(feature?.object.userData.buildForge.sourceType);Callers can customize feature interpretation with options.features.resolver. The resolver returns constrained instructions only; it does not create arbitrary THREE.Object3D instances.
const building = createBuilding3D({
place,
options: {
features: {
resolver(element, context) {
if (element.tags?.ignore === "true") {
return { kind: "skip" };
}
if (element.tags?.indoor === "room") {
return { kind: "preserve" };
}
return context.defaultInstruction.kind === "line"
? { kind: "line", color: 0x4f7cff, yOffset: 0.05 }
: context.defaultInstruction;
}
}
}
});Generated feature metadata is available in building.features, building.groups.features, building.adapter.getFeature(id), and object.userData.buildForge.
Deferred feature capabilities include relation geometry, room/polygon surfaces, tag-specific default visuals, local feature-node coordinates, snapping, trails, labels, and feature-type visibility.
Markers
Markers are optional runtime objects in building.markers and building.groups.markers.
const marker = building.adapter.upsertMarker({
id: "device-1",
lat: 55.699454602193,
lon: 13.049802649766,
level: "G",
metadata: { battery: 88 }
});
building.adapter.updateMarker("device-1", { local: { x: 2, z: 3 }, level: "G" });
building.adapter.removeMarker("device-1");
building.adapter.clearMarkers();Supported marker positions are:
- geographic
lat/lonpluslevel - local
{ x, z }pluslevel - explicit local
{ x, y, z }
Default markers are unlit sphere meshes, so lights are not required. Configure default marker appearance with options.markers.color, options.markers.radius, and options.markers.yOffset.
Custom marker objects can be created with options.markers.renderer. The renderer is called when a marker object is created; normal updateMarker calls move the existing object and preserve object identity.
const building = createBuilding3D({
place,
options: {
markers: {
renderer: (input, context) => {
const object = new THREE.Object3D();
object.position.copy(context.position);
return object;
}
}
}
});Marker failures return null or false and append warnings to building.warnings. In strict mode, unresolved marker levels throw. Removal and clear operations detach marker objects from the marker group but do not dispose custom geometry or materials by default.
Warnings and Strict Mode
By default, invalid geometry, unresolved levels, missing feature nodes, unsupported material changes, and invalid marker updates append structured warnings to building.warnings where possible. The adapter skips invalid objects instead of failing the whole build.
const building = createBuilding3D({
place,
options: {
strict: true
}
});Use strict: true when ingestion failures should throw immediately during development, tests, or controlled data imports.
Visibility
Floor visibility controls operate only on generated floor meshes; markers remain independently visible.
building.adapter.hideFloor("G");
building.adapter.showFloor("G");
building.adapter.showOnlyFloor("G");
building.adapter.showAllFloors();
building.adapter.setFloorOpacity("G", 0.4);setFloorOpacity clamps values to 0..1, mutates compatible existing materials, and updates the material transparent state. Unsupported material mutation returns false and appends a FLOOR_OPACITY_UNSUPPORTED warning.
Deferred Capabilities
Room anchors, marker snapping, marker animation, marker trails, marker labels, marker-kind visibility, and feature-type visibility are not part of this phase. They are intentionally not exposed as supported APIs yet.
Core Example
See examples/core-usage.ts for a tiny root API example using a real fixture without creating a scene, camera, renderer, controls, or UI.
Verification
npm test
npm run build
npm run check:declarations
npm run pack:dry-run
npm run smoke:import
npm run verify