mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-29 21:02:59 +00:00 
			
		
		
		
	Improve vertex transformation system
This migrates TurtleMultiModel's current vertex transformation system into something more powerful and "correct". Namely, it has the following improvements: - Handles all position formats (float, byte, etc...) - Correctly translates normals of quads - Reorders faces if the winding order is reversed
This commit is contained in:
		| @@ -0,0 +1,264 @@ | ||||
| package dan200.computercraft.client.render; | ||||
|  | ||||
| import net.minecraft.client.renderer.block.model.BakedQuad; | ||||
| import net.minecraft.client.renderer.texture.TextureAtlasSprite; | ||||
| import net.minecraft.client.renderer.vertex.VertexFormat; | ||||
| import net.minecraft.util.EnumFacing; | ||||
| import net.minecraftforge.client.model.pipeline.IVertexConsumer; | ||||
| import net.minecraftforge.client.model.pipeline.LightUtil; | ||||
| import net.minecraftforge.client.model.pipeline.VertexTransformer; | ||||
| import net.minecraftforge.common.model.TRSRTransformation; | ||||
|  | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.vecmath.Matrix4f; | ||||
| import javax.vecmath.Point3f; | ||||
| import javax.vecmath.Vector3f; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * Transforms vertices of a model, remaining aware of winding order, and rearranging | ||||
|  * vertices if needed. | ||||
|  */ | ||||
| public final class ModelTransformer | ||||
| { | ||||
|     private static final Matrix4f identity; | ||||
|  | ||||
|     static | ||||
|     { | ||||
|         identity = new Matrix4f(); | ||||
|         identity.setIdentity(); | ||||
|     } | ||||
|  | ||||
|     private ModelTransformer() | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public static void transformQuadsTo( List<BakedQuad> output, List<BakedQuad> input, Matrix4f transform ) | ||||
|     { | ||||
|         if( transform == null || transform.equals( identity ) ) | ||||
|         { | ||||
|             output.addAll( input ); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Matrix4f normalMatrix = new Matrix4f( transform ); | ||||
|             normalMatrix.invert(); | ||||
|             normalMatrix.transpose(); | ||||
|  | ||||
|             for( BakedQuad quad : input ) output.add( doTransformQuad( quad, transform, normalMatrix ) ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static BakedQuad transformQuad( BakedQuad input, Matrix4f transform ) | ||||
|     { | ||||
|         if( transform == null || transform.equals( identity ) ) return input; | ||||
|  | ||||
|         Matrix4f normalMatrix = new Matrix4f( transform ); | ||||
|         normalMatrix.invert(); | ||||
|         normalMatrix.transpose(); | ||||
|         return doTransformQuad( input, transform, normalMatrix ); | ||||
|     } | ||||
|  | ||||
|     private static BakedQuad doTransformQuad( BakedQuad input, Matrix4f positionMatrix, Matrix4f normalMatrix ) | ||||
|     { | ||||
|  | ||||
|         BakedQuadBuilder builder = new BakedQuadBuilder( input.getFormat() ); | ||||
|         NormalAwareTransformer transformer = new NormalAwareTransformer( builder, positionMatrix, normalMatrix ); | ||||
|         input.pipe( transformer ); | ||||
|  | ||||
|         if( transformer.areNormalsInverted() ) | ||||
|         { | ||||
|             builder.swap( 1, 3 ); | ||||
|             transformer.areNormalsInverted(); | ||||
|         } | ||||
|  | ||||
|         return builder.build(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A vertex transformer that tracks whether the normals have been inverted and so the vertices | ||||
|      * should be reordered so backface culling works as expected. | ||||
|      */ | ||||
|     private static class NormalAwareTransformer extends VertexTransformer | ||||
|     { | ||||
|         private final Matrix4f positionMatrix; | ||||
|         private final Matrix4f normalMatrix; | ||||
|  | ||||
|         private int vertexIndex = 0, elementIndex = 0; | ||||
|         private final Point3f[] before = new Point3f[ 4 ]; | ||||
|         private final Point3f[] after = new Point3f[ 4 ]; | ||||
|  | ||||
|         public NormalAwareTransformer( IVertexConsumer parent, Matrix4f positionMatrix, Matrix4f normalMatrix ) | ||||
|         { | ||||
|             super( parent ); | ||||
|             this.positionMatrix = positionMatrix; | ||||
|             this.normalMatrix = normalMatrix; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void setQuadOrientation( EnumFacing orientation ) | ||||
|         { | ||||
|             super.setQuadOrientation( orientation == null ? orientation : TRSRTransformation.rotate( positionMatrix, orientation ) ); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void put( int element, @Nonnull float... data ) | ||||
|         { | ||||
|             switch( getVertexFormat().getElement( element ).getUsage() ) | ||||
|             { | ||||
|                 case POSITION: | ||||
|                 { | ||||
|                     Point3f vec = new Point3f( data ); | ||||
|                     Point3f newVec = new Point3f(); | ||||
|                     positionMatrix.transform( vec, newVec ); | ||||
|  | ||||
|                     float[] newData = new float[ 4 ]; | ||||
|                     newVec.get( newData ); | ||||
|                     super.put( element, newData ); | ||||
|  | ||||
|  | ||||
|                     before[ vertexIndex ] = vec; | ||||
|                     after[ vertexIndex ] = newVec; | ||||
|                     break; | ||||
|                 } | ||||
|                 case NORMAL: | ||||
|                 { | ||||
|                     Vector3f vec = new Vector3f( data ); | ||||
|                     normalMatrix.transform( vec ); | ||||
|  | ||||
|                     float[] newData = new float[ 4 ]; | ||||
|                     vec.get( newData ); | ||||
|                     super.put( element, newData ); | ||||
|                     break; | ||||
|                 } | ||||
|                 default: | ||||
|                     super.put( element, data ); | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
|             elementIndex++; | ||||
|             if( elementIndex == getVertexFormat().getElementCount() ) | ||||
|             { | ||||
|                 vertexIndex++; | ||||
|                 elementIndex = 0; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public boolean areNormalsInverted() | ||||
|         { | ||||
|             Vector3f temp1 = new Vector3f(), temp2 = new Vector3f(); | ||||
|             Vector3f crossBefore = new Vector3f(), crossAfter = new Vector3f(); | ||||
|  | ||||
|             // Determine what cross product we expect to have | ||||
|             temp1.sub( before[ 1 ], before[ 0 ] ); | ||||
|             temp2.sub( before[ 1 ], before[ 2 ] ); | ||||
|             crossBefore.cross( temp1, temp2 ); | ||||
|             normalMatrix.transform( crossBefore ); | ||||
|  | ||||
|             // And determine what cross product we actually have | ||||
|             temp1.sub( after[ 1 ], after[ 0 ] ); | ||||
|             temp2.sub( after[ 1 ], after[ 2 ] ); | ||||
|             crossAfter.cross( temp1, temp2 ); | ||||
|  | ||||
|             // If the angle between expected and actual cross product is greater than  | ||||
|             // pi/2 radians then we will need to reorder our quads. | ||||
|             return Math.abs( crossBefore.angle( crossAfter ) ) >= Math.PI / 2; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A vertex consumer which is capable of building {@link BakedQuad}s. | ||||
|      * | ||||
|      * Equivalent to {@link net.minecraftforge.client.model.pipeline.UnpackedBakedQuad.Builder} but more memory | ||||
|      * efficient. | ||||
|      * | ||||
|      * This also provides the ability to swap vertices through {@link #swap(int, int)} to allow reordering. | ||||
|      */ | ||||
|     private static class BakedQuadBuilder implements IVertexConsumer | ||||
|     { | ||||
|         private final VertexFormat format; | ||||
|  | ||||
|         private final int[] vertexData; | ||||
|         private int vertexIndex = 0, elementIndex = 0; | ||||
|  | ||||
|         private EnumFacing orientation; | ||||
|         private int quadTint; | ||||
|         private boolean diffuse; | ||||
|         private TextureAtlasSprite texture; | ||||
|  | ||||
|         private BakedQuadBuilder( VertexFormat format ) | ||||
|         { | ||||
|             this.format = format; | ||||
|             this.vertexData = new int[ format.getNextOffset() ]; | ||||
|         } | ||||
|  | ||||
|         @Nonnull | ||||
|         @Override | ||||
|         public VertexFormat getVertexFormat() | ||||
|         { | ||||
|             return format; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void setQuadTint( int tint ) | ||||
|         { | ||||
|             this.quadTint = tint; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void setQuadOrientation( @Nonnull EnumFacing orientation ) | ||||
|         { | ||||
|             this.orientation = orientation; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void setApplyDiffuseLighting( boolean diffuse ) | ||||
|         { | ||||
|             this.diffuse = diffuse; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void setTexture( @Nonnull TextureAtlasSprite texture ) | ||||
|         { | ||||
|             this.texture = texture; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void put( int element, @Nonnull float... data ) | ||||
|         { | ||||
|             LightUtil.pack( data, vertexData, format, vertexIndex, element ); | ||||
|  | ||||
|             elementIndex++; | ||||
|             if( elementIndex == getVertexFormat().getElementCount() ) | ||||
|             { | ||||
|                 vertexIndex++; | ||||
|                 elementIndex = 0; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public void swap( int a, int b ) | ||||
|         { | ||||
|             int length = vertexData.length / 4; | ||||
|             for( int i = 0; i < length; i++ ) | ||||
|             { | ||||
|                 int temp = vertexData[ a * length + i ]; | ||||
|                 vertexData[ a * length + i ] = vertexData[ b * length + i ]; | ||||
|                 vertexData[ b * length + i ] = temp; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public BakedQuad build() | ||||
|         { | ||||
|             if( elementIndex != 0 || vertexIndex != 4 ) | ||||
|             { | ||||
|                 throw new IllegalStateException( "Got an unexpected number of elements/vertices" ); | ||||
|             } | ||||
|             if( texture == null ) | ||||
|             { | ||||
|                 throw new IllegalStateException( "Texture has not been set" ); | ||||
|             } | ||||
|  | ||||
|             return new BakedQuad( vertexData, quadTint, orientation, texture, diffuse, format ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -6,13 +6,10 @@ import net.minecraft.client.renderer.block.model.IBakedModel; | ||||
| import net.minecraft.client.renderer.block.model.ItemCameraTransforms; | ||||
| import net.minecraft.client.renderer.block.model.ItemOverrideList; | ||||
| import net.minecraft.client.renderer.texture.TextureAtlasSprite; | ||||
| import net.minecraft.client.renderer.vertex.VertexFormat; | ||||
| import net.minecraft.client.renderer.vertex.VertexFormatElement; | ||||
| import net.minecraft.util.EnumFacing; | ||||
|  | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.vecmath.Matrix4f; | ||||
| import javax.vecmath.Point3f; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| @@ -48,29 +45,17 @@ public class TurtleMultiModel implements IBakedModel | ||||
|     { | ||||
|         if( side != null ) | ||||
|         { | ||||
|             if( !m_faceQuads.containsKey( side ) ) | ||||
|             { | ||||
|                 ArrayList<BakedQuad> quads = new ArrayList<>(); | ||||
|                 if( m_overlayModel != null ) | ||||
|                 { | ||||
|                     quads.addAll( m_overlayModel.getQuads( state, side, rand ) ); | ||||
|                 } | ||||
|                 if( m_leftUpgradeModel != null ) | ||||
|                 { | ||||
|                     quads.addAll( transformQuads( m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ) ); | ||||
|                 } | ||||
|                 if( m_rightUpgradeModel != null ) | ||||
|                 { | ||||
|                     quads.addAll( transformQuads( m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ) ); | ||||
|                 } | ||||
|                 quads.trimToSize(); | ||||
|                 m_faceQuads.put( side, quads ); | ||||
|             } | ||||
|             if( !m_faceQuads.containsKey( side ) ) m_faceQuads.put( side, buildQuads( state, side, rand ) ); | ||||
|             return m_faceQuads.get( side ); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if( m_generalQuads == null ) | ||||
|             if( m_generalQuads == null ) m_generalQuads = buildQuads( state, side, rand ); | ||||
|             return m_generalQuads; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private List<BakedQuad> buildQuads( IBlockState state, EnumFacing side, long rand ) | ||||
|     { | ||||
|         ArrayList<BakedQuad> quads = new ArrayList<>(); | ||||
|         quads.addAll( m_baseModel.getQuads( state, side, rand ) ); | ||||
| @@ -80,17 +65,14 @@ public class TurtleMultiModel implements IBakedModel | ||||
|         } | ||||
|         if( m_leftUpgradeModel != null ) | ||||
|         { | ||||
|                     quads.addAll( transformQuads( m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ) ); | ||||
|             ModelTransformer.transformQuadsTo( quads, m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ); | ||||
|         } | ||||
|         if( m_rightUpgradeModel != null ) | ||||
|         { | ||||
|                     quads.addAll( transformQuads( m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ) ); | ||||
|             ModelTransformer.transformQuadsTo( quads, m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ); | ||||
|         } | ||||
|         quads.trimToSize(); | ||||
|                 m_generalQuads = quads; | ||||
|             } | ||||
|             return m_generalQuads; | ||||
|         } | ||||
|         return quads; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -132,63 +114,4 @@ public class TurtleMultiModel implements IBakedModel | ||||
|     { | ||||
|         return ItemOverrideList.NONE; | ||||
|     } | ||||
|  | ||||
|     private List<BakedQuad> transformQuads( List<BakedQuad> input, Matrix4f transform ) | ||||
|     { | ||||
|         if( transform == null || input.size() == 0 ) | ||||
|         { | ||||
|             return input; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             List<BakedQuad> output = new ArrayList<>( input.size() ); | ||||
|             for( BakedQuad quad : input ) | ||||
|             { | ||||
|                 output.add( transformQuad( quad, transform ) ); | ||||
|             } | ||||
|             return output; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private BakedQuad transformQuad( BakedQuad quad, Matrix4f transform ) | ||||
|     { | ||||
|         int[] vertexData = quad.getVertexData().clone(); | ||||
|         int offset = 0; | ||||
|         BakedQuad copy = new BakedQuad( vertexData, -1, quad.getFace(), quad.getSprite(), quad.shouldApplyDiffuseLighting(), quad.getFormat() ); | ||||
|         VertexFormat format = copy.getFormat(); | ||||
|         for( int i=0; i<format.getElementCount(); ++i ) // For each vertex element | ||||
|         { | ||||
|             VertexFormatElement element = format.getElement( i ); | ||||
|             if( element.isPositionElement() && | ||||
|                 element.getType() == VertexFormatElement.EnumType.FLOAT && | ||||
|                 element.getElementCount() == 3 ) // When we find a position element | ||||
|             { | ||||
|                 for( int j=0; j<4; ++j ) // For each corner of the quad | ||||
|                 { | ||||
|                     int start = offset + j * format.getNextOffset(); | ||||
|                     if( (start % 4) == 0 ) | ||||
|                     { | ||||
|                         start = start / 4; | ||||
|  | ||||
|                         // Extract the position | ||||
|                         Point3f pos = new Point3f( | ||||
|                             Float.intBitsToFloat( vertexData[ start ] ), | ||||
|                             Float.intBitsToFloat( vertexData[ start + 1 ] ), | ||||
|                             Float.intBitsToFloat( vertexData[ start + 2 ] ) | ||||
|                         ); | ||||
|  | ||||
|                         // Transform the position | ||||
|                         transform.transform( pos ); | ||||
|  | ||||
|                         // Insert the position | ||||
|                         vertexData[ start ] = Float.floatToRawIntBits( pos.x ); | ||||
|                         vertexData[ start + 1 ] = Float.floatToRawIntBits( pos.y ); | ||||
|                         vertexData[ start + 2 ] = Float.floatToRawIntBits( pos.z ); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             offset += element.getSize(); | ||||
|         } | ||||
|         return copy; | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev