# 3D Mesh Document ## Preparation and Publishing Guide **Basic requirements, formats, CRS/CRC, scene/layer conventions, and GeoJSON metadata preparation for Monitored-AI / OCLI** --- ## Architecture & Lifecycle Overview How a 3D document travels from raw pipeline output to interactive 3D web rendering: ``` [ 3D Asset (.glb) ] │ ├── 1. Export & Validate (Meters, Y-up, Node Hierarchy) │ [ Metadata (.geojson) ] ◄── 2. ai makegeojson (CRS anchor, layers, roles) │ ├── 3. ai upload (Upload .glb & .geojson to S3/COS with CRC check) │ [ Cloud Object Storage ] │ ├── 4. publish post (Register Feature in PostGIS docs DB) │ [ Monitored-AI UI ] ── 5. Fetch Feature ──► Stream GLB ──► Render 3D Scene ``` --- ## 3D Format Specifications | Parameter | Specification | Notes | |:---------------------|:------------------------------------|:----------------------------------------------------------------------------------------------------| | **Payload Format** | **Binary glTF (`.glb`)** | Self-contained single file (geometry, buffers, textures embedded). | | **Units & Scale** | **Meters (1 unit = 1.0 m)** | Crucial for correct positioning on digital elevation / globe. | | **Axis Orientation** | **glTF Standard: Y-Up, +Z Forward** | Model roots must conform to glTF Y-up. Conversion from GIS Z-up must be baked into node transforms. | | **Geometry** | Indexed triangle meshes | Keep clean normal vectors and UV coordinates. | | **Optimization** | Draco compression optional | Recommended for models > 20 MB; ensure viewer supports extension. | --- ## Spatial Reference (CRS) & Georeferencing 3D models and metadata use **EPSG:4979** (3D) and **EPSG:4326** (2D) as standard across 3D AIKPs (e.g., `drillholes`): * **Anchor Position CRS (`EPSG:4979` — WGS 84 3D Geographic)**: * Official 3D CRS with coordinates: `[longitude, latitude, altitude]` (height in meters above ellipsoid). * Used in `geoReference.position` and 3D bounding calculations (as implemented in `drillholes`). * **Footprint Geometry CRS (`EPSG:4326` — WGS 84 2D Geographic)**: * Standard 2D `[longitude, latitude]` for the GeoJSON `geometry` (`Polygon` or `Point`). * Enables 2D spatial indexing and map queries in PostGIS. * **Model Coordinate Frame (`model_crs`)**: * Set in `geoReference.model_crs`: **`EPSG:3857`** (Web Mercator frame with height, centered at local origin) or **`EPSG:4978`** (WGS 84 ECEF). Model vertices are shifted around the anchor centroid. * **Reference Implementation**: * See `ocli.pro.aikp.drillholes` (`constants.WGS84_CRS = 'epsg:4979'`, `ai_makegeojson.py`, `make.py`). --- ## Coordinate Pipeline: Web Mercator, Centroid & Shift Model vertices are constructed in **Web Mercator (EPSG:3857 + height)**, centered at `(0, 0, 0)`, and anchored in **EPSG:4979**: ``` Source Data (Vector / Shapefile / 3D Points) │ ▼ [ 1. Web Mercator Projection ] ──► Vertices P = [X_3857, Y_3857, Z_height] │ ▼ [ 2. Calculate Centroid ] ──► C = [ (Xmin+Xmax)/2, (Ymin+Ymax)/2, (Zmin+Zmax)/2 ] │ ┌─────────────────────────┴─────────────────────────┐ ▼ ▼ [ 3. Shift Vertices (P - C) ] [ 4. Transform C (3857+Z -> 4979) ] │ │ P_local = [ P.x - C.x, P.y - C.y, P.z - C.z ] Anchor = [ lon, lat, altitude ] │ │ ▼ ▼ [ 5. GLTF Model Generation ] [ 6. GeoJSON Metadata Generation ] Model Centroid is (0, 0, 0) geoReference: { No 32-bit GPU FP jitter "position": Anchor, "model_crs": "EPSG:3857" (or 4978) } ``` 1. **Web Mercator Projection**: Vertices are converted to EPSG:3857 with metric height `Z` so distances/radii are in meters. 2. **Calculate Centroid (`C`)**: Center of the 3D bounding box is computed in EPSG:3857+height coordinates. 3. **Shift Vertices (`P - C`)**: All vertices are shifted relative to `C`, setting local model center to `(0, 0, 0)`. 4. **Anchor Projection**: Centroid `C` is transformed from `EPSG:3857` (with height) into **`EPSG:4979`** `[longitude, latitude, altitude]`. 5. **GeoJSON Attachment**: Anchor coordinates are written to `geoReference.position`, anchoring the local origin on the globe. 6. **Reference Implementation (`drillholes/preview/make.py`)**: ```python stack.to_crs(3857, inplace=True) centroid = self._centroid(self._bbox(stack.geometry)) line_start = np.array(line.coords[0]) - centroid line_end = np.array(line.coords[-1]) - centroid transformer = Transformer.from_crs(3857, 4979, always_xy=True) centroid_4979 = transformer.transform(centroid[0], centroid[1], centroid[2]) ``` --- ## Integrity & Checksum Verification (CRC) Ensuring binary asset integrity between pipeline output and cloud storage: * **CRC32 / Checksum Integrity**: * Pre-compute CRC32 checksum or SHA-256 hash prior to upload. * Store hash in GeoJSON metadata (`properties.metadata.checksum_crc32` or `sha256`). * **S3/COS ETag Verification**: * Verify payload against S3 ETag post-upload (`ai upload`). * **Protection against silent corruption**: * Guards against partial uploads, proxy truncations, or corrupted 3D model buffers. --- ## Scene & Node Hierarchy Conventions Internal glTF node hierarchy determines how parts are identified: ``` Scene Root ├── layer_terrain (Ground surface / foundation mesh) ├── layer_structures_main (Primary buildings / architectural elements) ├── layer_infrastructure_pipe (Utilities, pipelines, subterranean) └── layer_sensors_active (Instrument locations / sensor equipment) ``` * **Prefix Convention**: `layer__` * **Rules**: * Node names **must be unique** within the glTF scene graph. * Avoid DCC default names (e.g., `Cube.001`, `Mesh_Group`). * Group static meshes logically to minimize draw calls while maintaining layer toggleability. --- ## UI Layer Mapping (`properties.layers`) Connects internal glTF nodes to user-toggleable layers in the UI layer tree: ```json "layers": [ { "id": "terrain", "node_name": "layer_terrain", "name": "Ground Terrain", "visible": true, "opacity": 1.0, "category": "base" }, { "id": "structures", "node_name": "layer_structures_main", "name": "Primary Structures", "color": "#3b82f6", "visible": true, "opacity": 0.9, "category": "assets" } ] ``` --- ## GeoJSON Document Preparation (DB Metadata) ```json { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[4.891, 52.372], [4.896, 52.372], [4.896, 52.375], [4.891, 52.375], [4.891, 52.372]]] }, "properties": { "type": "Mesh", "friendly_name": "Industrial Facility 3D Model", "Bucket": "staging", "Endpoint": "https://s3.optoss.net", "ResultKey": "projects/site_a/facility_3d", "roles": ["public_read", "draft_read"], "source": { "url": "/s3/staging/projects/site_a/facility_3d/model.glb", "type": "Mesh" }, "geoReference": { "position": [4.8935, 52.3735, 12.5], "model_crs": "EPSG:4978" }, "metadata": { "crs": "EPSG:4979", "units": "meters", "crc32": "a4f108d2" }, "style": { "scale": 1.0 } } } ``` --- ## Step-by-Step Publishing Workflow ### 1. Generate & Validate Model Export your model to binary `.glb` with meter units, embedded textures, and named nodes. ### 2. Generate GeoJSON Metadata (`makegeojson`) ```bash ai makegeojson --friendly-name "Facility 3D Mesh" --cos-key "facility_3d" ``` Or execute custom `make_geojson.py` populating `properties.type = "Mesh"`, `source`, and `geoReference`. ### 3. Upload Artifacts to Cloud Object Storage (`upload`) ```bash ai upload ``` Uploads `model.glb` and the accompanying metadata `.geojson` to target S3/COS bucket. ### 4. Register Document in DB (`publish post`) ```bash publish post ``` --- ## Pre-Flight Quality Checklist Before running `publish post`, verify: * [ ] **Format**: File is valid `.glb` (self-contained, valid JSON header + binary chunk). * [ ] **Units**: Real-world scale (1 unit = 1 meter). * [ ] **Orientation**: glTF Y-up axis correctly oriented to local zenith. * [ ] **CRS**: `geoReference.position` contains accurate `[lon, lat, elevation]`. * [ ] **Integrity**: CRC32 or ETag verified against source file. * [ ] **Naming**: Internal glTF node names follow `layer__` convention. * [ ] **Metadata**: `properties.type` is `"Mesh"` and `source.url` follows `/s3//.glb`. * [ ] **Access Roles**: Appropriate access roles set (e.g. `["public_read"]`). --- # Summary 1. **Standardized format**: Single `.glb` binary file with meter scaling. 2. **Deterministic georeferencing**: WGS84 anchor with `EPSG:4978` model CRS. 3. **Structured layer mapping**: glTF nodes map directly to GeoJSON UI controls. 4. **Standard OCLI pipeline**: `makegeojson` $\to$ `upload` $\to$ `publish post`.