package rabbitescape.render; import static rabbitescape.engine.BehaviourTools.*; import rabbitescape.engine.Block; import rabbitescape.engine.CellularDirection; import static rabbitescape.engine.CellularDirection.*; import rabbitescape.engine.WaterRegion; import rabbitescape.engine.World; import rabbitescape.engine.textworld.BlockRenderer; import rabbitescape.engine.util.CellDebugPrint; import rabbitescape.engine.util.LookupItem2D; import rabbitescape.engine.util.MathUtil; import rabbitescape.engine.util.Position; import rabbitescape.render.gameloop.WaterAnimation; import rabbitescape.render.Vertex; public class WaterRegionRenderer implements LookupItem2D { public static final int contentsPerParticle = 50; public static final int maxParticleCountChange = 2; private static final int maxHeightChange = 1; public static enum BounceSurface { VERTICAL, HORIZONTAL, LEFT_RISE, RIGHT_RISE } public WaterRegion region; private int targetWaterHeight = 0; private int height = 0; private int lastHeight = 0; private final World world; private final WaterAnimation waterAnimation; public WaterRegionRenderer(WaterRegion region, World world, WaterAnimation waterAnimation ) { this.region = region; this.world = world; this.waterAnimation = waterAnimation; } public WaterRegionRenderer adjacentRenderer( CellularDirection d ) { return waterAnimation.lookupRenderer.getItemAt( region.x + d.xOffset, region.y + d.yOffset ); } public boolean adjacentIsNull( CellularDirection d ) { return null == adjacentRenderer( d ); } public boolean adjacentWaterIsFalling( CellularDirection d ) { WaterRegionRenderer adj = adjacentRenderer( d ); if ( null == adj ) { return false; } return adj.isFalling(); } /** * Called once per game step */ public void setTargetWaterHeight() { if ( isFalling() ) { targetWaterHeight = 0; return; } Block block = world.getBlockAt( region.x, region.y ); if ( null == block || isBridge( block )) { targetWaterHeight = region.getContents() / 32; } else if ( isSlopeNotBridge( block ) ) { targetWaterHeight = triangleHeight( region.getContents() ); } else { throw new RuntimeException( "Unexpected block type" ); } targetWaterHeight = targetWaterHeight > 32 ? 32 : targetWaterHeight; } /** * Called once per animation step */ public void setWaterHeight() { height = MathUtil.constrain( targetWaterHeight, lastHeight - maxHeightChange , lastHeight + maxHeightChange ); lastHeight = height; } /** * Called once per animation step, after all heights set. Checks if cell above has something in, and makes this cell * look full, possibly breaking maxHeightChange. */ public void removeHeightGaps() { WaterRegionRenderer above = adjacentRenderer( CellularDirection.UP ); if ( null == above ) { return; } if ( above.height > 0 ) { height = 32; } } /** * A = 0.5 * l^2 */ private int triangleHeight( int area ) { return (int)Math.sqrt( (double)( 2 * area ) ); } /** * Adds an upper vertex for the polygon for this region to the * supplied ArrayLists of coordinates. Supplied vertex is towards the * cell in the supplied direction. */ public Vertex topVertex( CellularDirection d ) { int x = region.x * 32, y = region.y * 32; // Local cell origin in nominal pixels. if ( 0 == height ) { switch ( d ) { case LEFT: return new Vertex( x, y + 32 ); case RIGHT: return new Vertex( x + 32, y + 32 ); default: throw new RuntimeException( "Can only add vertices for LEFT or RIGHT cells."); } } Block block = world.getBlockAt( region.x, region.y ); int boundaryHeight = calcBoundaryHeight( d ); int xOffset; switch ( d ) { case LEFT: if ( shapeEquals ( block, Block.Shape.UP_LEFT ) ) { xOffset = 32 - boundaryHeight; } else { xOffset = 0; } break; case RIGHT: if ( shapeEquals ( block, Block.Shape.UP_RIGHT ) ) { xOffset = boundaryHeight; } else { xOffset = 32; } break; default: throw new RuntimeException( "Can only add vertices for LEFT or RIGHT cells." ); } return new Vertex( x + xOffset, y + 32 - boundaryHeight ); } /** * For matching up heights in left/right cells. */ public int adjacentRendererWaterHeight( CellularDirection d ) { if ( region.isConnected( d ) ) { WaterRegionRenderer wrr = adjacentRenderer ( d ); if ( null == wrr ) { return height; } return wrr.height; } return height; } private int calcBoundaryHeight( CellularDirection d ) { if ( !region.isConnected( d ) ) { // The cell in that direction is not relevant return height; } WaterRegionRenderer adjWrr = adjacentRenderer( d ); if ( null == adjWrr ) { // Adjacent is probably empty, and this cell is probably a low level return height; } int heightDefect = Math.abs( targetWaterHeight - height ); int adjHeightDefect = Math.abs ( adjWrr.targetWaterHeight - adjWrr.height ); if ( heightDefect > adjHeightDefect ) { if ( heightDefect > maxHeightChange ) { return height; } } else { if ( adjHeightDefect > maxHeightChange ) { return adjWrr.height; } } int adjHeight = adjacentRendererWaterHeight( d ); int boundaryHeight = adjHeight > height ? adjHeight : height; // Use max return boundaryHeight; } public Vertex bottomVertex( CellularDirection d ) { int x = region.x * 32, y = region.y * 32; // Local cell origin in nominal pixels. int xOffset; Block block = world.getBlockAt( region.x, region.y ); switch ( d ) { case LEFT: if ( shapeEquals ( block, Block.Shape.UP_LEFT ) ) { xOffset = 32; } else { xOffset = 0; } break; case RIGHT: if ( shapeEquals ( block, Block.Shape.UP_RIGHT ) ) { xOffset = 0; } else { xOffset = 32; } break; default: throw new RuntimeException( "Can only add vertices for LEFT or RIGHT cells." ); } return new Vertex( x + xOffset, y + 32 ); } boolean isFalling() { if ( isSlopeNotBridge( world.getBlockAt( region.x, region.y ) ) ) { // Non bridge slopes are not connected down. return false; } return !isFull( region.x, region.y + 1 ); } boolean isFull( int x, int y ) { Block b = world.getBlockAt( x, y ); if ( s_isFlat( b ) ) { // Flat block cells have zero capacity: always full. return true; } WaterRegionRenderer wrr = waterAnimation.lookupRenderer.getItemAt( x, y ); if ( null == wrr ) { // Not full of flat block, but no region yet, empty return false; } return 32 == wrr.height && 32 == wrr.targetWaterHeight; } public Vector2D cellPosition() { return new Vector2D( region.x, region.y ); } @Override public Position getPosition() { return new Position( region.x, region.y ); } public void debugCellPrint( CellDebugPrint p ) { Block b = world.getBlockAt( region.x, region.y ); String s = null == b ? " " : "" + BlockRenderer.charForBlock( b ); String connStr = "U" + bool01( region.isConnected( UP ) ) + " " + "D" + bool01( region.isConnected( DOWN ) ) + " " + "L" + bool01( region.isConnected( LEFT ) ) + " " + "R" + bool01( region.isConnected( RIGHT ) ); p.addString( region.x, region.y, 0, s ); p.addString( region, 1, "cont %04d", region.getContents() ); //p.addString( region, 2, "netflow%6s", calcNetFlow().toString() ); //p.addString( region, 3, "watervel%6s", estimateVelocity( calcNetFlow() ).toString() ); p.addString( region, 4, connStr ); p.addString( region, 5, "falling" + bool01( isFalling() ) + " full" + isFull( region.x, region.y ) ); p.addString( region, 6, "height(targ) %02d(%02d)", height, targetWaterHeight ); p.addString( region, 7, "(%d,%d)", region.x, region.y ); } private String bool01( boolean b ) { return b ? "1" : "0"; } }