289 lines
11 KiB
C#

// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
using System;
using ClassicalSharp.Entities;
using ClassicalSharp.GraphicsAPI;
using ClassicalSharp.Renderers;
using ClassicalSharp.TexturePack;
using OpenTK;
namespace ClassicalSharp.Model {
public class BlockModel : IModel {
byte block = (byte)Block.Air;
float height;
TerrainAtlas1D atlas;
bool bright;
Vector3 minBB, maxBB;
public bool NoShade = false, SwitchOrder = false;
public float CosX = 1, SinX = 0;
ModelCache cache;
public BlockModel( Game game ) : base( game ) {
cache = game.ModelCache;
}
internal override void CreateParts() { }
public override bool Bobbing { get { return false; } }
public override float NameYOffset { get { return height + 0.075f; } }
public override float GetEyeY( Entity entity ) {
byte block = Byte.Parse( entity.ModelName );
float minY = game.BlockInfo.MinBB[block].Y;
float maxY = game.BlockInfo.MaxBB[block].Y;
return block == 0 ? 1 : (minY + maxY) / 2;
}
static Vector3 colShrink = new Vector3( 0.75f/16, 0.75f/16, 0.75f/16 );
public override Vector3 CollisionSize {
get { return (maxBB - minBB) - colShrink; } // to fit slightly inside
}
static Vector3 offset = new Vector3( -0.5f, -0.5f, -0.5f );
public override AABB PickingBounds {
get { return new AABB( minBB, maxBB ).Offset( offset ); }
}
public void CalcState( byte block ) {
if( game.BlockInfo.IsAir[block] ) {
bright = false;
minBB = Vector3.Zero;
maxBB = Vector3.One;
height = 1;
} else {
bright = game.BlockInfo.FullBright[block];
minBB = game.BlockInfo.MinBB[block];
maxBB = game.BlockInfo.MaxBB[block];
height = maxBB.Y - minBB.Y;
if( game.BlockInfo.IsSprite[block] )
height = 1;
}
}
public override bool ShouldRender( Entity p, FrustumCulling culling ) {
block = Utils.FastByte( p.ModelName );
CalcState( block );
return base.ShouldRender( p, culling );
}
public override float RenderDistance( Entity p ) {
block = Utils.FastByte( p.ModelName );
CalcState( block );
return base.RenderDistance( p );
}
int lastTexId = -1;
int colPacked;
protected override void DrawModel( Player p ) {
// TODO: using 'is' is ugly, but means we can avoid creating
// a string every single time held block changes.
if( p is FakePlayer ) {
Player realP = game.LocalPlayer;
col = game.World.IsLit( realP.EyePosition )
? game.World.Env.Sunlight : game.World.Env.Shadowlight;
// Adjust pitch so angle when looking straight down is 0.
float adjPitch = realP.PitchDegrees - 90;
if( adjPitch < 0 ) adjPitch += 360;
// Adjust colour so held block is brighter when looking straght up
float t = Math.Abs( adjPitch - 180 ) / 180;
float colScale = Utils.Lerp( 0.9f, 0.7f, t);
col = FastColour.Scale( col, colScale );
block = ((FakePlayer)p).Block;
} else {
block = Utils.FastByte( p.ModelName );
}
CalcState( block );
colPacked = col.Pack();
if( game.BlockInfo.IsAir[block] ) return;
lastTexId = -1;
atlas = game.TerrainAtlas1D;
bool sprite = game.BlockInfo.IsSprite[block];
DrawParts( sprite );
if( index == 0 ) return;
IGraphicsApi api = game.Graphics;
api.BindTexture( lastTexId );
TransformVertices();
if( sprite ) api.FaceCulling = true;
UpdateVB();
if( sprite ) api.FaceCulling = false;
}
void FlushIfNotSame( int texIndex ) {
int texId = game.TerrainAtlas1D.TexIds[texIndex];
if( texId == lastTexId ) return;
if( lastTexId != -1 ) {
game.Graphics.BindTexture( lastTexId );
TransformVertices();
UpdateVB();
}
lastTexId = texId;
index = 0;
}
void TransformVertices() {
for( int i = 0; i < index; i++ ) {
VertexP3fT2fC4b v = cache.vertices[i];
float t = 0;
t = CosX * v.Y + SinX * v.Z; v.Z = -SinX * v.Y + CosX * v.Z; v.Y = t; // Inlined RotX
t = cosYaw * v.X - sinYaw * v.Z; v.Z = sinYaw * v.X + cosYaw * v.Z; v.X = t; // Inlined RotY
v.X *= scale; v.Y *= scale; v.Z *= scale;
v.X += pos.X; v.Y += pos.Y; v.Z += pos.Z;
cache.vertices[i] = v;
}
}
void DrawParts( bool sprite ) {
// SwitchOrder is needed for held block, which renders without depth testing
if( sprite ) {
if( SwitchOrder ) {
SpriteZQuad( Side.Back, false );
SpriteXQuad( Side.Right, false );
} else {
SpriteXQuad( Side.Right, false );
SpriteZQuad( Side.Back, false );
}
if( SwitchOrder ) {
SpriteXQuad( Side.Right, true );
SpriteZQuad( Side.Back, true );
} else {
SpriteZQuad( Side.Back, true );
SpriteXQuad( Side.Right, true );
}
} else {
YQuad( 0, Side.Bottom, FastColour.ShadeYBottom );
if( SwitchOrder ) {
XQuad( maxBB.X - 0.5f, Side.Right, true, FastColour.ShadeX );
ZQuad( maxBB.Z - 0.5f, Side.Back, false, FastColour.ShadeZ );
XQuad( minBB.X - 0.5f, Side.Left, false, FastColour.ShadeX );
ZQuad( minBB.Z - 0.5f, Side.Front, true, FastColour.ShadeZ );
} else {
ZQuad( minBB.Z - 0.5f, Side.Front, true, FastColour.ShadeZ );
XQuad( maxBB.X - 0.5f, Side.Right, true, FastColour.ShadeX );
ZQuad( maxBB.Z - 0.5f, Side.Back, false, FastColour.ShadeZ );
XQuad( minBB.X - 0.5f, Side.Left, false, FastColour.ShadeX );
}
YQuad( height, Side.Top, 1.0f );
}
}
void YQuad( float y, int side, float shade ) {
int texId = game.BlockInfo.GetTextureLoc( block, side ), texIndex = 0;
TextureRec rec = atlas.GetTexRec( texId, 1, out texIndex );
FlushIfNotSame( texIndex );
int col = bright ? FastColour.WhitePacked : (NoShade ? colPacked : FastColour.Scale( this.col, shade ).Pack() );
float vOrigin = (texId % atlas.elementsPerAtlas1D) * atlas.invElementSize;
rec.U1 = minBB.X; rec.U2 = maxBB.X;
rec.V1 = vOrigin + minBB.Z * atlas.invElementSize;
rec.V2 = vOrigin + maxBB.Z * atlas.invElementSize * 15.99f/16f;
cache.vertices[index++] = new VertexP3fT2fC4b( minBB.X - 0.5f, y, minBB.Z - 0.5f, rec.U1, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( maxBB.X - 0.5f, y, minBB.Z - 0.5f, rec.U2, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( maxBB.X - 0.5f, y, maxBB.Z - 0.5f, rec.U2, rec.V2, col );
cache.vertices[index++] = new VertexP3fT2fC4b( minBB.X - 0.5f, y, maxBB.Z - 0.5f, rec.U1, rec.V2, col );
}
void ZQuad( float z, int side, bool swapU, float shade ) {
int texId = game.BlockInfo.GetTextureLoc( block, side ), texIndex = 0;
TextureRec rec = atlas.GetTexRec( texId, 1, out texIndex );
FlushIfNotSame( texIndex );
int col = bright ? FastColour.WhitePacked : (NoShade ? colPacked : FastColour.Scale( this.col, shade ).Pack() );
float vOrigin = (texId % atlas.elementsPerAtlas1D) * atlas.invElementSize;
rec.U1 = minBB.X; rec.U2 = maxBB.X;
rec.V1 = vOrigin + (1 - minBB.Y) * atlas.invElementSize;
rec.V2 = vOrigin + (1 - maxBB.Y) * atlas.invElementSize * 15.99f/16f;
if( swapU ) rec.SwapU();
cache.vertices[index++] = new VertexP3fT2fC4b( minBB.X - 0.5f, 0, z, rec.U1, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( minBB.X - 0.5f, height, z, rec.U1, rec.V2, col );
cache.vertices[index++] = new VertexP3fT2fC4b( maxBB.X - 0.5f, height, z, rec.U2, rec.V2, col );
cache.vertices[index++] = new VertexP3fT2fC4b( maxBB.X - 0.5f, 0, z, rec.U2, rec.V1, col );
}
void XQuad( float x, int side, bool swapU, float shade ) {
int texId = game.BlockInfo.GetTextureLoc( block, side ), texIndex = 0;
TextureRec rec = atlas.GetTexRec( texId, 1, out texIndex );
FlushIfNotSame( texIndex );
int col = bright ? FastColour.WhitePacked : (NoShade ? colPacked : FastColour.Scale( this.col, shade ).Pack() );
float vOrigin = (texId % atlas.elementsPerAtlas1D) * atlas.invElementSize;
rec.U1 = minBB.Z; rec.U2 = maxBB.Z;
rec.V1 = vOrigin + (1 - minBB.Y) * atlas.invElementSize;
rec.V2 = vOrigin + (1 - maxBB.Y) * atlas.invElementSize * 15.99f/16f;
if( swapU ) rec.SwapU();
cache.vertices[index++] = new VertexP3fT2fC4b( x, 0, minBB.Z - 0.5f, rec.U1, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( x, height, minBB.Z - 0.5f, rec.U1, rec.V2, col );
cache.vertices[index++] = new VertexP3fT2fC4b( x, height, maxBB.Z - 0.5f, rec.U2, rec.V2, col );
cache.vertices[index++] = new VertexP3fT2fC4b( x, 0, maxBB.Z - 0.5f, rec.U2, rec.V1, col );
}
void SpriteZQuad( int side, bool firstPart ) {
SpriteZQuad( side, firstPart, false );
SpriteZQuad( side, firstPart, true );
}
void SpriteZQuad( int side, bool firstPart, bool mirror ) {
int texId = game.BlockInfo.GetTextureLoc( block, side ), texIndex = 0;
TextureRec rec = atlas.GetTexRec( texId, 1, out texIndex );
FlushIfNotSame( texIndex );
if( height != 1 )
rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f);
int col = bright ? FastColour.WhitePacked : this.col.Pack();
float p1 = 0, p2 = 0;
if( firstPart ) { // Need to break into two quads for when drawing a sprite model in hand.
if( mirror ) { rec.U1 = 0.5f; p1 = -5.5f/16; }
else { rec.U2 = 0.5f; p2 = -5.5f/16; }
} else {
if( mirror ) { rec.U2 = 0.5f; p2 = 5.5f/16; }
else { rec.U1 = 0.5f; p1 = 5.5f/16; }
}
cache.vertices[index++] = new VertexP3fT2fC4b( p1, 0, p1, rec.U2, rec.V2, col );
cache.vertices[index++] = new VertexP3fT2fC4b( p1, 1, p1, rec.U2, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( p2, 1, p2, rec.U1, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( p2, 0, p2, rec.U1, rec.V2, col );
}
void SpriteXQuad( int side, bool firstPart ) {
SpriteXQuad( side, firstPart, false );
SpriteXQuad( side, firstPart, true );
}
void SpriteXQuad( int side, bool firstPart, bool mirror ) {
int texId = game.BlockInfo.GetTextureLoc( block, side ), texIndex = 0;
TextureRec rec = atlas.GetTexRec( texId, 1, out texIndex );
FlushIfNotSame( texIndex );
if( height != 1 )
rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f);
int col = bright ? FastColour.WhitePacked : this.col.Pack();
float x1 = 0, x2 = 0, z1 = 0, z2 = 0;
if( firstPart ) {
if( mirror ) { rec.U2 = 0.5f; x2 = -5.5f/16; z2 = 5.5f/16; }
else { rec.U1 = 0.5f; x1 = -5.5f/16; z1 = 5.5f/16; }
} else {
if( mirror ) { rec.U1 = 0.5f; x1 = 5.5f/16; z1 = -5.5f/16; }
else { rec.U2 = 0.5f; x2 = 5.5f/16; z2 = -5.5f/16; }
}
cache.vertices[index++] = new VertexP3fT2fC4b( x1, 0, z1, rec.U2, rec.V2, col );
cache.vertices[index++] = new VertexP3fT2fC4b( x1, 1, z1, rec.U2, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( x2, 1, z2, rec.U1, rec.V1, col );
cache.vertices[index++] = new VertexP3fT2fC4b( x2, 0, z2, rec.U1, rec.V2, col );
}
}
}