#!/usr/bin/env bash # End-to-end helper to convert CityGML tiles to tile-local GLBs. # Assumes heightmaps/textures are already exported so tile_index.csv exists. set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" RAW_DIR="${RAW_DIR:-$ROOT/raw/citygml/lod2}" CITYJSON_DIR="${CITYJSON_DIR:-$ROOT/work/cityjson}" CLEAN_DIR="${CLEAN_DIR:-$ROOT/work/cityjson_clean}" TRI_DIR="${TRI_DIR:-$ROOT/work/cityjson_tri}" TRI_LOCAL_DIR="${TRI_LOCAL_DIR:-$ROOT/work/cityjson_tri_local}" SPLIT_DIR="${SPLIT_DIR:-$ROOT/work/cityjson_split_local}" GLB_DIR="${GLB_DIR:-$ROOT/export_unity/buildings_glb}" GLB_SPLIT_DIR="${GLB_SPLIT_DIR:-$ROOT/export_unity/buildings_glb_split}" TILE_INDEX="${TILE_INDEX:-$ROOT/export_unity/tile_index.csv}" CITYGML_TOOLS="${CITYGML_TOOLS:-$ROOT/tools/citygml-tools-2.4.0/citygml-tools}" CJIO_CMD="${CJIO:-uv run cjio}" PYTHON_CMD="${PYTHON_CMD:-uv run python}" log() { printf '[citygml->glb] %s\n' "$*" } resolve_cityjson() { local path="$1" if [[ -f "$path" ]]; then echo "$path" return 0 fi if [[ -d "$path" ]]; then local base base="$(basename "$path" .city.json)" if [[ -f "$path/$base.json" ]]; then echo "$path/$base.json" return 0 fi local first first="$(find "$path" -maxdepth 1 -name '*.json' | head -n1 || true)" if [[ -n "$first" ]]; then echo "$first" return 0 fi fi return 1 } if [[ ! -f "$TILE_INDEX" ]]; then echo "tile_index.csv missing at $TILE_INDEX; run heightmap export first." >&2 exit 1 fi mapfile -t GMLS < <(find "$RAW_DIR" -maxdepth 1 -name 'LoD2_*.gml' | sort) if [[ ${#GMLS[@]} -eq 0 ]]; then echo "No CityGML tiles found in $RAW_DIR" >&2 exit 1 fi mkdir -p "$CITYJSON_DIR" "$CLEAN_DIR" "$TRI_DIR" "$TRI_LOCAL_DIR" "$SPLIT_DIR" "$GLB_DIR" "$GLB_SPLIT_DIR" log "CityGML -> CityJSON (${#GMLS[@]} tiles)" for gml in "${GMLS[@]}"; do base="$(basename "$gml" .gml)" out="$CITYJSON_DIR/${base}.city.json" if [[ -e "$out" ]]; then log "skip existing $out" continue fi "$CITYGML_TOOLS" to-cityjson "$gml" -o "$out" done log "Clean invalid vertices" $PYTHON_CMD "$ROOT/scripts/clean_cityjson_vertices.py" --input-dir "$CITYJSON_DIR" --output-dir "$CLEAN_DIR" log "Triangulate" mapfile -t CITYJSONS < <(find "$CLEAN_DIR" -maxdepth 1 -name '*.city.json' | sort) for path in "${CITYJSONS[@]}"; do base="$(basename "$path" .city.json)" target="$TRI_DIR/${base}.tri.city.json" if [[ -e "$target" ]]; then log "skip existing $target" continue fi input_json="$(resolve_cityjson "$path" || true)" if [[ -z "$input_json" ]]; then log "skip $path (cannot resolve json)" continue fi $CJIO_CMD --ignore_duplicate_keys "$input_json" upgrade triangulate vertices_clean save "$target" done log "Rebase to tile-local XY" $PYTHON_CMD "$ROOT/scripts/rebase_cityjson_tiles.py" --input-dir "$TRI_DIR" --output-dir "$TRI_LOCAL_DIR" --tile-index "$TILE_INDEX" log "Split semantics (roof/wall)" $PYTHON_CMD "$ROOT/scripts/split_semantics.py" --input-dir "$TRI_LOCAL_DIR" --output-dir "$SPLIT_DIR" log "Export GLB (base)" mapfile -t TRIS < <(find "$TRI_LOCAL_DIR" -maxdepth 1 -name '*.tri.city.json' | sort) for tri in "${TRIS[@]}"; do base="$(basename "$tri" .tri.city.json)" out="$GLB_DIR/${base}.glb" if [[ -e "$out" ]]; then log "skip existing $out" continue fi $CJIO_CMD "$tri" export glb "$out" done log "Export GLB (roof/wall)" mapfile -t ROOFS < <(find "$SPLIT_DIR" -maxdepth 1 -name '*.roof.city.json' | sort) for roof in "${ROOFS[@]}"; do base="$(basename "$roof" .roof.city.json)" out="$GLB_SPLIT_DIR/${base}_roof.glb" if [[ -e "$out" ]]; then log "skip existing $out" continue fi $CJIO_CMD "$roof" export glb "$out" done mapfile -t WALLS < <(find "$SPLIT_DIR" -maxdepth 1 -name '*.wall.city.json' | sort) for wall in "${WALLS[@]}"; do base="$(basename "$wall" .wall.city.json)" out="$GLB_SPLIT_DIR/${base}_wall.glb" if [[ -e "$out" ]]; then log "skip existing $out" continue fi $CJIO_CMD "$wall" export glb "$out" done log "Verify outputs" $PYTHON_CMD "$ROOT/scripts/verify_pipeline.py" --mode both --tri-dir "$TRI_LOCAL_DIR" --split-dir "$SPLIT_DIR" log "Done"