Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Created March 9, 2013 19:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattdesl/5125296 to your computer and use it in GitHub Desktop.
Save mattdesl/5125296 to your computer and use it in GitHub Desktop.
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