import java.nio.*; // Native IO buffers: LWJGL uses these to efficiently exchange data with system memory
import java.io.*;
import org.lwjgl.opengl.*;
import org.lwjgl.util.glu.*;
import org.lwjgl.input.Keyboard;
import glmodel.*;
/**
* Render a model and cast a shadow on the floor.
* Uses the stencil buffer to restrict the shadow to just one surface.
*
* Requires the glmodel package to load model formats into a GL_Object class.
* Requires GLMaterial.java
* Requires Vector3D.java
*
*/
public class GLART_11_Shadow {
private boolean done = false;
private final String windowTitle = "Shadow Demo";
private DisplayMode displayMode;
private float rotateModelY = 0;
private GL_Mesh obj;
int texture;
// shadow matrix (see initOpenGL() and setShadowMatrix())
FloatBuffer fShadowMatrix;
// equation for the plane the shadow will fall on (the normal for the plane, 0,1,0)
float[] floorPlane = new float[] {0f,1f,0f,0f};
/**
* Main function just creates and runs the application.
*/
public static void main(String args[]) {
GLART_11_Shadow app = new GLART_11_Shadow();
app.run();
}
/**
* Initialize the app, then sit in a render loop until done==true.
*/
public void run() {
try {
init();
while (!done) {
mainloop();
render();
Display.update();
}
cleanup();
}
catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
/**
* Initialize the environment
* @throws Exception
*/
private void init() throws Exception {
GL_OBJ_Importer importOBJ;
GLImage textureImg;
InputStream in;
initDisplay();
initGL();
// load model
importOBJ = new GL_OBJ_Importer();
in = getInputStream("models/cow.obj");
obj = importOBJ.importFromStream(in);
obj.regenerateNormals();
// prepare a texture
textureImg = new GLImage("images/Flag_of_the_United_States.png");
texture = makeTexture(textureImg);
makeTextureMipMap(textureImg);
}
/**
* Create an OpenGL display, in this case a fullscreen window.
* @throws Exception
*/
private void initDisplay() throws Exception {
// set to full screen, no chrome
Display.setFullscreen(false);
// get all possible display resolutions
DisplayMode d[] = Display.getAvailableDisplayModes();
// find a resolution we like
for (int i = 0; i < d.length; i++) {
if (d[i].getWidth() == 800
&& d[i].getHeight() == 600
&& d[i].getBitsPerPixel() == 32) {
displayMode = d[i];
break;
}
}
// set the display to the resolution we picked
Display.setDisplayMode(displayMode);
Display.setTitle(windowTitle);
// create the window
// specify Pixel Format:
// 0 -- default bits per pixel for alpha values
// 24 -- 24 bits per pixel for depth buffer
// 8 -- set bits per pixel for stencil buffer
// NOTE: Need to set bits for Stencil buffer or shadows won't work
Display.create(new PixelFormat(0,24,8));
}
/**
* Initialize OpenGL
*
*/
private void initGL() {
// color of overall scene lighting
float ambient[] = { .1f, .1f, .1f, 1f };
// color of light source
float lightDiffuse[] = { 1f, 1f, .9f, 1f }; // direct light
float lightSpecular[] = { 1f, 1f, .9f, 1f }; // highlight
float lightAmbient[] = { .3f, .3f, .4f, 1f }; // scattered light
// light position: if last value is 0, then this describes light direction.
float lightPosition[] = { -5f, 5f, 5, 0f };
// setup material
GLMaterial material = new GLMaterial();
material = new GLMaterial();
material.setDiffuse(new float[] {1f,1f,1f,1f});
material.setAmbient(new float[] {.8f,.8f,.8f,1f});
material.apply();
// Select the Projection Matrix (controls perspective)
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
// Define perspective
GLU.gluPerspective(
30.0f, // Field Of View
(float)displayMode.getWidth() / (float)displayMode.getHeight(), // aspect ratio
0.01f, // near Z clipping plane
1000.0f); // far Z clipping plane
// Select The Modelview Matrix (controls model orientation)
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadIdentity(); // Reset The Modelview Matrix
// make sure OpenGL correctly layers objects
GL11.glEnable(GL11.GL_DEPTH_TEST);
// turn on texturing
GL11.glEnable(GL11.GL_TEXTURE_2D);
// don't draw backward facing triangles ("back faces")
GL11.glEnable(GL11.GL_CULL_FACE);
// turn lighting on
GL11.glEnable(GL11.GL_LIGHTING);
// overall scene lighting
setAmbientLight(ambient);
// Create a light
setLight( GL11.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition );
//////////////////////////////////////////////////////////////
// For Shadows: define a matrix based on light position and shadow surface
float[] fShadowMatrixArray = new float[16];
makeShadowMatrix(fShadowMatrixArray, lightPosition, floorPlane);
// convert the float array to a FloatBuffer
fShadowMatrix = allocFloats(fShadowMatrixArray);
///////////////////////////////////////////////////////////////
// set the background color
GL11.glClearColor(.5f, .5f, .8f, 1);
}
/**
* Handle keyboard input. Just check for escape key or user
* clicking to close the window.
*/
private void mainloop() {
// Check for Escape key
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
done = true;
}
// Has Window been closed
if(Display.isCloseRequested()) {
done = true;
}
}
/**
* Cleanup all the resources.
*
*/
private void cleanup() {
Display.destroy();
}
//----------------------------------------------------------------
// Rendering
//----------------------------------------------------------------
/**
* Render the scene.
*/
private void render() {
rotateModelY += .2;
// Clear screen and depth buffer
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
// reset the coordinate system to center of screen
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadIdentity();
// Place the 'camera'
GLU.gluLookAt( 0, 5, 10, // camera position
0, 2, 0, // look at a point directly in front of camera
0f, 1f, 0f); // which way is up
// draw shadow cast on floor plane
drawShadow();
// draw the object normally
drawObject();
}
/**
* draw the object that will be shadowed
*/
public void drawObject() {
GL11.glPushMatrix();
{
GL11.glTranslatef(0,1.8f,0); // move up a litte (off the floor)
GL11.glScalef(.5f,.5f,.5f); // scale model down a little
GL11.glRotatef(rotateModelY, 0, 1, 0); // rotate around vertical axis
renderMesh(obj, texture);
}
GL11.glPopMatrix();
}
/**
* draw the shadowed object from the lights point of view. this will
* create the right shape for a shadow. To flatten out the object
* we turn off lighting and texture, and just draw with a flat gray.
*/
public void drawShadow() {
// draw the shadow onto floor
GL11.glPushMatrix();
{
// no texture
GL11.glDisable(GL11.GL_TEXTURE_2D);
// no light
GL11.glDisable(GL11.GL_LIGHTING);
// no depth
GL11.glDisable(GL11.GL_DEPTH_TEST);
// dark gray color
GL11.glColor4f(.3f, .3f, .35f, 1);
// multiply the current modelview matrix with the shadow matrix
GL11.glMultMatrix(fShadowMatrix);
// draw the object to be shadowed (as if viewed from light position)
drawObject();
// re-enable settings
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_LIGHTING);
}
GL11.glPopMatrix();
}
/**
* Make a matrix that will transform the geometry as if it is
* projected onto the given plane, from the viewpoint of the given light position.
*/
void makeShadowMatrix(float[] fDestMat, float[] fLightPos, float[] fPlane)
{
float dot;
// dot product of plane and light position
dot = fPlane[0] * fLightPos[0] +
fPlane[1] * fLightPos[1] +
fPlane[2] * fLightPos[2] +
fPlane[3] * fLightPos[3];
// first column
fDestMat[0] = dot - fLightPos[0] * fPlane[0];
fDestMat[4] = 0.0f - fLightPos[0] * fPlane[1];
fDestMat[8] = 0.0f - fLightPos[0] * fPlane[2];
fDestMat[12] = 0.0f - fLightPos[0] * fPlane[3];
// second column
fDestMat[1] = 0.0f - fLightPos[1] * fPlane[0];
fDestMat[5] = dot - fLightPos[1] * fPlane[1];
fDestMat[9] = 0.0f - fLightPos[1] * fPlane[2];
fDestMat[13] = 0.0f - fLightPos[1] * fPlane[3];
// third column
fDestMat[2] = 0.0f - fLightPos[2] * fPlane[0];
fDestMat[6] = 0.0f - fLightPos[2] * fPlane[1];
fDestMat[10] = dot - fLightPos[2] * fPlane[2];
fDestMat[14] = 0.0f - fLightPos[2] * fPlane[3];
// fourth column
fDestMat[3] = 0.0f - fLightPos[3] * fPlane[0];
fDestMat[7] = 0.0f - fLightPos[3] * fPlane[1];
fDestMat[11] = 0.0f - fLightPos[3] * fPlane[2];
fDestMat[15] = dot - fLightPos[3] * fPlane[3];
}
//----------------------------------------------------------------
// render mesh
//----------------------------------------------------------------
/**
* Render mesh with normals and texture coordinates. Loops through
* all triangles in the mesh object.
*
* Several triangles may refer to the same vertex, but each face
* can have different normals for that vertex. This allows for
* sharp edges between faces.
*
* @param o mesh object to render
*/
public void renderMesh(GL_Mesh o, int textureHandle)
{
GL_Triangle t;
GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle);
GL11.glBegin(GL11.GL_TRIANGLES);
for (int j = 0; j < o.triangles.length; j++) { // draw all triangles in object
t = o.triangles[j];
GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y);
GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z);
GL11.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z);
GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y);
GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z);
GL11.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z);
GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y);
GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z);
GL11.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z);
}
GL11.glEnd();
}
//----------------------------------------------------------------
// Lighting
//----------------------------------------------------------------
/**
* Simple way to setup a light. Uses same color for direct light (diffuse),
* reflected highlight (specular) and scattered light (ambient). Ambient
* color is darkened to 1/4 of the light color.
*
* @param GLLightHandle
* @param color
* @param position
*/
public static void setLight(int GLLightHandle, float[] color, float[] position )
{
float[] ambientLight = {color[0]/4f, color[1]/4f, color[2]/4f, color[3]/4f};
FloatBuffer lightColor = allocFloats(color);
FloatBuffer ambientColor = allocFloats(ambientLight);
FloatBuffer ltPosition = allocFloats(position);
GL11.glLight(GLLightHandle, GL11.GL_DIFFUSE, lightColor); // color of the direct illumination
GL11.glLight(GLLightHandle, GL11.GL_SPECULAR, lightColor); // color of the highlight (same as direct light)
GL11.glLight(GLLightHandle, GL11.GL_AMBIENT, ambientColor); // color of the scattered light (darker)
GL11.glLight(GLLightHandle, GL11.GL_POSITION, ltPosition);
GL11.glEnable(GLLightHandle); // Enable the light (GL_LIGHT1 - 7)
}
/**
* Set the color of a 'positional' light (a light that has a specific
* position within the scene).
*
* Params:
* an OpenGL light number (GL11.GL_LIGHT1),
* 'Diffuse': color of direct light from this source,
* 'Ambient': color of scattered light from this source
* 'Specular': color of this light reflected off a surface,
* position.
*/
public static void setLight( int GLLightHandle,
float[] diffuseLightColor, float[] ambientLightColor,
float[] specularLightColor, float[] position )
{
FloatBuffer ltDiffuse = allocFloats(diffuseLightColor);
FloatBuffer ltAmbient = allocFloats(ambientLightColor);
FloatBuffer ltSpecular = allocFloats(specularLightColor);
FloatBuffer ltPosition = allocFloats(position);
GL11.glLight(GLLightHandle, GL11.GL_DIFFUSE, ltDiffuse); // color of the direct illumination
GL11.glLight(GLLightHandle, GL11.GL_AMBIENT, ltAmbient); // color of the reflected light
GL11.glLight(GLLightHandle, GL11.GL_SPECULAR, ltSpecular); // color of the highlight (same as direct light)
GL11.glLight(GLLightHandle, GL11.GL_POSITION, ltPosition);
GL11.glEnable(GLLightHandle); // Enable the light (GL_LIGHT1 - 7)
//GL11.glLightf(GLLightHandle, GL11.GL_QUADRATIC_ATTENUATION, .005F); // how light beam drops off
}
/**
* Set the position of a light to the given xyz. NOTE: Positional light only,
* not directional.
*/
public static void setLightPos(int GLLightHandle, float x, float y, float z)
{
float[] position = new float[] {x,y,z,1};
GL11.glLight(GLLightHandle, GL11.GL_POSITION, allocFloats(position));
}
/**
* Set the color of the Global Ambient Light. Affects all objects in
* scene regardless of their placement.
*/
public static void setAmbientLight(float[] ambientLightColor) {
FloatBuffer ltAmbient = allocFloats(ambientLightColor);
GL11.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, ltAmbient);
}
/**
* Open a file InputStream and trap errors.
* @param filenameOrURL
* @return
*/
public static InputStream getInputStream(String filename) {
InputStream in = null;
try {
in = new FileInputStream(filename);
}
catch (IOException ioe) {
System.out.println("getInputStream (" + filename + "): " + ioe);
if (in != null) {
try {
in.close();
in = null;
}
catch (Exception e) {}
}
}
catch (Exception e) {
System.out.println("getInputStream (" + filename + "): " + e);
}
return in;
}
/**
* Create a texture from the given image.
*/
public static int makeTexture(GLImage textureImg)
{
if ( textureImg == null ) {
return 0;
}
else {
return makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h);
}
}
/**
* Create a texture from the given pixels in RGBA format. Set the texture
* to repeat in both directions and use LINEAR for magnification.
* @return the texture handle
*/
public static int makeTexture(ByteBuffer pixels, int w, int h)
{
// get a new empty texture
int textureHandle = allocateTexture();
// 'select' the new texture by it's handle
GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle);
// set texture parameters:
// how to wrap texture
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
// how to scale up texture
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST);
// Create the texture from pixels
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, // type of texture we're creating
0, // level-of-detail: use 0
GL11.GL_RGBA, // texture pixel format
w, h, // width and height of texture image (powers of 2)
0, // widtt of the border (either 0 or 1, use 0)
GL11.GL_RGBA, // image pixel format
GL11.GL_UNSIGNED_BYTE, // image pixel data type
pixels // image pixel data
);
return textureHandle;
}
/**
* Build "Mipmap" for currently selected texture. Builds different
* versions of the texture image at lower levels of detail, so texture
* can scale down gracefully at longer distances.
*
* @param textureImg the texture image
* @return error code of buildMipMap call
*/
public static int makeTextureMipMap(GLImage textureImg)
{
int ret = GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, 4, textureImg.w,
textureImg.h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, textureImg.getPixelsRGBA());
if (ret != 0) {
System.out.println("GLApp.makeTextureMipMap(): Error occured while building mip map, ret=" + ret);
}
//Assign the mip map levels and texture info
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_NEAREST);
GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
return ret;
}
/**
* Allocate a texture (glGenTextures) and return the handle to it.
*/
public static int allocateTexture()
{
IntBuffer textureHandle = allocInts(1);
GL11.glGenTextures(textureHandle);
return textureHandle.get(0);
}
//----------------------------------------------------------------
// Native IO buffer functions
//----------------------------------------------------------------
public static final int SIZE_FLOAT = 4;
public static final int SIZE_INT = 4;
public static FloatBuffer allocFloats(int howmany) {
return ByteBuffer.allocateDirect(howmany * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();
}
public static FloatBuffer allocFloats(float[] floatarray) {
FloatBuffer fb = ByteBuffer.allocateDirect(floatarray.length * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();
fb.put(floatarray).flip();
return fb;
}
public static IntBuffer allocInts(int howmany) {
return ByteBuffer.allocateDirect(howmany * SIZE_INT).order(ByteOrder.nativeOrder()).asIntBuffer();
}
}