@@ -69,7 +69,15 @@ export class Scene {
6969 add ( body , element , addUniverse = true ) {
7070 this . scene . add ( body ) ;
7171 if ( element ) {
72- this . elements . push ( element ) ;
72+ // Prevent duplicate entries in elements array (same element instance
73+ // or same body UUID). Duplicates can occur from double-renders, HMR,
74+ // or race conditions between Importer and ensure* methods.
75+ const alreadyExists = this . elements . some (
76+ e => e === element || ( e . hasBody ( ) && e . getBody ( ) . uuid === body . uuid ) ,
77+ ) ;
78+ if ( ! alreadyExists ) {
79+ this . elements . push ( element ) ;
80+ }
7381 }
7482
7583 if ( addUniverse ) {
@@ -82,14 +90,24 @@ export class Scene {
8290 // Only include camera in hierarchy if it's serializable (scene camera is not)
8391 const cameraChildren =
8492 camera && camera . isSerializable ( ) ? [ camera . getHierarchy ( options ) ] : [ ] ;
93+
94+ // Deduplicate elements by UUID to prevent showing the same element twice
95+ // in the hierarchy (can happen from race conditions or double-adds)
96+ const seenUUIDs = new Set ( ) ;
97+ const deduplicatedElements = this . elements . filter ( e => {
98+ if ( ! e . hasBody ( ) ) return false ;
99+ const uuid = e . getBody ( ) . uuid ;
100+ if ( seenUUIDs . has ( uuid ) ) return false ;
101+ seenUUIDs . add ( uuid ) ;
102+ return ! e . hasParent ( ) && ! e . isHelper ( ) && e . isSerializable ( ) ;
103+ } ) ;
104+
85105 return [
86106 {
87107 element : this . toJSON ( options . parseJSON ) ,
88108 children : [
89109 ...cameraChildren ,
90- ...this . elements
91- . filter ( e => ! e . hasParent ( ) && ! e . isHelper ( ) && e . isSerializable ( ) )
92- . map ( e => e . getHierarchy ( options ) ) ,
110+ ...deduplicatedElements . map ( e => e . getHierarchy ( options ) ) ,
93111 ] ,
94112 } ,
95113 ] ;
0 commit comments