1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-12 11:10:29 +00:00

Merge pull request #492 from SquidDev-CC/ComputerCraft/feature/fun-turtle-rendering

Improve vertex transformation system
This commit is contained in:
SquidDev 2017-12-01 19:39:33 +00:00
commit 3f70ca5192
4 changed files with 356 additions and 129 deletions

View File

@ -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 );
}
}
}

View File

@ -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;
@ -121,18 +120,22 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
GlStateManager.translate( posX + offset.x, posY + offset.y, posZ + offset.z );
// Render the label
IComputer computer = turtle.getComputer();
String label = (computer != null) ? computer.getLabel() : null;
String label = turtle.createProxy().getLabel();
if( label != null )
{
renderLabel( turtle.getAccess().getPosition(), label );
}
// Render the turtle
GlStateManager.translate( 0.5f, 0.0f, 0.5f );
GlStateManager.translate( 0.5f, 0.5f, 0.5f );
GlStateManager.rotate( 180.0f - yaw, 0.0f, 1.0f, 0.0f );
GlStateManager.translate( -0.5f, 0.0f, -0.5f );
if( label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" )) )
{
// Flip the model and swap the cull face as winding order will have changed.
GlStateManager.scale( 1.0f, -1.0f, 1.0f );
GlStateManager.cullFace( GlStateManager.CullFace.FRONT );
}
GlStateManager.translate( -0.5f, -0.5f, -0.5f );
// Render the turtle
int colour;
ComputerFamily family;
@ -172,6 +175,7 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
finally
{
GlStateManager.popMatrix();
GlStateManager.cullFace( GlStateManager.CullFace.BACK );
}
}

View File

@ -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;
@ -20,16 +17,17 @@ import java.util.Map;
public class TurtleMultiModel implements IBakedModel
{
private IBakedModel m_baseModel;
private IBakedModel m_overlayModel;
private IBakedModel m_leftUpgradeModel;
private Matrix4f m_leftUpgradeTransform;
private IBakedModel m_rightUpgradeModel;
private Matrix4f m_rightUpgradeTransform;
private final IBakedModel m_baseModel;
private final IBakedModel m_overlayModel;
private final Matrix4f m_generalTransform;
private final IBakedModel m_leftUpgradeModel;
private final Matrix4f m_leftUpgradeTransform;
private final IBakedModel m_rightUpgradeModel;
private final Matrix4f m_rightUpgradeTransform;
private List<BakedQuad> m_generalQuads;
private Map<EnumFacing, List<BakedQuad>> 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;
@ -38,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<>();
}
@ -48,51 +47,52 @@ 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 )
{
ArrayList<BakedQuad> 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<BakedQuad> buildQuads( IBlockState state, EnumFacing side, long rand )
{
ArrayList<BakedQuad> quads = new ArrayList<>();
ModelTransformer.transformQuadsTo( quads, m_baseModel.getQuads( state, side, rand ), m_generalTransform );
if( m_overlayModel != null )
{
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 )
{
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 )
{
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;
}
@Override
public boolean isAmbientOcclusion()
{
@ -132,63 +132,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;
}
}

View File

@ -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<TurtleModelCombination, IBakedModel> 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<IBakedModel, Matrix4f> leftModel = (combo.m_leftUpgrade != null) ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
Pair<IBakedModel, Matrix4f> 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 );
}
}