Roi Atalla avatar Roi Atalla committed f2b6fe9

First commit.

Comments (0)

Files changed (87)

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/LWJGL"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>OpenGL</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

.settings/org.eclipse.jdt.core.prefs

+#Wed Jan 25 16:44:15 EST 2012
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7

src/com/ra4king/opengl/GLProgram.java

+package com.ra4king.opengl;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.util.glu.GLU.*;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.ContextAttribs;
+import org.lwjgl.opengl.Display;
+import org.lwjgl.opengl.DisplayMode;
+import org.lwjgl.opengl.PixelFormat;
+
+public abstract class GLProgram {
+	private int fps;
+	
+	public GLProgram(boolean vsync) {
+		try {
+			Display.setFullscreen(true);
+			Display.setVSyncEnabled(vsync);
+		}
+		catch(Exception exc) {
+			exc.printStackTrace();
+		}
+	}
+	
+	public GLProgram(String name, int width, int height, boolean resizable) {
+		Display.setTitle(name);
+		
+		try {
+			Display.setDisplayMode(new DisplayMode(width,height));
+		}
+		catch(Exception exc) {
+			exc.printStackTrace();
+		}
+		
+		Display.setResizable(resizable);
+		
+		fps = 60;
+	}
+	
+	public void setFPS(int fps) {
+		this.fps = fps;
+	}
+	
+	public int getFPS() {
+		return fps;
+	}
+	
+	public final void run() {
+		run(false);
+	}
+	
+	public final void run(boolean core) {
+		try {
+			Display.create(new PixelFormat(),core ? new ContextAttribs(3,3) : new ContextAttribs());
+		}
+		catch(Exception exc) {
+			exc.printStackTrace();
+			System.exit(1);
+		}
+		
+		init();
+		
+		resized();
+		
+		long lastTime, lastFPS;
+		lastTime = lastFPS = System.nanoTime();
+		int frames = 0;
+		
+		while(!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
+			long deltaTime = System.nanoTime() - lastTime;
+			lastTime += deltaTime;
+			
+			if(Display.wasResized())
+				resized();
+			
+			update(deltaTime);
+			render();
+			
+			Display.update();
+			
+			int error;
+			while((error = glGetError()) != GL_NO_ERROR)
+				System.out.println(gluErrorString(error));
+			
+			frames++;
+			if(System.nanoTime() - lastFPS >= 1e9) {
+				System.out.println("FPS: ".concat(String.valueOf(frames)));
+				lastFPS += 1e9;
+				frames = 0;
+			}
+			
+			Display.sync(fps);
+		}
+		
+		destroy();
+	}
+	
+	public int getWidth() {
+		return Display.getWidth();
+	}
+	
+	public int getHeight() {
+		return Display.getHeight();
+	}
+	
+	public abstract void init();
+	
+	public void resized() {
+		glViewport(0,0,getWidth(),getHeight());
+	}
+	
+	public void update(long deltaTime) {}
+	
+	public abstract void render();
+	
+	public void destroy() {
+		Display.destroy();
+		System.exit(0);
+	}
+	
+	protected String readFromFile(String file) {
+		try(BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file),"UTF-8"))) {
+			StringBuilder s = new StringBuilder();
+			String l;
+			
+			while((l = reader.readLine()) != null)
+				s.append(l).append("\n");
+			
+			return s.toString();
+		}
+		catch(Exception exc) {
+			throw new RuntimeException("Failure reading file: " + file,exc);
+		}
+	}
+}

src/com/ra4king/opengl/PNGDecoder.java

+/*
+ * Copyright (c) 2008-2010, Matthias Mann
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright notice,
+ *       this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Matthias Mann nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.ra4king.opengl;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * A PNGDecoder. The slick PNG decoder is based on this class :)
+ * 
+ * @author Matthias Mann
+ */
+public class PNGDecoder {
+
+    public enum Format {
+        ALPHA(1, true),
+        LUMINANCE(1, false),
+        LUMINANCE_ALPHA(2, true),
+        RGB(3, false),
+        RGBA(4, true),
+        BGRA(4, true),
+        ABGR(4, true);
+
+        final int numComponents;
+        final boolean hasAlpha;
+
+        private Format(int numComponents, boolean hasAlpha) {
+            this.numComponents = numComponents;
+            this.hasAlpha = hasAlpha;
+        }
+
+        public int getNumComponents() {
+            return numComponents;
+        }
+
+        public boolean isHasAlpha() {
+            return hasAlpha;
+        }
+    }
+    
+    private static final byte[] SIGNATURE = {(byte)137, 80, 78, 71, 13, 10, 26, 10};
+
+    private static final int IHDR = 0x49484452;
+    private static final int PLTE = 0x504C5445;
+    private static final int tRNS = 0x74524E53;
+    private static final int IDAT = 0x49444154;
+    
+    private static final byte COLOR_GREYSCALE = 0;
+    private static final byte COLOR_TRUECOLOR = 2;
+    private static final byte COLOR_INDEXED = 3;
+    private static final byte COLOR_GREYALPHA = 4;
+    private static final byte COLOR_TRUEALPHA = 6;  
+    
+    private final InputStream input;
+    private final CRC32 crc;
+    private final byte[] buffer;
+    
+    private int chunkLength;
+    private int chunkType;
+    private int chunkRemaining;
+    
+    private int width;
+    private int height;
+    private int bitdepth;
+    private int colorType;
+    private int bytesPerPixel;
+    private byte[] palette;
+    private byte[] paletteA;
+    private byte[] transPixel;
+    
+    public PNGDecoder(InputStream input) throws IOException {
+        this.input = input;
+        this.crc = new CRC32();
+        this.buffer = new byte[4096];
+        
+        readFully(buffer, 0, SIGNATURE.length);
+        if(!checkSignature(buffer)) {
+            throw new IOException("Not a valid PNG file");
+        }
+        
+        openChunk(IHDR);
+        readIHDR();
+        closeChunk();
+        
+        searchIDAT: for(;;) {
+            openChunk();
+            switch (chunkType) {
+            case IDAT:
+                break searchIDAT;
+            case PLTE:
+                readPLTE();
+                break;
+            case tRNS:
+                readtRNS();
+                break;
+            }
+            closeChunk();
+        }
+
+        if(colorType == COLOR_INDEXED && palette == null) {
+            throw new IOException("Missing PLTE chunk");
+        }
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+    
+    /**
+     * Checks if the image has a real alpha channel.
+     * This method does not check for the presence of a tRNS chunk.
+     *
+     * @return true if the image has an alpha channel
+     * @see #hasAlpha()
+     */
+    public boolean hasAlphaChannel() {
+        return colorType == COLOR_TRUEALPHA || colorType == COLOR_GREYALPHA;
+    }
+
+    /**
+     * Checks if the image has transparency information either from
+     * an alpha channel or from a tRNS chunk.
+     * 
+     * @return true if the image has transparency
+     * @see #hasAlphaChannel()
+     * @see #overwriteTRNS(byte, byte, byte)
+     */
+    public boolean hasAlpha() {
+        return hasAlphaChannel() ||
+                paletteA != null || transPixel != null;
+    }
+    
+    public boolean isRGB() {
+        return colorType == COLOR_TRUEALPHA ||
+                colorType == COLOR_TRUECOLOR ||
+                colorType == COLOR_INDEXED;
+    }
+
+    /**
+     * Overwrites the tRNS chunk entry to make a selected color transparent.
+     * <p>This can only be invoked when the image has no alpha channel.</p>
+     * <p>Calling this method causes {@link #hasAlpha()} to return true.</p>
+     *
+     * @param r the red component of the color to make transparent
+     * @param g the green component of the color to make transparent
+     * @param b the blue component of the color to make transparent
+     * @throws UnsupportedOperationException if the tRNS chunk data can't be set
+     * @see #hasAlphaChannel() 
+     */
+    public void overwriteTRNS(byte r, byte g, byte b) {
+        if(hasAlphaChannel()) {
+            throw new UnsupportedOperationException("image has an alpha channel");
+        }
+        byte[] pal = this.palette;
+        if(pal == null) {
+            transPixel = new byte[] { 0, r, 0, g, 0, b };
+        } else {
+            paletteA = new byte[pal.length/3];
+            for(int i=0,j=0 ; i<pal.length ; i+=3,j++) {
+                if(pal[i] != r || pal[i+1] != g || pal[i+2] != b) {
+                    paletteA[j] = (byte)0xFF;
+                }
+            }
+        }
+    }
+
+    /**
+     * Computes the implemented format conversion for the desired format.
+     *
+     * @param fmt the desired format
+     * @return format which best matches the desired format
+     * @throws UnsupportedOperationException if this PNG file can't be decoded
+     */
+    public Format decideTextureFormat(Format fmt) {
+        switch (colorType) {
+        case COLOR_TRUECOLOR:
+            switch (fmt) {
+            case ABGR:
+            case RGBA:
+            case BGRA:
+            case RGB: return fmt;
+            default: return Format.RGB;
+            }
+        case COLOR_TRUEALPHA:
+            switch (fmt) {
+            case ABGR:
+            case RGBA:
+            case BGRA:
+            case RGB: return fmt;
+            default: return Format.RGBA;
+            }
+        case COLOR_GREYSCALE:
+            switch (fmt) {
+            case LUMINANCE:
+            case ALPHA: return fmt;
+            default: return Format.LUMINANCE;
+            }
+        case COLOR_GREYALPHA:
+            return Format.LUMINANCE_ALPHA;
+        case COLOR_INDEXED:
+            switch (fmt) {
+            case ABGR:
+            case RGBA:
+            case BGRA: return fmt;
+            default: return Format.RGBA;
+            }
+        default:
+            throw new UnsupportedOperationException("Not yet implemented");
+        }
+    }
+
+    /**
+     * Decodes the image into the specified buffer. The first line is placed at
+     * the current position. After decode the buffer position is at the end of
+     * the last line.
+     *
+     * @param buffer the buffer
+     * @param stride the stride in bytes from start of a line to start of the next line, can be negative.
+     * @param fmt the target format into which the image should be decoded.
+     * @throws IOException if a read or data error occurred
+     * @throws IllegalArgumentException if the start position of a line falls outside the buffer
+     * @throws UnsupportedOperationException if the image can't be decoded into the desired format
+     */
+    public void decode(ByteBuffer buffer, int stride, Format fmt) throws IOException {
+        final int offset = buffer.position();
+        final int lineSize = ((width * bitdepth + 7) / 8) * bytesPerPixel;
+        byte[] curLine = new byte[lineSize+1];
+        byte[] prevLine = new byte[lineSize+1];
+        byte[] palLine = (bitdepth < 8) ? new byte[width+1] : null;
+        
+        final Inflater inflater = new Inflater();
+        try {
+            for(int y=0 ; y<height ; y++) {
+                readChunkUnzip(inflater, curLine, 0, curLine.length);
+                unfilter(curLine, prevLine);
+
+                buffer.position(offset + y*stride);
+
+                switch (colorType) {
+                case COLOR_TRUECOLOR:
+                    switch (fmt) {
+                    case ABGR: copyRGBtoABGR(buffer, curLine); break;
+                    case RGBA: copyRGBtoRGBA(buffer, curLine); break;
+                    case BGRA: copyRGBtoBGRA(buffer, curLine); break;
+                    case RGB: copy(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_TRUEALPHA:
+                    switch (fmt) {
+                    case ABGR: copyRGBAtoABGR(buffer, curLine); break;
+                    case RGBA: copy(buffer, curLine); break;
+                    case BGRA: copyRGBAtoBGRA(buffer, curLine); break;
+                    case RGB: copyRGBAtoRGB(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_GREYSCALE:
+                    switch (fmt) {
+                    case LUMINANCE:
+                    case ALPHA: copy(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_GREYALPHA:
+                    switch (fmt) {
+                    case LUMINANCE_ALPHA: copy(buffer, curLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                case COLOR_INDEXED:
+                    switch(bitdepth) {
+                        case 8: palLine = curLine; break;
+                        case 4: expand4(curLine, palLine); break;
+                        case 2: expand2(curLine, palLine); break;
+                        case 1: expand1(curLine, palLine); break;
+                        default: throw new UnsupportedOperationException("Unsupported bitdepth for this image");
+                    }
+                    switch (fmt) {
+                    case ABGR: copyPALtoABGR(buffer, palLine); break;
+                    case RGBA: copyPALtoRGBA(buffer, palLine); break;
+                    case BGRA: copyPALtoBGRA(buffer, palLine); break;
+                    default: throw new UnsupportedOperationException("Unsupported format for this image");
+                    }
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Not yet implemented");
+                }
+
+                byte[] tmp = curLine;
+                curLine = prevLine;
+                prevLine = tmp;
+            }
+        } finally {
+            inflater.end();
+        }
+    }
+
+    /**
+     * Decodes the image into the specified buffer. The last line is placed at
+     * the current position. After decode the buffer position is at the end of
+     * the first line.
+     *
+     * @param buffer the buffer
+     * @param stride the stride in bytes from start of a line to start of the next line, must be positive.
+     * @param fmt the target format into which the image should be decoded.
+     * @throws IOException if a read or data error occurred
+     * @throws IllegalArgumentException if the start position of a line falls outside the buffer
+     * @throws UnsupportedOperationException if the image can't be decoded into the desired format
+     */
+    public void decodeFlipped(ByteBuffer buffer, int stride, Format fmt) throws IOException {
+        if(stride <= 0) {
+            throw new IllegalArgumentException("stride");
+        }
+        int pos = buffer.position();
+        int posDelta = (height-1) * stride;
+        buffer.position(pos + posDelta);
+        decode(buffer, -stride, fmt);
+        buffer.position(buffer.position() + posDelta);
+    }
+    
+    private void copy(ByteBuffer buffer, byte[] curLine) {
+        buffer.put(curLine, 1, curLine.length-1);
+    }
+
+    private void copyRGBtoABGR(ByteBuffer buffer, byte[] curLine) {
+        if(transPixel != null) {
+            byte tr = transPixel[1];
+            byte tg = transPixel[3];
+            byte tb = transPixel[5];
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                byte r = curLine[i];
+                byte g = curLine[i+1];
+                byte b = curLine[i+2];
+                byte a = (byte)0xFF;
+                if(r==tr && g==tg && b==tb) {
+                    a = 0;
+                }
+                buffer.put(a).put(b).put(g).put(r);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                buffer.put((byte)0xFF).put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]);
+            }
+        }
+    }
+
+    private void copyRGBtoRGBA(ByteBuffer buffer, byte[] curLine) {
+        if(transPixel != null) {
+            byte tr = transPixel[1];
+            byte tg = transPixel[3];
+            byte tb = transPixel[5];
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                byte r = curLine[i];
+                byte g = curLine[i+1];
+                byte b = curLine[i+2];
+                byte a = (byte)0xFF;
+                if(r==tr && g==tg && b==tb) {
+                    a = 0;
+                }
+                buffer.put(r).put(g).put(b).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                buffer.put(curLine[i]).put(curLine[i+1]).put(curLine[i+2]).put((byte)0xFF);
+            }
+        }
+    }
+
+    private void copyRGBtoBGRA(ByteBuffer buffer, byte[] curLine) {
+        if(transPixel != null) {
+            byte tr = transPixel[1];
+            byte tg = transPixel[3];
+            byte tb = transPixel[5];
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                byte r = curLine[i];
+                byte g = curLine[i+1];
+                byte b = curLine[i+2];
+                byte a = (byte)0xFF;
+                if(r==tr && g==tg && b==tb) {
+                    a = 0;
+                }
+                buffer.put(b).put(g).put(r).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=3) {
+                buffer.put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]).put((byte)0xFF);
+            }
+        }
+    }
+
+    private void copyRGBAtoABGR(ByteBuffer buffer, byte[] curLine) {
+        for(int i=1,n=curLine.length ; i<n ; i+=4) {
+            buffer.put(curLine[i+3]).put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]);
+        }
+    }
+
+    private void copyRGBAtoBGRA(ByteBuffer buffer, byte[] curLine) {
+        for(int i=1,n=curLine.length ; i<n ; i+=4) {
+            buffer.put(curLine[i+2]).put(curLine[i+1]).put(curLine[i]).put(curLine[i+3]);
+        }
+    }
+
+    private void copyRGBAtoRGB(ByteBuffer buffer, byte[] curLine) {
+        for(int i=1,n=curLine.length ; i<n ; i+=4) {
+            buffer.put(curLine[i]).put(curLine[i+1]).put(curLine[i+2]);
+        }
+    }
+
+    private void copyPALtoABGR(ByteBuffer buffer, byte[] curLine) {
+        if(paletteA != null) {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = paletteA[idx];
+                buffer.put(a).put(b).put(g).put(r);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = (byte)0xFF;
+                buffer.put(a).put(b).put(g).put(r);
+            }
+        }
+    }
+
+    private void copyPALtoRGBA(ByteBuffer buffer, byte[] curLine) {
+        if(paletteA != null) {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = paletteA[idx];
+                buffer.put(r).put(g).put(b).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = (byte)0xFF;
+                buffer.put(r).put(g).put(b).put(a);
+            }
+        }
+    }
+
+    private void copyPALtoBGRA(ByteBuffer buffer, byte[] curLine) {
+        if(paletteA != null) {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = paletteA[idx];
+                buffer.put(b).put(g).put(r).put(a);
+            }
+        } else {
+            for(int i=1,n=curLine.length ; i<n ; i+=1) {
+                int idx = curLine[i] & 255;
+                byte r = palette[idx*3 + 0];
+                byte g = palette[idx*3 + 1];
+                byte b = palette[idx*3 + 2];
+                byte a = (byte)0xFF;
+                buffer.put(b).put(g).put(r).put(a);
+            }
+        }
+    }
+
+    private void expand4(byte[] src, byte[] dst) {
+        for(int i=1,n=dst.length ; i<n ; i+=2) {
+            int val = src[1 + (i >> 1)] & 255;
+            switch(n-i) {
+                default: dst[i+1] = (byte)(val & 15);
+                case 1:  dst[i  ] = (byte)(val >> 4);
+            }
+        }
+    }
+
+    private void expand2(byte[] src, byte[] dst) {
+        for(int i=1,n=dst.length ; i<n ; i+=4) {
+            int val = src[1 + (i >> 2)] & 255;
+            switch(n-i) {
+                default: dst[i+3] = (byte)((val     ) & 3);
+                case 3:  dst[i+2] = (byte)((val >> 2) & 3);
+                case 2:  dst[i+1] = (byte)((val >> 4) & 3);
+                case 1:  dst[i  ] = (byte)((val >> 6)    );
+            }
+        }
+    }
+
+    private void expand1(byte[] src, byte[] dst) {
+        for(int i=1,n=dst.length ; i<n ; i+=8) {
+            int val = src[1 + (i >> 3)] & 255;
+            switch(n-i) {
+                default: dst[i+7] = (byte)((val     ) & 1);
+                case 7:  dst[i+6] = (byte)((val >> 1) & 1);
+                case 6:  dst[i+5] = (byte)((val >> 2) & 1);
+                case 5:  dst[i+4] = (byte)((val >> 3) & 1);
+                case 4:  dst[i+3] = (byte)((val >> 4) & 1);
+                case 3:  dst[i+2] = (byte)((val >> 5) & 1);
+                case 2:  dst[i+1] = (byte)((val >> 6) & 1);
+                case 1:  dst[i  ] = (byte)((val >> 7)    );
+            }
+        }
+    }
+    
+    private void unfilter(byte[] curLine, byte[] prevLine) throws IOException {
+        switch (curLine[0]) {
+            case 0: // none
+                break;
+            case 1:
+                unfilterSub(curLine);
+                break;
+            case 2:
+                unfilterUp(curLine, prevLine);
+                break;
+            case 3:
+                unfilterAverage(curLine, prevLine);
+                break;
+            case 4:
+                unfilterPaeth(curLine, prevLine);
+                break;
+            default:
+                throw new IOException("invalide filter type in scanline: " + curLine[0]);
+        }
+    }
+    
+    private void unfilterSub(byte[] curLine) {
+        final int bpp = this.bytesPerPixel;
+        for(int i=bpp+1,n=curLine.length ; i<n ; ++i) {
+            curLine[i] += curLine[i-bpp];
+        }
+    }
+    
+    private void unfilterUp(byte[] curLine, byte[] prevLine) {
+        for(int i=1,n=curLine.length ; i<n ; ++i) {
+            curLine[i] += prevLine[i];
+        }
+    }
+    
+    private void unfilterAverage(byte[] curLine, byte[] prevLine) {
+        final int bpp = this.bytesPerPixel;
+        
+        int i;
+        for(i=1 ; i<=bpp ; ++i) {
+            curLine[i] += (byte)((prevLine[i] & 0xFF) >>> 1);
+        }
+        for(int n=curLine.length ; i<n ; ++i) {
+            curLine[i] += (byte)(((prevLine[i] & 0xFF) + (curLine[i - bpp] & 0xFF)) >>> 1);
+        }
+    }
+    
+    private void unfilterPaeth(byte[] curLine, byte[] prevLine) {
+        final int bpp = this.bytesPerPixel;
+        
+        int i;
+        for(i=1 ; i<=bpp ; ++i) {
+            curLine[i] += prevLine[i];
+        }
+        for(int n=curLine.length ; i<n ; ++i) {
+            int a = curLine[i - bpp] & 255;
+            int b = prevLine[i] & 255;
+            int c = prevLine[i - bpp] & 255;
+            int p = a + b - c;
+            int pa = p - a; if(pa < 0) pa = -pa;
+            int pb = p - b; if(pb < 0) pb = -pb;
+            int pc = p - c; if(pc < 0) pc = -pc;
+            if(pa<=pb && pa<=pc)
+                c = a;
+            else if(pb<=pc)
+                c = b;
+            curLine[i] += (byte)c;
+        }
+    }
+      
+    private void readIHDR() throws IOException {
+        checkChunkLength(13);
+        readChunk(buffer, 0, 13);
+        width = readInt(buffer, 0);
+        height = readInt(buffer, 4);
+        bitdepth = buffer[8] & 255;
+        colorType = buffer[9] & 255;
+        
+        switch (colorType) {
+        case COLOR_GREYSCALE:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 1;
+            break;
+        case COLOR_GREYALPHA:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 2;
+            break;
+        case COLOR_TRUECOLOR:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 3;
+            break;
+        case COLOR_TRUEALPHA:
+            if(bitdepth != 8) {
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            bytesPerPixel = 4;
+            break;
+        case COLOR_INDEXED:
+            switch(bitdepth) {
+            case 8:
+            case 4:
+            case 2:
+            case 1:
+                bytesPerPixel = 1;
+                break;
+            default:
+                throw new IOException("Unsupported bit depth: " + bitdepth);
+            }
+            break;
+        default:
+            throw new IOException("unsupported color format: " + colorType);
+        }
+        
+        if(buffer[10] != 0) {
+            throw new IOException("unsupported compression method");
+        }
+        if(buffer[11] != 0) {
+            throw new IOException("unsupported filtering method");
+        }
+        if(buffer[12] != 0) {
+            throw new IOException("unsupported interlace method");
+        }
+    }
+
+    private void readPLTE() throws IOException {
+        int paletteEntries = chunkLength / 3;
+        if(paletteEntries < 1 || paletteEntries > 256 || (chunkLength % 3) != 0) {
+            throw new IOException("PLTE chunk has wrong length");
+        }
+        palette = new byte[paletteEntries*3];
+        readChunk(palette, 0, palette.length);
+    }
+
+    private void readtRNS() throws IOException {
+        switch (colorType) {
+        case COLOR_GREYSCALE:
+            checkChunkLength(2);
+            transPixel = new byte[2];
+            readChunk(transPixel, 0, 2);
+            break;
+        case COLOR_TRUECOLOR:
+            checkChunkLength(6);
+            transPixel = new byte[6];
+            readChunk(transPixel, 0, 6);
+            break;
+        case COLOR_INDEXED:
+            if(palette == null) {
+                throw new IOException("tRNS chunk without PLTE chunk");
+            }
+            paletteA = new byte[palette.length/3];
+            Arrays.fill(paletteA, (byte)0xFF);
+            readChunk(paletteA, 0, paletteA.length);
+            break;
+        default:
+            // just ignore it
+        }
+    }
+    
+    private void closeChunk() throws IOException {
+        if(chunkRemaining > 0) {
+            // just skip the rest and the CRC
+            skip(chunkRemaining + 4);
+        } else {
+            readFully(buffer, 0, 4);
+            int expectedCrc = readInt(buffer, 0);
+            int computedCrc = (int)crc.getValue();
+            if(computedCrc != expectedCrc) {
+                throw new IOException("Invalid CRC");
+            }
+        }
+        chunkRemaining = 0;
+        chunkLength = 0;
+        chunkType = 0;
+    }
+    
+    private void openChunk() throws IOException {
+        readFully(buffer, 0, 8);
+        chunkLength = readInt(buffer, 0);
+        chunkType = readInt(buffer, 4);
+        chunkRemaining = chunkLength;
+        crc.reset();
+        crc.update(buffer, 4, 4);   // only chunkType
+    }
+    
+    private void openChunk(int expected) throws IOException {
+        openChunk();
+        if(chunkType != expected) {
+            throw new IOException("Expected chunk: " + Integer.toHexString(expected));
+        }
+    }
+
+    private void checkChunkLength(int expected) throws IOException {
+        if(chunkLength != expected) {
+            throw new IOException("Chunk has wrong size");
+        }
+    }
+    
+    private int readChunk(byte[] buffer, int offset, int length) throws IOException {
+        if(length > chunkRemaining) {
+            length = chunkRemaining;
+        }
+        readFully(buffer, offset, length);
+        crc.update(buffer, offset, length);
+        chunkRemaining -= length;
+        return length;
+    }
+
+    private void refillInflater(Inflater inflater) throws IOException {
+        while(chunkRemaining == 0) {
+            closeChunk();
+            openChunk(IDAT);
+        }
+        int read = readChunk(buffer, 0, buffer.length);
+        inflater.setInput(buffer, 0, read);
+    }
+    
+    private void readChunkUnzip(Inflater inflater, byte[] buffer, int offset, int length) throws IOException {
+        assert(buffer != this.buffer);
+        try {
+            do {
+                int read = inflater.inflate(buffer, offset, length);
+                if(read <= 0) {
+                    if(inflater.finished()) {
+                        throw new EOFException();
+                    }
+                    if(inflater.needsInput()) {
+                        refillInflater(inflater);
+                    } else {
+                        throw new IOException("Can't inflate " + length + " bytes");
+                    }
+                } else {
+                    offset += read;
+                    length -= read;
+                }
+            } while(length > 0);
+        } catch (DataFormatException ex) {
+            throw (IOException)(new IOException("inflate error").initCause(ex));
+        }
+    }
+
+    private void readFully(byte[] buffer, int offset, int length) throws IOException {
+        do {
+            int read = input.read(buffer, offset, length);
+            if(read < 0) {
+                throw new EOFException();
+            }
+            offset += read;
+            length -= read;
+        } while(length > 0);
+    }
+    
+    private int readInt(byte[] buffer, int offset) {
+        return
+                ((buffer[offset  ]      ) << 24) |
+                ((buffer[offset+1] & 255) << 16) |
+                ((buffer[offset+2] & 255) <<  8) |
+                ((buffer[offset+3] & 255)      );
+    }
+
+    private void skip(long amount) throws IOException {
+        while(amount > 0) {
+            long skipped = input.skip(amount);
+            if(skipped < 0) {
+                throw new EOFException();
+            }
+            amount -= skipped;
+        }
+    }
+    
+    private static boolean checkSignature(byte[] buffer) {
+        for(int i=0 ; i<SIGNATURE.length ; i++) {
+            if(buffer[i] != SIGNATURE[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+}

src/com/ra4king/opengl/ShaderProgram.java

+package com.ra4king.opengl;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL20.*;
+
+public class ShaderProgram {
+	private int program;
+	
+	public ShaderProgram(String vertexShader, String fragmentShader) {
+		int vs = glCreateShader(GL_VERTEX_SHADER);
+		glShaderSource(vs, vertexShader);
+		
+		glCompileShader(vs);
+		
+		if(glGetShader(vs, GL_COMPILE_STATUS) == GL_FALSE)
+			throw new RuntimeException("Failure in compiling vertex shader. Error log:\n" + glGetShaderInfoLog(vs, glGetShader(vs, GL_INFO_LOG_LENGTH)));
+		
+		int fs = glCreateShader(GL_FRAGMENT_SHADER);
+		glShaderSource(fs, fragmentShader);
+		
+		glCompileShader(fs);
+		
+		if(glGetShader(fs, GL_COMPILE_STATUS) == GL_FALSE)
+			throw new RuntimeException("Failure in compiling fragment shader. Error log:\n" + glGetShaderInfoLog(fs, glGetShader(fs, GL_INFO_LOG_LENGTH)));
+		
+		program = glCreateProgram();
+		glAttachShader(program, vs);
+		glAttachShader(program, fs);
+		
+		glLinkProgram(program);
+		
+		if(glGetProgram(program, GL_LINK_STATUS) == GL_FALSE)
+			throw new RuntimeException("Failure in linking program. Error log:\n" + glGetProgramInfoLog(program, glGetProgram(program, GL_INFO_LOG_LENGTH)));
+		
+		glDetachShader(program, vs);
+		glDetachShader(program, fs);
+		
+		glDeleteShader(vs);
+		glDeleteShader(fs);
+	}
+	
+	public int getProgram() {
+		return program;
+	}
+	
+	public void begin() {
+		glUseProgram(program);
+	}
+	
+	public void end() {
+		glUseProgram(0);
+	}
+	
+	public void destroy() {
+		glDeleteProgram(program);
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter1/Example1_1.java

+package com.ra4king.opengl.arcsynthesis.chapter1;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+
+public class Example1_1 extends GLProgram {
+	public static void main(String[] args) {
+		new Example1_1().run(true);
+	}
+	
+	private int program, vbo;
+	
+	public Example1_1() {
+		super("Example 1.1", 500, 500, false);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		
+		int vs = glCreateShader(GL_VERTEX_SHADER);
+		glShaderSource(vs, readFromFile("example1.1.vert"));
+		
+		glCompileShader(vs);
+		
+		if(glGetShader(vs, GL_COMPILE_STATUS) == GL_FALSE) {
+			System.err.println("Failure in compiling vertex shader. Error log:\n" + glGetShaderInfoLog(vs, glGetShader(vs, GL_INFO_LOG_LENGTH)));
+			System.exit(0);
+		}
+		
+		int fs = glCreateShader(GL_FRAGMENT_SHADER);
+		glShaderSource(fs, readFromFile("example1.1.frag"));
+		
+		glCompileShader(fs);
+		
+		if(glGetShader(fs, GL_COMPILE_STATUS) == GL_FALSE) {
+			System.err.println("Failure in compiling fragment shader. Error log:\n" + glGetShaderInfoLog(fs, glGetShader(fs, GL_INFO_LOG_LENGTH)));
+			destroy();
+		}
+		
+		program = glCreateProgram();
+		glAttachShader(program, vs);
+		glAttachShader(program, fs);
+		
+		glLinkProgram(program);
+		
+		if(glGetProgram(program, GL_LINK_STATUS) == GL_FALSE) {
+			System.err.println("Failure in linking program. Error log:\n" + glGetProgramInfoLog(program, glGetProgram(program, GL_INFO_LOG_LENGTH)));
+			destroy();
+		}
+		
+		glDetachShader(program, vs);
+		glDetachShader(program, fs);
+		
+		glDeleteShader(vs);
+		glDeleteShader(fs);
+		
+		vbo = glGenBuffers();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, (FloatBuffer)BufferUtils.createFloatBuffer(24).put(new float[] { 0.75f,  0.75f, 0.0f, 1.0f,
+																									   0.75f, -0.75f, 0.0f, 1.0f,
+																									  -0.75f, -0.75f, 0.0f, 1.0f}).flip(),GL_STATIC_DRAW);
+		
+		glBindVertexArray(glGenVertexArrays());
+		
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void render() {
+		glClear(GL_COLOR_BUFFER_BIT);
+		
+		glUseProgram(program);
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0);
+		
+		glDrawArrays(GL_TRIANGLES, 0, 3);
+		
+		glDisableVertexAttribArray(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		
+		glUseProgram(0);
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter1/example1.1.frag

+#version 330
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = vec4(1,1,1,1);
+}

src/com/ra4king/opengl/arcsynthesis/chapter1/example1.1.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+
+void main()
+{
+	gl_Position = position;
+}

src/com/ra4king/opengl/arcsynthesis/chapter2/example1/Example2_1.java

+package com.ra4king.opengl.arcsynthesis.chapter2.example1;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.ShaderProgram;
+
+public class Example2_1 extends GLProgram {
+	public static void main(String[] args) {
+		new Example2_1().run(true);
+	}
+	
+	private ShaderProgram program;
+	private int vbo;
+	
+	public Example2_1() {
+		super("Example 2.1", 500, 500, false);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		
+		program = new ShaderProgram(readFromFile("example2.1.vert"),readFromFile("example2.1.frag"));
+		
+		vbo = glGenBuffers();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, (FloatBuffer)BufferUtils.createFloatBuffer(12).put(new float[] { 0.75f,  0.75f, 0.0f, 1.0f,
+																									   0.75f, -0.75f, 0.0f, 1.0f,
+																									  -0.75f, -0.75f, 0.0f, 1.0f}).flip(),GL_STATIC_DRAW);
+		
+		glBindVertexArray(glGenVertexArrays());
+		
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void render() {
+		glClear(GL_COLOR_BUFFER_BIT);
+		
+		program.begin();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0);
+		
+		glDrawArrays(GL_TRIANGLES, 0, 3);
+		
+		glDisableVertexAttribArray(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		
+		program.end();
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter2/example1/example2.1.frag

+#version 330
+
+out vec4 outputColor;
+
+void main()
+{
+    float lerpValue = gl_FragCoord.y / 600.0f;
+        
+    outputColor = mix(vec4(1.0f, 1.0f, 1.0f, 1.0f), vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpValue);
+}

src/com/ra4king/opengl/arcsynthesis/chapter2/example1/example2.1.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+
+void main()
+{
+	gl_Position = position;
+}

src/com/ra4king/opengl/arcsynthesis/chapter2/example2/Example2_2.java

+package com.ra4king.opengl.arcsynthesis.chapter2.example2;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.ShaderProgram;
+
+public class Example2_2 extends GLProgram {
+	public static void main(String[] args) {
+		new Example2_2().run(true);
+	}
+	
+	private ShaderProgram program;
+	private int vbo;
+	
+	public Example2_2() {
+		super("Example 2.2", 500, 500, false);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		
+		program = new ShaderProgram(readFromFile("example2.2.vert"),readFromFile("example2.2.frag"));
+		
+		vbo = glGenBuffers();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, (FloatBuffer)BufferUtils.createFloatBuffer(24).put(new float[] { 0.0f,    0.5f, 0.0f, 1.0f,
+																									   0.5f, -0.366f, 0.0f, 1.0f,
+																									  -0.5f, -0.366f, 0.0f, 1.0f,
+																									   1.0f,    0.0f, 0.0f, 1.0f,
+																									   0.0f,    1.0f, 0.0f, 1.0f,
+																									   0.0f,    0.0f, 1.0f, 1.0f}).flip(),GL_STATIC_DRAW);
+		
+		glBindVertexArray(glGenVertexArrays());
+		
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void render() {
+		glClear(GL_COLOR_BUFFER_BIT);
+		
+		program.begin();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glEnableVertexAttribArray(0);
+		glEnableVertexAttribArray(1);
+		glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0);
+		glVertexAttribPointer(1, 4, GL_FLOAT, false, 0, 48);
+		
+		glDrawArrays(GL_TRIANGLES, 0, 3);
+		
+		glDisableVertexAttribArray(0);
+		glDisableVertexAttribArray(1);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		
+		program.end();
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter2/example2/example2.2.frag

+#version 330
+
+smooth in vec4 theColor;
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = theColor;
+}

src/com/ra4king/opengl/arcsynthesis/chapter2/example2/example2.2.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec4 color;
+
+smooth out vec4 theColor;
+
+void main()
+{
+	gl_Position = position;
+	theColor = color;
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example1/Example3_1.java

+package com.ra4king.opengl.arcsynthesis.chapter3.example1;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.ShaderProgram;
+
+public class Example3_1 extends GLProgram {
+	public static void main(String[] args) {
+		new Example3_1().run(true);
+	}
+	
+	private ShaderProgram program;
+	
+	private FloatBuffer vertexData, newData;
+	private int vbo;
+	
+	private long elapsedTime;
+	
+	public Example3_1() {
+		super("Example 3.1", 500, 500, false);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		
+		program = new ShaderProgram(readFromFile("example3.1.vert"),readFromFile("example3.1.frag"));
+		
+		vbo = glGenBuffers();
+		
+		vertexData = (FloatBuffer)BufferUtils.createFloatBuffer(12).put(new float[] { 0.25f,  0.25f, 0.0f, 1.0f,
+																					  0.25f, -0.25f, 0.0f, 1.0f,
+																					 -0.25f, -0.25f, 0.0f, 1.0f}).flip();
+		newData = BufferUtils.createFloatBuffer(12);
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STREAM_DRAW);
+		
+		glBindVertexArray(glGenVertexArrays());
+		
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void update(long deltaTime) {
+		elapsedTime += deltaTime;
+		
+		float loopDuration = 5;
+		float scale = (float)Math.PI * 2 / loopDuration;
+		float currentTimeThroughLoop = (elapsedTime/(float)1e9) % loopDuration;
+		
+		float xOffset = (float)Math.cos(currentTimeThroughLoop * scale) * 0.5f;
+		float yOffset = (float)Math.sin(currentTimeThroughLoop * scale) * 0.5f;
+		
+		newData.clear();
+		
+		for(int a = 0; a < 12; a += 4) {
+			newData.put(vertexData.get(a) + xOffset);
+			newData.put(vertexData.get(a+1) + yOffset);
+			newData.put(0);
+			newData.put(1);
+		}
+		
+		newData.flip();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferSubData(GL_ARRAY_BUFFER, 0, newData);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void render() {
+		glClear(GL_COLOR_BUFFER_BIT);
+		
+		program.begin();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0);
+		
+		glDrawArrays(GL_TRIANGLES, 0, 3);
+		
+		glDisableVertexAttribArray(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		
+		program.end();
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example1/example3.1.frag

+#version 330
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = vec4(1, 1, 1, 1);
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example1/example3.1.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+
+void main()
+{
+	gl_Position = position;
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example2/Example3_2.java

+package com.ra4king.opengl.arcsynthesis.chapter3.example2;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.ShaderProgram;
+
+public class Example3_2 extends GLProgram {
+	public static void main(String[] args) {
+		new Example3_2().run(true);
+	}
+	
+	private ShaderProgram program;
+	private int offsetLocation;
+	private int vbo;
+	
+	private long elapsedTime;
+	
+	public Example3_2() {
+		super("Example 3.2", 500, 500, false);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		
+		program = new ShaderProgram(readFromFile("example3.2.vert"),readFromFile("example3.2.frag"));
+		offsetLocation = glGetUniformLocation(program.getProgram(), "offset");
+		
+		vbo = glGenBuffers();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, (FloatBuffer)BufferUtils.createFloatBuffer(12).put(new float[] { 0.25f,  0.25f, 0.0f, 1.0f,
+																									   0.25f, -0.25f, 0.0f, 1.0f,
+																									  -0.25f, -0.25f, 0.0f, 1.0f}).flip(), GL_STATIC_DRAW);
+		
+		glBindVertexArray(glGenVertexArrays());
+		
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void update(long deltaTime) {
+		elapsedTime += deltaTime;
+	}
+	
+	@Override
+	public void render() {
+		float loopDuration = 5;
+		float scale = (float)Math.PI * 2 / loopDuration;
+		float currentTimeThroughLoop = (elapsedTime/(float)1e9) % loopDuration;
+		
+		float xOffset = (float)Math.cos(currentTimeThroughLoop * scale) * 0.5f;
+		float yOffset = (float)Math.sin(currentTimeThroughLoop * scale) * 0.5f;
+		
+		glClear(GL_COLOR_BUFFER_BIT);
+		
+		program.begin();
+		
+		glUniform2f(offsetLocation, xOffset, yOffset);
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0);
+		
+		glDrawArrays(GL_TRIANGLES, 0, 3);
+		
+		glDisableVertexAttribArray(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		
+		program.end();
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example2/example3.2.frag

+#version 330
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = vec4(1, 1, 1, 1);
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example2/example3.2.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+uniform vec2 offset;
+
+void main()
+{
+	gl_Position = position + vec4(offset, 0, 0);
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example3/Example3_3.java

+package com.ra4king.opengl.arcsynthesis.chapter3.example3;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.ShaderProgram;
+
+public class Example3_3 extends GLProgram {
+	public static void main(String[] args) {
+		new Example3_3().run(true);
+	}
+	
+	private ShaderProgram program;
+	private int timeLocation;
+	private int vbo;
+	
+	private long elapsedTime;
+	
+	public Example3_3() {
+		super("Example 3.3", 500, 500, false);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		
+		program = new ShaderProgram(readFromFile("example3.3.vert"),readFromFile("example3.3.frag"));
+		timeLocation = glGetUniformLocation(program.getProgram(), "time");
+		
+		int loopDurationLocation = glGetUniformLocation(program.getProgram(), "loopDuration");
+		program.begin();
+		glUniform1f(loopDurationLocation, 5);
+		program.end();
+		
+		vbo = glGenBuffers();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, (FloatBuffer)BufferUtils.createFloatBuffer(12).put(new float[] { 0.25f,  0.25f, 0.0f, 1.0f,
+																									   0.25f, -0.25f, 0.0f, 1.0f,
+																									  -0.25f, -0.25f, 0.0f, 1.0f}).flip(), GL_STATIC_DRAW);
+		
+		glBindVertexArray(glGenVertexArrays());
+		
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void update(long deltaTime) {
+		elapsedTime += deltaTime;
+	}
+	
+	@Override
+	public void render() {
+		glClear(GL_COLOR_BUFFER_BIT);
+		
+		program.begin();
+		
+		glUniform1f(timeLocation, elapsedTime/(float)1e9);
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0);
+		
+		glDrawArrays(GL_TRIANGLES, 0, 3);
+		
+		glDisableVertexAttribArray(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		
+		program.end();
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example3/example3.3.frag

+#version 330
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = vec4(1, 1, 1, 1);
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example3/example3.3.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+uniform float loopDuration;
+uniform float time;
+
+void main()
+{
+	float timeScale = 3.14159f * 2.0f / loopDuration;
+	
+	float currTime = mod(time, loopDuration);
+	
+	gl_Position = position + vec4(cos(currTime * timeScale) * 0.5f,
+								  sin(currTime * timeScale) * 0.5f,
+								  0.0f,
+								  0.0f);
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example4/Example3_4.java

+package com.ra4king.opengl.arcsynthesis.chapter3.example4;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.ShaderProgram;
+
+public class Example3_4 extends GLProgram {
+	public static void main(String[] args) {
+		new Example3_4().run(true);
+	}
+	
+	private ShaderProgram program;
+	private int timeLocation;
+	private int vbo;
+	
+	private long elapsedTime;
+	
+	public Example3_4() {
+		super("Example 3.4", 500, 500, false);
+	}
+	
+	@Override
+	public void init() {
+		glClearColor(0, 0, 0, 0);
+		
+		program = new ShaderProgram(readFromFile("example3.4.vert"),readFromFile("example3.4.frag"));
+		timeLocation = glGetUniformLocation(program.getProgram(), "time");
+		
+		int loopDurationLocation = glGetUniformLocation(program.getProgram(), "loopDuration");
+		int fragLoopDuration = glGetUniformLocation(program.getProgram(), "fragLoopDuration");
+		program.begin();
+		glUniform1f(loopDurationLocation, 5);
+		glUniform1f(fragLoopDuration, 10);
+		program.end();
+		
+		vbo = glGenBuffers();
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glBufferData(GL_ARRAY_BUFFER, (FloatBuffer)BufferUtils.createFloatBuffer(12).put(new float[] { 0.25f,  0.25f, 0.0f, 1.0f,
+																									   0.25f, -0.25f, 0.0f, 1.0f,
+																									  -0.25f, -0.25f, 0.0f, 1.0f}).flip(), GL_STATIC_DRAW);
+		
+		glBindVertexArray(glGenVertexArrays());
+		
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+	}
+	
+	@Override
+	public void update(long deltaTime) {
+		elapsedTime += deltaTime;
+	}
+	
+	@Override
+	public void render() {
+		glClear(GL_COLOR_BUFFER_BIT);
+		
+		program.begin();
+		
+		glUniform1f(timeLocation, elapsedTime/(float)1e9);
+		
+		glBindBuffer(GL_ARRAY_BUFFER, vbo);
+		glEnableVertexAttribArray(0);
+		glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0);
+		
+		glDrawArrays(GL_TRIANGLES, 0, 3);
+		
+		glDisableVertexAttribArray(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		
+		program.end();
+	}
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example4/example3.4.frag

+#version 330
+
+out vec4 outputColor;
+
+uniform float fragLoopDuration;
+uniform float time;
+
+const vec4 firstColor = vec4(1, 1, 1, 1);
+const vec4 secondColor = vec4(0, 1, 0, 1);
+
+void main()
+{
+	float currTime = mod(time, fragLoopDuration);
+	float currLerp = currTime/ fragLoopDuration;
+	
+	outputColor = mix(firstColor, secondColor, currLerp);
+}

src/com/ra4king/opengl/arcsynthesis/chapter3/example4/example3.4.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+uniform float loopDuration;
+uniform float time;
+
+void main()
+{
+	float timeScale = 3.14159f * 2.0f / loopDuration;
+	
+	float currTime = mod(time, loopDuration);
+	
+	gl_Position = position + vec4(cos(currTime * timeScale) * 0.5f,
+								  sin(currTime * timeScale) * 0.5f,
+								  0.0f,
+								  0.0f);
+}

src/com/ra4king/opengl/arcsynthesis/chapter4/example1/Example4_1.java

+package com.ra4king.opengl.arcsynthesis.chapter4.example1;
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import com.ra4king.opengl.GLProgram;
+import com.ra4king.opengl.ShaderProgram;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+
+public class Example4_1 extends GLProgram {
+	public static void main(String[] args) {
+		new Example4_1().run(true);
+	}
+	
+	private final float[] data = {
+			 0.25f,  0.25f, 0.75f, 1.0f,
+			 0.25f, -0.25f, 0.75f, 1.0f,
+			-0.25f,  0.25f, 0.75f, 1.0f,
+			
+			 0.25f, -0.25f, 0.75f, 1.0f,
+			-0.25f, -0.25f, 0.75f, 1.0f,
+			-0.25f,  0.25f, 0.75f, 1.0f,
+			
+			 0.25f,  0.25f, -0.75f, 1.0f,
+			-0.25f,  0.25f, -0.75f, 1.0f,
+			 0.25f, -0.25f, -0.75f, 1.0f,
+			
+			 0.25f, -0.25f, -0.75f, 1.0f,
+			-0.25f,  0.25f, -0.75f, 1.0f,
+			-0.25f, -0.25f, -0.75f, 1.0f,
+			
+			-0.25f,  0.25f,  0.75f, 1.0f,
+			-0.25f, -0.25f,  0.75f, 1.0f,
+			-0.25f, -0.25f, -0.75f, 1.0f,