Created
March 9, 2013 19:02
-
-
Save mattdesl/5125296 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.applet.Applet; | |
import java.awt.AWTEvent; | |
import java.awt.Color; | |
import java.awt.Graphics; | |
import java.awt.event.KeyEvent; | |
import java.awt.event.MouseEvent; | |
import java.awt.event.MouseWheelEvent; | |
import java.awt.image.BufferedImage; | |
import java.awt.image.DataBufferInt; | |
import java.util.Arrays; | |
import java.util.Random; | |
public class NormalMap extends Applet implements Runnable { | |
////////////////////////////////////////////////////////// | |
// entity tool output //////////////////////////////////// | |
////////////////////////////////////////////////////////// | |
//Direction | |
public final static float DIRECTION_RIGHT = 0; | |
public final static float DIRECTION_LEFT = 1; | |
//Entity types | |
public final static float PLAYER = 0; | |
public final static float PIRATE = 1; | |
public final static float REDBEARD = 2; | |
public final static float BARREL = 3; | |
public final static float LIGHT = 4; | |
public final static float PORTHOLE = 5; | |
//Bool | |
public final static float FALSE = 0; | |
public final static float TRUE = 1; | |
//Animation states | |
public final static float IDLE = 0; | |
public final static float WALK = 1; | |
public final static float ATTACK = 2; | |
//number of float elements for one entity | |
public final static int ENTITY_STRIDE = 21; | |
//total number of entities | |
public final static int ENTITY_COUNT = 10; | |
//array length | |
public final static int ENTITY_ARRAY_LENGTH = ENTITY_STRIDE * ENTITY_COUNT; | |
//Entity properties | |
public final static int ENTITY_ACTIVE = 0; | |
public final static int ENTITY_TYPE = 1; | |
public final static int ENTITY_HIDDEN = 2; | |
public final static int ENTITY_SPRITE = 3; | |
public final static int ENTITY_X = 4; | |
public final static int ENTITY_Y = 5; | |
public final static int ENTITY_VX = 6; | |
public final static int ENTITY_VY = 7; | |
public final static int LIGHT_INTENSITY = 8; | |
public final static int LIGHT_Z = 9; | |
public final static int LIGHT_R = 10; | |
public final static int LIGHT_G = 11; | |
public final static int LIGHT_B = 12; | |
public final static int LIGHT_A = 13; | |
public final static int LIGHT_ATT1 = 14; | |
public final static int LIGHT_ATT2 = 15; | |
public final static int LIGHT_ATT3 = 16; | |
public final static int ENTITY_ANIMATION = 17; | |
public final static int ENTITY_JUMPING = 18; | |
public final static int ENTITY_DIRECTION = 19; | |
public final static int ENTITY_HEALTH = 20; | |
//Entity data | |
public final static float[] ENTITIES = new float[ENTITY_COUNT * ENTITY_STRIDE]; | |
////////////////////////////////////////////////////////// | |
// image tool output ///////////////////////////////////// | |
////////////////////////////////////////////////////////// | |
//RGBA color table | |
public static final int[] TABLE = new int[] { | |
0, 0, 0, 0, //transparent | |
26, 26, 26, 255, //0x1a1a1a | |
191, 191, 191, 255, //0xbfbfbf | |
162, 148, 70, 255, //0xa29446 | |
247, 226, 107, 255, //0xf7e26b | |
194, 0, 0, 255, //0xc20000 | |
255, 0, 0, 255, //0xff0000 | |
157, 157, 157, 255, //0x9d9d9d | |
111, 68, 23, 255, //0x6f4417 | |
164, 100, 34, 255 //0xa46422 | |
}; | |
//sprite array properties | |
public static final int SPRITE_WIDTH = 11; | |
public static final int SPRITE_HEIGHT = 9; | |
public static final int SPRITE_COUNT = 6; | |
public static final int SPRITE_SIZE = SPRITE_WIDTH * SPRITE_HEIGHT; | |
public static final int SPRITE_COMPONENTS = 4 * 3; //Diffuse.RGBA, Normal.XYZ | |
public static final int SPRITE_STRIDE = SPRITE_SIZE * SPRITE_COMPONENTS; | |
public static final int SPRITE_ARRAY_LEN = SPRITE_STRIDE * SPRITE_COUNT; | |
public static final int SPRITE_ENCODING_LENGTH = 594; | |
//the sprite image color data | |
public static final float[] SPRITES = new float[SPRITE_ARRAY_LEN]; | |
//offsets to different sprite indices | |
public static final int SPRITE_PIRATE1_ATTACK = SPRITE_STRIDE * 0; | |
public static final int SPRITE_PIRATE1_WALK1 = SPRITE_STRIDE * 1; | |
public static final int SPRITE_PIRATE1_WALK2 = SPRITE_STRIDE * 2; | |
public static final int SPRITE_PIRATE2_ATTACK = SPRITE_STRIDE * 3; | |
public static final int SPRITE_PIRATE2_WALK1 = SPRITE_STRIDE * 4; | |
public static final int SPRITE_PIRATE2_WALK2 = SPRITE_STRIDE * 5; | |
////////////////////////////////////////////////////////// | |
// end of generated output /////////////////////////////// | |
////////////////////////////////////////////////////////// | |
//Sprite color components | |
public final static int RED = 0; | |
public final static int GREEN = 1; | |
public final static int BLUE = 2; | |
public final static int ALPHA = 3; | |
public final static int NORMAL_X = 4; | |
public final static int NORMAL_Y = 5; | |
public final static int NORMAL_Z = 6; | |
//First few entities are reserved for player, lights, etc. | |
public static final int LIGHT_0_INDEX = ENTITY_STRIDE * 1; | |
public static final int LIGHT_END_INDEX = LIGHT_0_INDEX + ENTITY_STRIDE; | |
public static final int ENTITY_START_INDEX = LIGHT_END_INDEX; | |
//TODO: remove these if not needed | |
boolean[] keys = new boolean[32767]; | |
float mouseX, mouseY, mouseWheel; | |
//Random | |
public final static Random RANDOM = new Random(); | |
public static final int WIDTH = 240/2; | |
public static final int HEIGHT = 160/2; | |
public static final int SCALE = 4; | |
public static final float ASPECT_RATIO = WIDTH / (float)HEIGHT; | |
public static final int spriteX = 40; | |
public static final int spriteY = 50; | |
public static final int SPRITE_SCALE = 2; | |
public static final float SPRITE_X_RATIO = SPRITE_WIDTH / (float)(SPRITE_WIDTH*SPRITE_SCALE); | |
public static final float SPRITE_Y_RATIO = SPRITE_HEIGHT / (float)(SPRITE_HEIGHT*SPRITE_SCALE); | |
float cameraX; | |
//RGBA and Normal XYZ buffer | |
public static final float[] SCREEN_BUFFER = new float[WIDTH * HEIGHT * 4 * 3]; | |
public void start() { | |
enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); | |
new Thread(this).start(); | |
} | |
public void run() { | |
//Set up graphics | |
setSize(WIDTH*SCALE, HEIGHT*SCALE); // For AppletViewer, remove later. | |
//Set up our screen image | |
final BufferedImage screen = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); | |
final int[] pixels = ((DataBufferInt)screen.getRaster().getDataBuffer()).getData(); | |
final Graphics g = screen.getGraphics(); | |
final Graphics appletGraphics = getGraphics(); | |
// Some variables to use for the fps. | |
int tick = 0, fps = 0, acc = 0; | |
long lastTime = System.nanoTime(); | |
//initialize mouse light | |
ENTITIES[LIGHT_0_INDEX + ENTITY_ACTIVE] = TRUE; | |
ENTITIES[LIGHT_0_INDEX + ENTITY_TYPE] = LIGHT; | |
ENTITIES[LIGHT_0_INDEX + LIGHT_INTENSITY] = 1f; | |
ENTITIES[LIGHT_0_INDEX + LIGHT_R] = 1f; | |
ENTITIES[LIGHT_0_INDEX + LIGHT_G] = 1f; | |
ENTITIES[LIGHT_0_INDEX + LIGHT_B] = 1f; | |
ENTITIES[LIGHT_0_INDEX + LIGHT_Z] = 0.25f; | |
//attenuation factors | |
ENTITIES[LIGHT_0_INDEX + LIGHT_ATT1] = 0.5f; | |
ENTITIES[LIGHT_0_INDEX + LIGHT_ATT2] = 1f; | |
ENTITIES[LIGHT_0_INDEX + LIGHT_ATT3] = 1f; | |
float[] noise = new float[WIDTH*HEIGHT]; | |
for (int i=0; i<noise.length; i++) | |
noise[i] = 1f + (((float)Math.random()*2-1)*.05f); | |
//TODO: use System.arraycopy for a slight opt | |
// ---------------- GAME LOOP ---------------- // | |
while (true) { | |
long now = System.nanoTime(); | |
long delta = now - lastTime; | |
acc += delta; | |
tick++; | |
if (acc >= 1000000000L) { | |
acc -= 1000000000L; | |
fps = tick; | |
tick = 0; | |
} | |
// Update | |
lastTime = now; | |
ENTITIES[LIGHT_0_INDEX + ENTITY_X] = mouseX; | |
ENTITIES[LIGHT_0_INDEX + ENTITY_Y] = mouseY; | |
//TODO: remove later if not needed | |
g.clearRect(0, 0, WIDTH, HEIGHT); | |
// ---------------- RENDER ENTITIES ---------------- // | |
// First render all entities to an offscreen buffer | |
Arrays.fill(SCREEN_BUFFER, 0f); | |
for (int i=0; i<ENTITY_ARRAY_LENGTH; i+=ENTITY_STRIDE) { | |
//ignore non-active entities | |
if (ENTITIES[i + ENTITY_ACTIVE]==FALSE) | |
continue; | |
int ex = (int)ENTITIES[i + ENTITY_X]; | |
int ey = (int)ENTITIES[i + ENTITY_Y]; | |
//if the entity is visible and within the screen | |
if (ENTITIES[i + ENTITY_TYPE]!=LIGHT | |
&& ex+SPRITE_WIDTH*SPRITE_SCALE >= cameraX | |
&& ex < cameraX+WIDTH | |
&& ey+SPRITE_HEIGHT*SPRITE_SCALE >= 0 | |
&& ey < HEIGHT) { | |
//nearest neighbour copy | |
for (int off=0; off<SPRITE_WIDTH*SPRITE_SCALE * SPRITE_HEIGHT*SPRITE_SCALE; off++) { | |
int y = off / (SPRITE_WIDTH*SPRITE_SCALE); | |
int x = off - (SPRITE_WIDTH*SPRITE_SCALE)*y; | |
int xx = | |
ENTITIES[i + ENTITY_DIRECTION] == DIRECTION_LEFT | |
? (SPRITE_WIDTH*SPRITE_SCALE - x - 1) | |
: x; | |
xx = (int)Math.floor(xx*SPRITE_X_RATIO); | |
int yy = (int)Math.floor(y*SPRITE_Y_RATIO); | |
int offset = (int)ENTITIES[ENTITY_SPRITE] + (xx + (yy * SPRITE_WIDTH))*SPRITE_COMPONENTS; | |
int screenSpace = ex-(int)cameraX; | |
//if sprite is not transparent, and not being wrapped around screen | |
if (SPRITES[offset + ALPHA] > 0f | |
&& screenSpace + x < WIDTH | |
&& screenSpace + x >= 0) { | |
int dstPos = ((screenSpace + x) + ((ey + y) * WIDTH))*SPRITE_COMPONENTS; | |
if (dstPos >= 0 && dstPos < SCREEN_BUFFER.length) | |
for (int indexOff=0; indexOff<SPRITE_COMPONENTS; indexOff++) | |
SCREEN_BUFFER[dstPos+indexOff] = SPRITES[offset+indexOff]; | |
} | |
} | |
} | |
} | |
final int bw = 15; | |
final int bh = 9; | |
// ---------------- PER-PIXEL LIGHTING ---------------- // | |
//Now render the offscreen entity buffer on top of our brick background | |
//Apply per-pixel lighting as we go... | |
for (int i=0; i<WIDTH*HEIGHT; i++) { | |
int y = i / WIDTH; | |
int x = i - WIDTH*y; | |
float R=140/255f,G=133/255f,B=138/255f; //diffuse | |
float NX=0f,NY=0f,NZ=1f; //normals | |
x+=cameraX; | |
// ---------------- PROCEDURAL BRICK ---------------- // | |
int bx = x; | |
bx += (y)%(bh*2) < bh ? bw/2 : 0; | |
int xbw = bx%bw; | |
int ybh = y%bh; | |
NX = ((xbw/(float)bw)*2-1)*0.25f; | |
NY = ((ybh/(float)bh)*2-1)*0.25f; | |
if ( xbw >= bw-1 ) | |
NX += 0.5f; | |
if ( xbw < 2 ) | |
NX -= 0.5f; | |
if ( ybh >= bh-2 ) | |
NY += 0.5f; | |
if ( ybh < 1 ) | |
NY -= 0.5f; | |
final int lw = 1; | |
if ((y+lw)%bh<lw || ((bx)%bw)<lw) { | |
NX = 0f; | |
NY = 0f; | |
NZ = 1f; | |
R = G = B = 0.5f; | |
} | |
float NOISE = noise[(bx*y)%noise.length]; | |
//mix in noise | |
R *= NOISE; | |
G *= NOISE; | |
B *= NOISE; | |
NX *= NOISE; | |
NY *= NOISE; | |
NZ *= NOISE; | |
// ---------------- COPY ENTITIES ---------------- // | |
if (SCREEN_BUFFER[i*SPRITE_COMPONENTS + ALPHA] > 0f) { | |
R = SCREEN_BUFFER[i*SPRITE_COMPONENTS ]; | |
G = SCREEN_BUFFER[i*SPRITE_COMPONENTS + GREEN]; | |
B = SCREEN_BUFFER[i*SPRITE_COMPONENTS + BLUE]; | |
NX = SCREEN_BUFFER[i*SPRITE_COMPONENTS + NORMAL_X]; | |
NY = SCREEN_BUFFER[i*SPRITE_COMPONENTS + NORMAL_Y]; | |
NZ = SCREEN_BUFFER[i*SPRITE_COMPONENTS + NORMAL_Z]; | |
} | |
//normalize the normals | |
float dist = (float)Math.sqrt(NX*NX + NY*NY + NZ*NZ); | |
NX/=dist; | |
NY/=dist; | |
NZ/=dist; | |
// ---------------- PER-PIXEL LIGHTING ---------------- // | |
float FR=0f,FG=0f,FB=0f; //final color | |
for (int idx=LIGHT_0_INDEX; idx<LIGHT_END_INDEX; idx+=ENTITY_STRIDE) { | |
float LX = (ENTITIES[idx + ENTITY_X] - x)/(float)WIDTH; | |
LX *= ASPECT_RATIO; | |
float LY = (ENTITIES[idx + ENTITY_Y] - y)/(float)HEIGHT; | |
float LZ = ENTITIES[idx + LIGHT_Z]; | |
//dist of lightDir vec3 | |
dist = (float)Math.sqrt(LX*LX + LY*LY + LZ*LZ); | |
//normalize lightDir | |
LX/=dist; | |
LY/=dist; | |
LZ/=dist; | |
float lambert = ENTITIES[idx + LIGHT_INTENSITY] * Math.max(0f, Math.min(10f, NX*LX + NY*LY + NZ*LZ)); | |
final float att = 1f/(ENTITIES[idx + LIGHT_ATT1] //TODO: use constants for attenuation / intensity | |
+(ENTITIES[idx + LIGHT_ATT2]*dist) | |
+(ENTITIES[idx + LIGHT_ATT3]*dist*dist)); | |
final float AMB = 0.0f; | |
FR += R*( AMB + (ENTITIES[idx + LIGHT_R]*lambert) * att); | |
FG += G*( AMB + (ENTITIES[idx + LIGHT_G]*lambert) * att); | |
FB += B*( AMB + (ENTITIES[idx + LIGHT_B]*lambert) * att); | |
} | |
float FILMGRAIN = RANDOM.nextFloat()/20; | |
FR += FILMGRAIN; | |
FG += FILMGRAIN; | |
FB += FILMGRAIN; | |
FR = Math.max(0f, Math.min(1f, FR)); | |
FG = Math.max(0f, Math.min(1f, FG)); | |
FB = Math.max(0f, Math.min(1f, FB)); | |
pixels[i] = ((int)(FR*255) << 16) | ((int)(FG*255) << 8) | (int)(FB*255); | |
} | |
//TODO: remove later | |
g.setColor(Color.white); | |
g.drawString("FPS " + String.valueOf(fps), 20, 30); | |
// Draw the entire results on the screen. | |
appletGraphics.drawImage(screen, 0, 0, WIDTH*SCALE, HEIGHT*SCALE, null); | |
do { | |
Thread.yield(); | |
} while (System.nanoTime() - lastTime < 1000000000/60); | |
if (!isActive()) { | |
return; | |
} | |
} | |
} | |
public void processEvent(AWTEvent e) | |
{ | |
boolean down = false; | |
switch (e.getID()) | |
{ | |
case KeyEvent.KEY_PRESSED: | |
down = true; | |
case KeyEvent.KEY_RELEASED: | |
keys[((KeyEvent) e).getKeyCode()] = down; | |
break; | |
case MouseEvent.MOUSE_PRESSED: | |
down = true; | |
case MouseEvent.MOUSE_RELEASED: | |
keys[((MouseEvent) e).getButton()] = down; | |
case MouseEvent.MOUSE_MOVED: | |
case MouseEvent.MOUSE_DRAGGED: | |
mouseX = ((MouseEvent) e).getX()/SCALE; | |
mouseY = ((MouseEvent) e).getY()/SCALE; | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment