@@ -93,9 +93,12 @@ export interface ViewerOptions {
9393 clips ?: Record < string , string > ;
9494 /**
9595 * Keep the procedural figure visible while a skinned `characterUrl` loads.
96- * This viewer always shows the procedural figure during load (and on load
97- * failure), so the flag is accepted for API compatibility; the default
98- * behavior already matches `true`.
96+ * Defaults to `true` (the procedural figure poses the scene during the load,
97+ * matching callers that never set this). Set `false` alongside a
98+ * `characterUrl` to hide the procedural meshes until the character resolves —
99+ * so a page load shows the skinned figure or nothing, never a blink of the
100+ * crude procedural figure. On load failure the procedural figure is revealed
101+ * regardless, so the scene never stays blank.
99102 */
100103 showProceduralWhileLoading ?: boolean ;
101104}
@@ -182,10 +185,20 @@ export function createViewer(
182185 enableShadows ( mannequin . root ) ;
183186 scene . add ( mannequin . root ) ;
184187
188+ // When a skinned character is requested and the caller opted out of the
189+ // procedural fallback during load, hide the procedural meshes up front so the
190+ // crude figure never flashes for the character's fetch time on a page load.
191+ // The skeleton still drives animation and grounding; only the meshes hide
192+ // (same as the post-load swap). Revealed again if the character fails to load.
193+ const deferProceduralMeshes =
194+ Boolean ( opts . characterUrl ) && opts . showProceduralWhileLoading === false ;
195+ if ( deferProceduralMeshes ) setMeshVisibility ( mannequin . root , false ) ;
196+
185197 // Skinned character layer (optional). While loading (and on failure) the
186- // procedural figure stays; once ready, the driver skeleton is rebuilt with
187- // the character's proportions, its meshes are hidden (they keep feeding the
188- // bounding-box grounding), and the character mirrors it every frame.
198+ // procedural figure stays — unless deferred above; once ready, the driver
199+ // skeleton is rebuilt with the character's proportions, its meshes are hidden
200+ // (they keep feeding the bounding-box grounding), and the character mirrors it
201+ // every frame.
189202 let character : Character | null = null ;
190203
191204 // Mocap-clip layer (optional, character-only). When the loaded document
@@ -843,9 +856,7 @@ export function createViewer(
843856 scene . remove ( mannequin . root ) ;
844857 disposeTree ( mannequin . root ) ;
845858 mannequin = buildMannequin ( undefined , char . proportions ) ;
846- mannequin . root . traverse ( ( obj ) => {
847- if ( ( obj as THREE . Mesh ) . isMesh ) obj . visible = false ;
848- } ) ;
859+ setMeshVisibility ( mannequin . root , false ) ;
849860 scene . add ( mannequin . root ) ;
850861 scene . add ( char . group ) ;
851862 character = char ;
@@ -857,14 +868,27 @@ export function createViewer(
857868 else char . sync ( mannequin ) ;
858869 } )
859870 . catch ( ( ) => {
860- // Keep the procedural figure. Deliberately silent: an offline embed
861- // or a blocked CDN should degrade, not error.
871+ // Character failed (offline embed, blocked/404 CDN): reveal the
872+ // procedural figure we may have hidden, so the scene degrades to the
873+ // working fallback instead of staying blank. Deliberately silent.
874+ if ( deferProceduralMeshes ) setMeshVisibility ( mannequin . root , true ) ;
862875 } ) ;
863876 }
864877
865878 return api ;
866879}
867880
881+ /**
882+ * Show or hide a figure's meshes. The skeleton keeps driving animation and
883+ * bounding-box grounding regardless, so hiding only the meshes lets a hidden
884+ * procedural figure still pose the scene while its character stand-in loads.
885+ */
886+ function setMeshVisibility ( root : THREE . Object3D , visible : boolean ) : void {
887+ root . traverse ( ( obj ) => {
888+ if ( ( obj as THREE . Mesh ) . isMesh ) obj . visible = visible ;
889+ } ) ;
890+ }
891+
868892/** True for a finger bone id (thumb/index/middle/ring/pinky_left|right). */
869893function isFingerId ( id : string ) : boolean {
870894 return / ^ ( t h u m b | i n d e x | m i d d l e | r i n g | p i n k y ) _ / . test ( id ) ;
0 commit comments