1212using MyBox ;
1313using Reflex . Attributes ;
1414using UnityEngine ;
15- using UnityEngine . Rendering ;
1615using ZenKit ;
1716using Logger = Gothic . Core . Logging . Logger ;
1817using Material = UnityEngine . Material ;
@@ -117,7 +116,7 @@ public void SetMdm(string mdmName)
117116
118117 if ( Mdm == null )
119118 {
120- Logger . LogError ( $ "MDH from name >{ mdmName } < for object >{ RootGo . name } < not found.", LogCat . Mesh ) ;
119+ Logger . LogError ( $ "MDM from name >{ mdmName } < for object >{ RootGo . name } < not found.", LogCat . Mesh ) ;
121120 }
122121 }
123122
@@ -142,7 +141,7 @@ public void SetMrm(string mrmName)
142141
143142 if ( Mrm == null )
144143 {
145- Logger . LogError ( $ "MDH from name >{ mrmName } < for object >{ RootGo . name } < not found.", LogCat . Mesh ) ;
144+ Logger . LogError ( $ "MRM from name >{ mrmName } < for object >{ RootGo . name } < not found.", LogCat . Mesh ) ;
146145 }
147146 }
148147
@@ -392,7 +391,7 @@ protected void PrepareMeshRenderer(Renderer rend, IMultiResolutionMesh mrmData)
392391 if ( materialData . Texture . IsEmpty ( ) ) // No texture to add.
393392 {
394393 Logger . LogWarning ( "No texture was set for: " + materialData . Name , LogCat . Mesh ) ;
395- return ;
394+ continue ;
396395 }
397396
398397 Texture texture ;
@@ -447,6 +446,9 @@ protected void PrepareMeshRenderer(Renderer rend, IMultiResolutionMesh mrmData)
447446 protected void PrepareMeshFilter ( MeshFilter meshFilter , IMultiResolutionMesh mrmData , Renderer meshRenderer , int meshIndex , List < System . Numerics . Vector3 > calculatedVertices = null )
448447 {
449448 // ISoftSkinMeshes will be prepared before reaching this method. This is due to NPC armors having dedicated offsets per item.
449+ // A non-null calculatedVertices is also our soft-skin signal: only that overload passes it, and its bone
450+ // weights are filled afterwards in the un-welded 3-per-triangle order (see the ISoftSkinMesh overload).
451+ var isSoftSkin = calculatedVertices != null ;
450452 calculatedVertices ??= mrmData . Positions ;
451453
452454 var subMeshPerTextureFormat = new Dictionary < TextureCacheService . TextureArrayTypes , int > ( ) ;
@@ -471,12 +473,22 @@ protected void PrepareMeshFilter(MeshFilter meshFilter, IMultiResolutionMesh mrm
471473
472474 int triangleCount = mrmData . SubMeshes . Sum ( i => i . Triangles . Count ) ;
473475 int vertexCount = triangleCount * 3 ;
474- int index = 0 ;
475476 var preparedVertices = new List < Vector3 > ( vertexCount ) ;
476477 var preparedUVs = new List < Vector4 > ( vertexCount ) ;
477478 var normals = new List < Vector3 > ( vertexCount ) ;
478479 var preparedTriangles = new List < List < int > > ( ) ;
479480
481+ // Weld identical corners to a single Unity vertex instead of emitting 3 fresh vertices per triangle.
482+ // This removes the ~3x vertex bloat (and the vertex-shader + bandwidth cost it carries; on Quest the
483+ // stationary lighting is computed per-vertex). Keying on the full (position, normal, uv) tuple is
484+ // visually lossless: hard edges and UV seams keep their own vertices, only true duplicates collapse.
485+ // Disabled for soft-skin (bone weights rely on the expanded order) and morph meshes (morph animation
486+ // maps source positions to specific Unity vertices).
487+ var weldVertices = ! isSoftSkin && Mmb == null ;
488+ var weldMap = weldVertices
489+ ? new Dictionary < ( Vector3 pos , Vector3 normal , Vector4 uv ) , int > ( vertexCount )
490+ : null ;
491+
480492 foreach ( var subMesh in mrmData . SubMeshes )
481493 {
482494 // When using the texture array, get the index of the array of the matching texture format. Build sub meshes for each texture format, i.e. separating opaque and alpha cutout textures.
@@ -498,33 +510,46 @@ protected void PrepareMeshFilter(MeshFilter meshFilter, IMultiResolutionMesh mrm
498510 preparedTriangles . Add ( new List < int > ( ) ) ;
499511 }
500512
501- for ( var i = 0 ; i < subMesh . Triangles . Count ; i ++ )
513+ // Determine which triangle list to use
514+ var triangleList = UseTextureArray
515+ ? preparedTriangles [ subMeshPerTextureFormat [ textureArrayType ] ]
516+ : preparedTriangles [ ^ 1 ] ;
517+
518+ void AddWedgeVertex ( MeshWedge wedge )
502519 {
503- // One triangle is made of 3 elements for Unity. We therefore need to prepare 3 elements within one loop.
504- MeshWedge [ ] wedges =
505- {
506- subMesh . Wedges [ subMesh . Triangles [ i ] . Wedge2 ] , subMesh . Wedges [ subMesh . Triangles [ i ] . Wedge1 ] ,
507- subMesh . Wedges [ subMesh . Triangles [ i ] . Wedge0 ]
508- } ;
520+ var rawPosition = calculatedVertices [ wedge . Index ] ;
521+ var position = new Vector3 ( rawPosition . X / 100f , rawPosition . Y / 100f , rawPosition . Z / 100f ) ;
522+ var normal = new Vector3 ( wedge . Normal . X , wedge . Normal . Y , wedge . Normal . Z ) ;
523+ var uv = new Vector4 ( wedge . Texture . X * textureScale . x , wedge . Texture . Y * textureScale . y ,
524+ textureArrayIndex , maxMipLevel ) ;
509525
510- for ( var w = 0 ; w < wedges . Length ; w ++ )
526+ if ( weldVertices && weldMap . TryGetValue ( ( position , normal , uv ) , out var existingIndex ) )
511527 {
512- preparedVertices . Add ( calculatedVertices [ wedges [ w ] . Index ] . ToUnityVector ( ) ) ;
513- if ( UseTextureArray )
514- {
515- preparedTriangles [ subMeshPerTextureFormat [ textureArrayType ] ] . Add ( index ++ ) ;
516- }
517- else
518- {
519- preparedTriangles [ preparedTriangles . Count - 1 ] . Add ( index ++ ) ;
520- }
521-
522- normals . Add ( wedges [ w ] . Normal . ToUnityVector ( ) ) ;
523- var uv = Vector2 . Scale ( textureScale , wedges [ w ] . Texture . ToUnityVector ( ) ) ;
524- preparedUVs . Add ( new Vector4 ( uv . x , uv . y , textureArrayIndex , maxMipLevel ) ) ;
525-
526- CreateMorphMeshEntry ( wedges [ w ] . Index , preparedVertices . Count ) ;
528+ triangleList . Add ( existingIndex ) ;
529+ return ;
527530 }
531+
532+ var vertexIndex = preparedVertices . Count ;
533+ preparedVertices . Add ( position ) ;
534+ normals . Add ( normal ) ;
535+ preparedUVs . Add ( uv ) ;
536+ triangleList . Add ( vertexIndex ) ;
537+
538+ if ( weldVertices )
539+ weldMap [ ( position , normal , uv ) ] = vertexIndex ;
540+ else
541+ CreateMorphMeshEntry ( wedge . Index , preparedVertices . Count ) ;
542+ }
543+
544+ var wedges = subMesh . Wedges ;
545+ var triangles = subMesh . Triangles ;
546+ for ( var ti = 0 ; ti < triangles . Count ; ti ++ )
547+ {
548+ var triangle = triangles [ ti ] ;
549+ // The wedge order is reversed to flip the triangle winding for Unity's coordinate system.
550+ AddWedgeVertex ( wedges [ triangle . Wedge2 ] ) ;
551+ AddWedgeVertex ( wedges [ triangle . Wedge1 ] ) ;
552+ AddWedgeVertex ( wedges [ triangle . Wedge0 ] ) ;
528553 }
529554 }
530555
@@ -543,6 +568,13 @@ protected void PrepareMeshFilter(MeshFilter meshFilter, IMultiResolutionMesh mrm
543568
544569 CreateMorphMeshEnd ( preparedVertices ) ;
545570
571+ // Reorder the (now welded) index/vertex buffers for post-transform vertex-cache locality. Only useful
572+ // once vertices are shared, and it reorders the vertex buffer - so it is strictly gated to welded meshes
573+ // (never morph: their external positionIndex->vertex mapping would be invalidated; never soft-skin).
574+ // Built once per unique mesh (cached by name), so the cost amortizes across all instances.
575+ if ( weldVertices )
576+ mesh . Optimize ( ) ;
577+
546578 _multiTypeCacheService . Meshes . Add ( $ "{ MeshName } _{ meshIndex } ", mesh ) ;
547579 }
548580
@@ -736,22 +768,18 @@ private Material GetDefaultMaterial(TextureCacheService.TextureArrayTypes textur
736768 {
737769 if ( UseTextureArray )
738770 {
739- Shader shader ;
740771 switch ( textureType )
741772 {
742773 case TextureCacheService . TextureArrayTypes . Opaque :
743- shader = Constants . ShaderWorldLit ;
744- break ;
774+ return new Material ( Constants . ShaderWorldLit ) ;
745775 case TextureCacheService . TextureArrayTypes . Transparent :
746776 // Cutout for e.g. bushes.
747- shader = Constants . ShaderLitAlphaToCoverage ;
748- break ;
777+ return new Material ( Constants . ShaderLitAlphaToCoverage ) ;
778+ case TextureCacheService . TextureArrayTypes . Water :
779+ return GetWaterMaterial ( ) ;
749780 default :
750781 throw new ArgumentOutOfRangeException ( nameof ( textureType ) , textureType , null ) ;
751782 }
752-
753- var material = new Material ( shader ) ;
754- return material ;
755783 }
756784 else
757785 {
@@ -761,10 +789,8 @@ private Material GetDefaultMaterial(TextureCacheService.TextureArrayTypes textur
761789
762790 protected Material GetWaterMaterial ( )
763791 {
764- var material = new Material ( Constants . ShaderWater ) ;
765- // Manually correct the render queue for alpha test, as Unity doesn't want to do it from the shader's render queue tag.
766- material . renderQueue = ( int ) RenderQueue . Transparent ;
767- return material ;
792+ // The render queue is defined by the water shader's "Queue" tag.
793+ return new Material ( Constants . ShaderWater ) ;
768794 }
769795
770796 protected void SetPosAndRot ( GameObject obj , Matrix4x4 matrix )
0 commit comments