/** * @(#) pointblast.java * @(#) author: Mark J. Kilgard (converted to Java by Ron Cemer) */ /* Copyright (c) Mark J. Kilgard, 1997. */ /* This program is freely distributable without licensing fees and is provided without guarantee or warrantee expressed or implied. This program is -not- in the public domain. */ /* This example demonstrates how to render particle effects with OpenGL. A cloud of pinkish/orange particles explodes with the particles bouncing off the ground. When the EXT_point_parameters is present , the particle size is attenuated based on eye distance. */ import java.applet.*; import java.awt.*; import java.awt.event.*; import java.lang.*; import java.util.*; import java.io.*; import java.util.*; import gl4java.GLContext; import gl4java.awt.GLAnimCanvas; import gl4java.applet.SimpleGLAnimApplet1; public class pointblast extends SimpleGLAnimApplet1 { /* Initialize the applet */ public void init() { super.init(); Dimension d = getSize(); canvas = new pointblastCanvas(d.width, d.height); add("Center", canvas); } /* Local GLAnimCanvas extension class */ private class pointblastCanvas extends GLAnimCanvas implements MouseListener, MouseMotionListener, ActionListener { private final double M_PI = 3.14159265; private final String MENU_PAUSE = "Pause"; private final String MENU_RESUME = "Resume"; private final String MENU_RESET_TIME = "Reset time"; // private final String MENU_CONSTANT = "Constant"; // private final String MENU_LINEAR = "Linear"; // private final String MENU_QUADRATIC = "Quadratic"; private final String MENU_BLEND_ON = "Blend on"; private final String MENU_BLEND_OFF = "Blend off"; // private final String MENU_THRESHOLD_1 = "Threshold 1"; // private final String MENU_THRESHOLD_10 = "Threshold 10"; private final String MENU_POINT_SMOOTH_ON = "Point smooth on"; private final String MENU_POINT_SMOOTH_OFF = "Point smooth off"; private final String MENU_POINT_SIZE_2 = "Point size 2"; private final String MENU_POINT_SIZE_4 = "Point size 4"; private final String MENU_POINT_SIZE_8 = "Point size 8"; private final String MENU_ENABLE_SPIN = "Enable spin"; private final String MENU_DISABLE_SPIN = "Disable spin"; private final String MENU_200_POINTS = "200 points"; private final String MENU_500_POINTS = "500 points"; private final String MENU_1000_POINTS = "1000 points"; private final String MENU_2000_POINTS = "2000 points"; private final String MENU_ENABLE_TEXTURING = "Enable texturing"; private final String MENU_DISABLE_TEXTURING = "Disable texturing"; private final String MENU_LINEAR_TEXTURE_FILTERING = "Linear texture filtering"; private final String MENU_POINT_TEXTURE_FILTERING = "Point texture filtering"; private PopupMenu menu = null; private boolean menu_showing = false; private boolean save_suspended = false; private float angle = -150; /* in degrees */ private boolean spin = false; private boolean moving = false; private int begin; private boolean newModel = true; private float theTime; private boolean repeat = true; private boolean blend = true; private boolean texturing = true; // private boolean useMipmaps = true; private boolean linearFiltering = true; private float constant[] = { 1.0f/5.0f, 0.0f, 0.0f }; private float linear[] = { 0.0f, 1.0f/5.0f, 0.0f }; private float theQuad[] = { 0.25f, 0.0f, 1.0f/60.0f }; private final int MAX_POINTS = 2000; private int numPoints = 200; private float pointList[][] = new float[MAX_POINTS][3]; private float pointTime[] = new float[MAX_POINTS]; private float pointVelocity[][] = new float[MAX_POINTS][2]; private float pointDirection[][] = new float[MAX_POINTS][2]; private int colorList[] = new int[MAX_POINTS]; private boolean motion = false; private Random random = new Random(System.currentTimeMillis()); private final float MEAN_VELOCITY = 3.0f; private final float GRAVITY = 2.0f; private final float TIME_DELTA = 0.025f; /* The speed of time. */ /* Modeling units of ground extent in each X and Z direction. */ private final int EDGE = 12; private final float colorSet[][] = { /* Shades of red. */ { 0.7f, 0.2f, 0.4f, 0.5f }, { 0.8f, 0.0f, 0.7f, 0.5f }, { 1.0f, 0.0f, 0.0f, 0.5f }, { 0.9f, 0.3f, 0.6f, 0.5f }, { 1.0f, 0.4f, 0.0f, 0.5f }, { 1.0f, 0.0f, 0.5f, 0.5f } }; private final int NUM_COLORS = colorSet.length; private final int DEAD = NUM_COLORS; public pointblastCanvas(int w, int h) { super(w, h); GLContext.gljNativeDebug = false; GLContext.gljClassDebug = false; setAnimateFps(30.0); } public void preInit() { doubleBuffer = true; stereoView = false; } public void init() { System.out.println("init(): " + this); reshape(getSize().width, getSize().height); gl.glShadeModel(GL_FLAT); gl.glEnable(GL_DEPTH_TEST); gl.glEnable(GL_POINT_SMOOTH); gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); gl.glPointSize(8.0f); // gl.glPointParameterfvEXT(GL_DISTANCE_ATTENUATION_EXT, theQuad); gl.glMatrixMode(GL_PROJECTION); glu.gluPerspective (/* field of view in degree */ 40.0, /* aspect ratio */ 1.0, /* Z near */ 0.5, /* Z far */ 40.0); gl.glMatrixMode(GL_MODELVIEW); glu.gluLookAt (0.0f, 1.0f, 8.0f, /* eye location */ 0.0f, 1.0f, 0.0f, /* center is at (0,0,0) */ 0.0f, 1.0f, 0.0f); /* up is in postivie Y direction */ gl.glPushMatrix(); /* dummy push so we can pop on model recalc */ makePointList(); makeFloorTexture(); glj.gljCheckGL(); menu = new PopupMenu("Options"); menu.add(MENU_PAUSE); menu.add(MENU_RESUME); menu.add(MENU_RESET_TIME); // menu.add(MENU_CONSTANT); // menu.add(MENU_LINEAR); // menu.add(MENU_QUADRATIC); menu.add(MENU_BLEND_ON); menu.add(MENU_BLEND_OFF); // menu.add(MENU_THRESHOLD_1); // menu.add(MENU_THRESHOLD_10); menu.add(MENU_POINT_SMOOTH_ON); menu.add(MENU_POINT_SMOOTH_OFF); menu.add(MENU_POINT_SIZE_2); menu.add(MENU_POINT_SIZE_4); menu.add(MENU_POINT_SIZE_8); menu.add(MENU_ENABLE_SPIN); menu.add(MENU_DISABLE_SPIN); menu.add(MENU_200_POINTS); menu.add(MENU_500_POINTS); menu.add(MENU_1000_POINTS); menu.add(MENU_2000_POINTS); menu.add(MENU_ENABLE_TEXTURING); menu.add(MENU_DISABLE_TEXTURING); menu.add(MENU_LINEAR_TEXTURE_FILTERING); menu.add(MENU_POINT_TEXTURE_FILTERING); menu.addActionListener(this); add(menu); addMouseListener(this); addMouseMotionListener(this); } public void doCleanup() { System.out.println("destroy(): " + this); removeMouseListener(this); removeMouseMotionListener(this); menu.removeActionListener(this); } public void reshape(int width, int height) { gl.glViewport(0,0,width,height); } public void display() { if (glj.gljMakeCurrent() == false) return; if (!isSuspended()) { if (spin) { angle += 0.3f; newModel = true; } updatePointList(); } gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (newModel) recalcModelView(); gl.glDepthMask(false); /* Draw the floor. */ if (texturing) gl.glEnable(GL_TEXTURE_2D); gl.glColor3f(0.5f, 1.0f, 0.5f); gl.glBegin(GL_QUADS); gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-EDGE, -0.05f, -EDGE); gl.glTexCoord2f(20.0f, 0.0f); gl.glVertex3f(EDGE, -0.05f, -EDGE); gl.glTexCoord2f(20.0f, 20.0f); gl.glVertex3f(EDGE, -0.05f, EDGE); gl.glTexCoord2f(0.0f, 20.0f); gl.glVertex3f(-EDGE, -0.05f, EDGE); gl.glEnd(); /* Allow particles to blend with each other. */ gl.glDepthMask(true); if (blend) gl.glEnable(GL_BLEND); if (texturing) gl.glDisable(GL_TEXTURE_2D); gl.glBegin(GL_POINTS); for (int i = 0; i < numPoints; i++) { /* Draw alive particles. */ int c = colorList[i]; if ( (c < 0) || (c >= DEAD) ) continue; gl.glColor4fv(colorSet[colorList[i]]); gl.glVertex3fv(pointList[i]); } gl.glEnd(); gl.glDisable(GL_BLEND); glj.gljSwap(); glj.gljCheckGL(); glj.gljFree(); //if (!isSuspended()) repaint(); // Animate at full speed. } // Methods required for the implementation of MouseListener public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } public void mousePressed(MouseEvent evt) { // If user presses right mouse button within canvas area, // suspend animation and pop up menu. // If menu was already popped up and user presses either // mouse button within canvas area, resume animation // because the menu will have been removed automatically. if (!menu_showing) { if ((evt.getModifiers() & evt.BUTTON3_MASK) != 0) { menu_showing = true; save_suspended = isSuspended(); if (!save_suspended) { setSuspended(true); repaint(100); try { Thread.currentThread().sleep(200); } catch (Exception e) { } } menu.show(this,evt.getX(),evt.getY()); } else { // Must be left button. moving = true; begin = evt.getX(); } } else { menu_showing = false; setSuspended(save_suspended); } } public void mouseReleased(MouseEvent evt) { if ((evt.getModifiers() & evt.BUTTON3_MASK) == 0) { // Must be left button. moving = false; } } public void mouseClicked(MouseEvent evt) { } // Methods required for the implementation of MouseMotionListener public void mouseDragged(MouseEvent evt) { if (moving) { int x = evt.getX(); angle = angle + (x - begin); begin = x; newModel = true; if (isSuspended()) repaint(); } } public void mouseMoved(MouseEvent evt) { } // Method required for the implementation of ActionListener public void actionPerformed(ActionEvent evt) { if (glj.gljMakeCurrent() == false) return; boolean dorepaint = false; String c = evt.getActionCommand(); if (c.equals(MENU_PAUSE)) { if (menu_showing) save_suspended = true; else if (!isSuspended()) setSuspended(true); dorepaint = true; } else if (c.equals(MENU_RESUME)) { if (menu_showing) save_suspended = false; else if (isSuspended()) setSuspended(false); dorepaint = true; } else if (c.equals(MENU_RESET_TIME)) { makePointList(); dorepaint = true; } // else if (c.equals(MENU_CONSTANT)) // { // gl.glPointParameterfvEXT(GL_DISTANCE_ATTENUATION_EXT, constant); // dorepaint = true; // } // else if (c.equals(MENU_LINEAR)) // { // gl.glPointParameterfvEXT(GL_DISTANCE_ATTENUATION_EXT, linear); // dorepaint = true; // } // else if (c.equals(MENU_QUADRATIC)) // { // gl.glPointParameterfvEXT(GL_DISTANCE_ATTENUATION_EXT, theQuad); // dorepaint = true; // } else if (c.equals(MENU_BLEND_ON)) { blend = true; dorepaint = true; } else if (c.equals(MENU_BLEND_OFF)) { blend = false; dorepaint = true; } // else if (c.equals(MENU_THRESHOLD_1)) // { // gl.glPointParameterfEXT(GL_POINT_FADE_THRESHOLD_SIZE_EXT, 1.0f); // dorepaint = true; // } // else if (c.equals(MENU_THRESHOLD_10)) // { // gl.glPointParameterfEXT(GL_POINT_FADE_THRESHOLD_SIZE_EXT, 10.0f); // dorepaint = true; // } else if (c.equals(MENU_POINT_SMOOTH_ON)) { gl.glEnable(GL_POINT_SMOOTH); dorepaint = true; } else if (c.equals(MENU_POINT_SMOOTH_OFF)) { gl.glDisable(GL_POINT_SMOOTH); dorepaint = true; } else if (c.equals(MENU_POINT_SIZE_2)) { gl.glPointSize(2.0f); dorepaint = true; } else if (c.equals(MENU_POINT_SIZE_4)) { gl.glPointSize(4.0f); dorepaint = true; } else if (c.equals(MENU_POINT_SIZE_8)) { gl.glPointSize(8.0f); dorepaint = true; } else if (c.equals(MENU_ENABLE_SPIN)) { spin = true; dorepaint = true; } else if (c.equals(MENU_DISABLE_SPIN)) { spin = false; dorepaint = true; } else if (c.equals(MENU_200_POINTS)) { numPoints = 200; makePointList(); dorepaint = true; } else if (c.equals(MENU_500_POINTS)) { numPoints = 500; makePointList(); dorepaint = true; } else if (c.equals(MENU_1000_POINTS)) { numPoints = 1000; makePointList(); dorepaint = true; } else if (c.equals(MENU_2000_POINTS)) { numPoints = 2000; makePointList(); dorepaint = true; } else if (c.equals(MENU_ENABLE_TEXTURING)) { texturing = true; dorepaint = true; } else if (c.equals(MENU_DISABLE_TEXTURING)) { texturing = false; dorepaint = true; } else if (c.equals(MENU_LINEAR_TEXTURE_FILTERING)) { linearFiltering = true; makeFloorTexture(); dorepaint = true; } else if (c.equals(MENU_POINT_TEXTURE_FILTERING)) { linearFiltering = false; makeFloorTexture(); dorepaint = true; } glj.gljFree(); if (menu_showing) { menu_showing = false; setSuspended(save_suspended); } if ( (isSuspended()) && (dorepaint) ) repaint(); } private float float_rand() { return random.nextFloat(); } private float RANDOM_RANGE(float lo, float hi) { return lo + ((hi - lo) * float_rand()); } private void makePointList() { float angle, velocity, direction; motion = true; for (int i = 0; i < numPoints; i++) { pointList[i][0] = 0.0f; pointList[i][1] = 0.0f; pointList[i][2] = 0.0f; pointTime[i] = 0.0f; angle = (RANDOM_RANGE(60.0f, 70.0f)) * ((float)M_PI)/180.0f; direction = RANDOM_RANGE(0.0f, 360.0f) * ((float)M_PI)/180.0f; pointDirection[i][0] = (float)Math.cos(direction); pointDirection[i][1] = (float)Math.sin(direction); velocity = MEAN_VELOCITY + RANDOM_RANGE(-0.8f, 1.0f); pointVelocity[i][0] = velocity * (float)Math.cos(angle); pointVelocity[i][1] = velocity * (float)Math.sin(angle); colorList[i] = random.nextInt() % NUM_COLORS; } theTime = 0.0f; } private void updatePointList() { float distance; motion = false; for (int i = 0; i < numPoints; i++) { distance = pointVelocity[i][0] * theTime; /* X and Z */ pointList[i][0] = pointDirection[i][0] * distance; pointList[i][2] = pointDirection[i][1] * distance; /* Z */ pointList[i][1] = (pointVelocity[i][1] - 0.5f * GRAVITY * pointTime[i])*pointTime[i]; /* If we hit the ground, bounce the point upward again. */ if (pointList[i][1] <= 0.0f) { if (distance > EDGE) { /* Particle has hit ground past the distance duration of the particles. Mark particle as dead. */ colorList[i] = DEAD; /* Not moving. */ continue; } pointVelocity[i][1] *= 0.8f; /* 80% of previous up velocity. */ pointTime[i] = 0.0f; /* Reset the particles sense of up time. */ } motion = true; pointTime[i] += TIME_DELTA; } theTime += TIME_DELTA; if ( (repeat) && (!motion) ) makePointList(); } private void recalcModelView() { gl.glPopMatrix(); gl.glPushMatrix(); gl.glRotatef(angle, 0.0f, 1.0f, 0.0f); newModel = false; } private void makeFloorTexture() { /* Nice floor texture tiling pattern. */ String circles[] = { "....xxxx........", "..xxxxxxxx......", ".xxxxxxxxxx.....", ".xxx....xxx.....", "xxx......xxx....", "xxx......xxx....", "xxx......xxx....", "xxx......xxx....", ".xxx....xxx.....", ".xxxxxxxxxx.....", "..xxxxxxxx......", "....xxxx........", "................", "................", "................", "................" }; byte floorTexture[] = new byte[16*16*3]; /* Setup RGB image for the texture. */ int idx = 0; for (int t = 0; t < 16; t++) { for (int s = 0; s < 16; s++) { if (circles[t].charAt(s) == 'x') { /* Nice blue. */ floorTexture[idx++] = (byte)0x1f; floorTexture[idx++] = (byte)0x1f; floorTexture[idx++] = (byte)0x8f; } else { /* Light gray. */ floorTexture[idx++] = (byte)0xca; floorTexture[idx++] = (byte)0xca; floorTexture[idx++] = (byte)0xca; } } } gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // if (useMipmaps) // { // gl.glTexParameteri // (GL_TEXTURE_2D, // GL_TEXTURE_MIN_FILTER, // GL_LINEAR_MIPMAP_LINEAR); // glu.gluBuild2DMipmaps // (GL_TEXTURE_2D,3,16,16,GL_RGB,GL_UNSIGNED_BYTE,floorTexture); // } // else { if (linearFiltering) { gl.glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl.glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { gl.glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); gl.glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } gl.glTexImage2D (GL_TEXTURE_2D,0,3,16,16,0,GL_RGB,GL_UNSIGNED_BYTE,floorTexture); } } } }