55
66package org .citydb .vis .encoder .i3s ;
77
8+ import org .citydb .vis .encoder .TriangleRouter ;
9+ import org .citydb .vis .encoder .TriangleRouter .RoutedTriangle ;
810import org .citydb .vis .geometry .TriangleMesh ;
911import org .citydb .vis .geometry .VertexWelder ;
1012import org .citydb .vis .scene .BoundingVolume ;
@@ -152,17 +154,22 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
152154 return null ;
153155 }
154156
157+ // Format-neutral per-triangle routing facts (texture id, X3DMaterial-
158+ // colored flag, resolved style, face-range row) computed once and
159+ // shared with the 3D Tiles encoder. The I3S single-buffer / one-
160+ // material-per-node policy is applied on top of these facts below.
161+ List <RoutedTriangle > routed = TriangleRouter .route (mesh , weld , styleRegistry );
162+
155163 // Detect intra-feature mixed: textured node whose mesh still has
156164 // some triangles flagged untextured (texId < 0). The atlas builder
157165 // routes these via a reserved white-pixel sentinel region so they
158166 // ride through the textured material as flat white. Without
159167 // per-vertex bake the styling/default colour is dropped for those
160168 // triangles — the textured slot has no baseColorFactor.
161- List <Integer > triTexIds = hasTexCoords ? mesh .getTriangleTextureIds () : null ;
162169 boolean intraFeatureMixed = false ;
163170 if (hasTexCoords ) {
164- for (int ti : weld . validTriIndices () ) {
165- if (triTexIds . get ( ti ) < 0 ) {
171+ for (RoutedTriangle rt : routed ) {
172+ if (! rt . textured () ) {
166173 intraFeatureMixed = true ;
167174 break ;
168175 }
@@ -192,17 +199,16 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
192199 // Identity check `s != defaultStyle` works because
193200 // ObjectStyleRegistry's cache returns the same instance for every
194201 // type that resolves to the default style.
195- DefaultObjectStyle [] perTriangleStyle = null ;
196202 boolean anyTypeOverride = false ;
197203 boolean anyNonWhiteStyle = false ;
198204 boolean buildStyles = (!hasTexCoords && (styleRegistry .hasOverrides ()
199205 || (meshHasColors && styleRegistry .defaultStyle ().hasNonDefaultColor ())))
200206 || (hasTexCoords && intraFeatureMixed && (styleRegistry .hasOverrides ()
201207 || styleRegistry .defaultStyle ().hasNonDefaultColor ()));
202208 if (buildStyles ) {
203- perTriangleStyle = resolveTriangleStyles (weld , mesh , styleRegistry );
204209 DefaultObjectStyle defaultStyle = styleRegistry .defaultStyle ();
205- for (DefaultObjectStyle s : perTriangleStyle ) {
210+ for (RoutedTriangle rt : routed ) {
211+ DefaultObjectStyle s = rt .style ();
206212 if (s != defaultStyle ) {
207213 anyTypeOverride = true ;
208214 }
@@ -213,13 +219,13 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
213219 break ;
214220 }
215221 }
216- if (!anyNonWhiteStyle ) {
217- // Every triangle resolves to opaque white — no point
218- // paying the per-vertex COLOR_0 cost; fall back to
219- // WHITE_RGBA in the iterator.
220- perTriangleStyle = null ;
221- }
222222 }
223+ // Per-vertex COLOR_0 baking of resolved styles is worth its cost only
224+ // when the node carries a non-white style somewhere; otherwise every
225+ // triangle resolves to opaque white and the iterator falls back to
226+ // WHITE_RGBA. The per-triangle style itself is read straight off the
227+ // shared routing facts when baking.
228+ boolean bakeStyles = buildStyles && anyNonWhiteStyle ;
223229 // Styled-colored slot (NORMAL + COLOR_0 + alphaMode=blend) is used
224230 // only for pure-plain nodes that hit a per-type override. Mixed
225231 // nodes stay on the X3DMaterial colored slot 2/3 (one-material-per-
@@ -287,14 +293,11 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
287293 ? GeoTransform .EnuBasis .at (centerX , centerY ) : null ;
288294
289295 boolean [] anyAlphaBelowOne = new boolean [1 ];
290- DefaultObjectStyle [] styles = perTriangleStyle ;
291- List <Integer > validTriIndices = weld .validTriIndices ();
292- List <Integer > triTexIdsFinal = triTexIds ;
293296 VertexWelder .iterateOutputVertices (weld , mesh , (idx , weldedPos , srcIdx ) -> {
294297 outPositions [idx ] = weldedPos ;
298+ RoutedTriangle rt = routed .get (idx / 3 );
295299 if (outNormals != null ) {
296- int normalTriIdx = validTriIndices .get (idx / 3 );
297- boolean trulyTextured = hasTexCoords && triTexIdsFinal .get (normalTriIdx ) >= 0 ;
300+ boolean trulyTextured = hasTexCoords && rt .textured ();
298301 if (trulyTextured ) {
299302 enu .fillUpInEcef (outNormals [idx ]);
300303 } else {
@@ -303,12 +306,10 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
303306 }
304307 outUVs [idx ] = hasTexCoords ? mesh .getTexCoords ().get (srcIdx ) : ZERO_UV ;
305308 if (outColors != null ) {
306- int triLocalIdx = idx / 3 ;
307- int srcTriIdx = validTriIndices .get (triLocalIdx );
308- boolean whitePixelTri = hasTexCoords && triTexIdsFinal .get (srcTriIdx ) < 0 ;
309- boolean x3dColored = mesh .isTriangleColored (srcTriIdx );
309+ boolean whitePixelTri = hasTexCoords && !rt .textured ();
310+ boolean x3dColored = rt .colored ();
310311 float [] c ;
311- if (whitePixelTri && !x3dColored && styles != null ) {
312+ if (whitePixelTri && !x3dColored && bakeStyles ) {
312313 // Intra-feature mixed: this triangle has no real texture
313314 // (atlas builder pointed its UVs at the white-pixel
314315 // sentinel). Bake the resolved styling / default colour
@@ -317,7 +318,7 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
317318 // Skipped when the triangle is also X3D-authored: the
318319 // next branch returns the authored RGBA, which wins
319320 // over styling per X3DMaterial precedence.
320- c = styles [ triLocalIdx ] .color ();
321+ c = rt . style () .color ();
321322 } else if (meshHasColors && (hasTexCoords || x3dColored )) {
322323 // mesh.getColors() returns the authored X3DMaterial RGBA
323324 // for X3D-authored vertices and WHITE_RGBA padding (set
@@ -329,7 +330,7 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
329330 // X3D-authored triangles enter this branch — non-X3D
330331 // triangles fall through to styling or default white.
331332 c = mesh .getColors ().get (srcIdx );
332- } else if (!hasTexCoords && styles != null ) {
333+ } else if (!hasTexCoords && bakeStyles ) {
333334 // Non-textured + non-X3D triangle on a styled node:
334335 // bake the resolved style colour. Source is the
335336 // matching --feature-type-style override when present,
@@ -339,7 +340,7 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
339340 // textured triangle on a textured node — only the
340341 // dedicated white-pixel branch above applies styling
341342 // through a textured material.
342- c = styles [ triLocalIdx ] .color ();
343+ c = rt . style () .color ();
343344 } else {
344345 c = WHITE_RGBA ;
345346 }
@@ -379,24 +380,6 @@ public NodeGeometryResult writeNodeGeometry(Path layerDir, SceneNode node,
379380 return new NodeGeometryResult (rangeFeatureIds , featureAabbs );
380381 }
381382
382- /**
383- * Resolve each valid triangle's source surface type to a
384- * {@link DefaultObjectStyle} via the registry. Returned array is
385- * aligned with {@link VertexWelder.WeldResult#validTriIndices()} —
386- * index {@code i / 3} on a per-vertex iteration locates the style for
387- * the triangle owning vertex {@code i}.
388- */
389- private static DefaultObjectStyle [] resolveTriangleStyles (VertexWelder .WeldResult weld ,
390- TriangleMesh mesh ,
391- ObjectStyleRegistry registry ) {
392- List <Integer > validTriIndices = weld .validTriIndices ();
393- DefaultObjectStyle [] styles = new DefaultObjectStyle [validTriIndices .size ()];
394- for (int i = 0 ; i < validTriIndices .size (); i ++) {
395- styles [i ] = registry .resolve (mesh .getTriangleSurfaceType (validTriIndices .get (i )));
396- }
397- return styles ;
398- }
399-
400383 /**
401384 * Compute per-feature AABBs from welded (center-relative) positions,
402385 * converted back to absolute geographic coordinates. Returns a list
0 commit comments