From ce7923d2486adb67d275bf805762895a3137fbd8 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Tue, 21 Nov 2017 00:18:03 +0000 Subject: [PATCH 1/2] 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 --- .../client/render/ModelTransformer.java | 264 ++++++++++++++++++ .../client/render/TurtleMultiModel.java | 121 ++------ 2 files changed, 286 insertions(+), 99 deletions(-) create mode 100644 src/main/java/dan200/computercraft/client/render/ModelTransformer.java diff --git a/src/main/java/dan200/computercraft/client/render/ModelTransformer.java b/src/main/java/dan200/computercraft/client/render/ModelTransformer.java new file mode 100644 index 000000000..19653bfb2 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/render/ModelTransformer.java @@ -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 output, List 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 ); + } + } +} diff --git a/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java b/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java index 0d105be9a..205c2e9fe 100644 --- a/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java +++ b/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java @@ -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,51 +45,36 @@ public class TurtleMultiModel implements IBakedModel { if( side != null ) { - if( !m_faceQuads.containsKey( side ) ) - { - ArrayList 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 ) - { - ArrayList quads = new ArrayList<>(); - quads.addAll( m_baseModel.getQuads( state, side, rand ) ); - 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_generalQuads = quads; - } + if( m_generalQuads == null ) m_generalQuads = buildQuads( state, side, rand ); return m_generalQuads; } } + private List buildQuads( IBlockState state, EnumFacing side, long rand ) + { + ArrayList quads = new ArrayList<>(); + quads.addAll( m_baseModel.getQuads( state, side, rand ) ); + if( m_overlayModel != null ) + { + quads.addAll( m_overlayModel.getQuads( state, side, rand ) ); + } + if( m_leftUpgradeModel != null ) + { + ModelTransformer.transformQuadsTo( quads, m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ); + } + if( m_rightUpgradeModel != null ) + { + ModelTransformer.transformQuadsTo( quads, m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ); + } + quads.trimToSize(); + return quads; + } + @Override public boolean isAmbientOcclusion() { @@ -132,63 +114,4 @@ public class TurtleMultiModel implements IBakedModel { return ItemOverrideList.NONE; } - - private List transformQuads( List input, Matrix4f transform ) - { - if( transform == null || input.size() == 0 ) - { - return input; - } - else - { - List 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 Date: Tue, 21 Nov 2017 00:34:35 +0000 Subject: [PATCH 2/2] Improve turtles by 200% Every other mod has some fun feature, so should we. And yes, this was worth the 400 lines it took to implement. --- .../render/TileEntityTurtleRenderer.java | 16 +++--- .../client/render/TurtleMultiModel.java | 40 ++++++++++---- .../client/render/TurtleSmartItemModel.java | 52 +++++++++++++------ .../computer/blocks/TileComputerBase.java | 6 +++ 4 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java b/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java index 70c5780cf..cc9536a74 100644 --- a/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java @@ -9,7 +9,6 @@ package dan200.computercraft.client.render; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.computer.core.IComputer; import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera; import dan200.computercraft.shared.util.Holiday; @@ -129,18 +128,22 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer m_generalQuads; private Map> m_faceQuads; - public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform ) + public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, Matrix4f generalTransform, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform ) { // Get the models m_baseModel = baseModel; @@ -35,6 +36,7 @@ public class TurtleMultiModel implements IBakedModel m_leftUpgradeTransform = leftUpgradeTransform; m_rightUpgradeModel = rightUpgradeModel; m_rightUpgradeTransform = rightUpgradeTransform; + m_generalTransform = generalTransform; m_generalQuads = null; m_faceQuads = new HashMap<>(); } @@ -58,18 +60,34 @@ public class TurtleMultiModel implements IBakedModel private List buildQuads( IBlockState state, EnumFacing side, long rand ) { ArrayList quads = new ArrayList<>(); - quads.addAll( m_baseModel.getQuads( state, side, rand ) ); + ModelTransformer.transformQuadsTo( quads, m_baseModel.getQuads( state, side, rand ), m_generalTransform ); if( m_overlayModel != null ) { - quads.addAll( m_overlayModel.getQuads( state, side, rand ) ); + ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand ), m_generalTransform ); + } + if( m_overlayModel != null ) + { + ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand ), m_generalTransform ); } if( m_leftUpgradeModel != null ) { - ModelTransformer.transformQuadsTo( quads, m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ); + Matrix4f upgradeTransform = m_generalTransform; + if( m_leftUpgradeTransform != null ) + { + upgradeTransform = new Matrix4f( m_generalTransform ); + upgradeTransform.mul( m_leftUpgradeTransform ); + } + ModelTransformer.transformQuadsTo( quads, m_leftUpgradeModel.getQuads( state, side, rand ), upgradeTransform ); } if( m_rightUpgradeModel != null ) { - ModelTransformer.transformQuadsTo( quads, m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ); + Matrix4f upgradeTransform = m_generalTransform; + if( m_rightUpgradeTransform != null ) + { + upgradeTransform = new Matrix4f( m_generalTransform ); + upgradeTransform.mul( m_rightUpgradeTransform ); + } + ModelTransformer.transformQuadsTo( quads, m_rightUpgradeModel.getQuads( state, side, rand ), upgradeTransform ); } quads.trimToSize(); return quads; diff --git a/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java b/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java index e7c289a9e..6dbbf763e 100644 --- a/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java +++ b/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java @@ -35,6 +35,19 @@ import java.util.List; public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReloadListener { + private static final Matrix4f s_identity, s_flip; + + static + { + s_identity = new Matrix4f(); + s_identity.setIdentity(); + + s_flip = new Matrix4f(); + s_flip.setIdentity(); + s_flip.m11 = -1; // Flip on the y axis + s_flip.m13 = 1; // Models go from (0,0,0) to (1,1,1), so push back up. + } + private static class TurtleModelCombination { public final ComputerFamily m_family; @@ -43,8 +56,9 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload public final ITurtleUpgrade m_rightUpgrade; public final ResourceLocation m_overlay; public final boolean m_christmas; + public final boolean m_flip; - public TurtleModelCombination( ComputerFamily family, boolean colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, ResourceLocation overlay, boolean christmas ) + public TurtleModelCombination( ComputerFamily family, boolean colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, ResourceLocation overlay, boolean christmas, boolean flip ) { m_family = family; m_colour = colour; @@ -52,22 +66,26 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload m_rightUpgrade = rightUpgrade; m_overlay = overlay; m_christmas = christmas; + m_flip = flip; } @Override public boolean equals( Object other ) { - if( other == this ) { + if( other == this ) + { return true; } - if( other instanceof TurtleModelCombination ) { - TurtleModelCombination otherCombo = (TurtleModelCombination)other; + if( other instanceof TurtleModelCombination ) + { + TurtleModelCombination otherCombo = (TurtleModelCombination) other; if( otherCombo.m_family == m_family && otherCombo.m_colour == m_colour && otherCombo.m_leftUpgrade == m_leftUpgrade && otherCombo.m_rightUpgrade == m_rightUpgrade && Objects.equal( otherCombo.m_overlay, m_overlay ) && - otherCombo.m_christmas == m_christmas ) + otherCombo.m_christmas == m_christmas && + otherCombo.m_flip == m_flip ) { return true; } @@ -86,10 +104,11 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload result = prime * result + (m_rightUpgrade != null ? m_rightUpgrade.hashCode() : 0); result = prime * result + (m_overlay != null ? m_overlay.hashCode() : 0); result = prime * result + (m_christmas ? 1 : 0); + result = prime * result + (m_flip ? 1 : 0); return result; } } - + private HashMap m_cachedModels; private ItemOverrideList m_overrides; private final TurtleModelCombination m_defaultCombination; @@ -97,12 +116,12 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload public TurtleSmartItemModel() { m_cachedModels = new HashMap<>(); - m_defaultCombination = new TurtleModelCombination( ComputerFamily.Normal, false, null, null, null, false ); + m_defaultCombination = new TurtleModelCombination( ComputerFamily.Normal, false, null, null, null, false, false ); m_overrides = new ItemOverrideList( new ArrayList<>() ) { @Nonnull @Override - public IBakedModel handleItemState( @Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity) + public IBakedModel handleItemState( @Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity ) { ItemTurtleBase turtle = (ItemTurtleBase) stack.getItem(); ComputerFamily family = turtle.getFamily( stack ); @@ -111,7 +130,9 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload ITurtleUpgrade rightUpgrade = turtle.getUpgrade( stack, TurtleSide.Right ); ResourceLocation overlay = turtle.getOverlay( stack ); boolean christmas = HolidayUtil.getCurrentHoliday() == Holiday.Christmas; - TurtleModelCombination combo = new TurtleModelCombination( family, colour != -1, leftUpgrade, rightUpgrade, overlay, christmas ); + String label = turtle.getLabel( stack ); + boolean flip = label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" )); + TurtleModelCombination combo = new TurtleModelCombination( family, colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip ); if( m_cachedModels.containsKey( combo ) ) { return m_cachedModels.get( combo ); @@ -147,27 +168,24 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload ModelResourceLocation overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_family, combo.m_overlay, combo.m_christmas ); IBakedModel baseModel = modelManager.getModel( baseModelLocation ); IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( baseModelLocation ) : null; + Matrix4f transform = combo.m_flip ? s_flip : s_identity; Pair leftModel = (combo.m_leftUpgrade != null) ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null; Pair rightModel = (combo.m_rightUpgrade != null) ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null; if( leftModel != null && rightModel != null ) { - return new TurtleMultiModel( baseModel, overlayModel, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() ); + return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() ); } else if( leftModel != null ) { - return new TurtleMultiModel( baseModel, overlayModel, leftModel.getLeft(), leftModel.getRight(), null, null ); + return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), null, null ); } else if( rightModel != null ) { - return new TurtleMultiModel( baseModel, overlayModel, null, null, rightModel.getLeft(), rightModel.getRight() ); - } - else if( overlayModel != null ) - { - return new TurtleMultiModel( baseModel, overlayModel, null, null, null, null ); + return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, rightModel.getLeft(), rightModel.getRight() ); } else { - return baseModel; + return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, null, null ); } } diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java index 8e3c999e0..598a8010d 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java @@ -420,6 +420,12 @@ public abstract class TileComputerBase extends TileGeneric } } + public String getLabel() + { + IComputer computer = getComputer(); + return computer == null ? m_label : computer.getLabel(); + } + @Override public IComputer createComputer() {