3D编程指南第三部分:粒子系统和立即模式渲染(2)

juney Post at 2006/7/28 14:56:00
TutorialMidlet
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class TutorialMidlet extends MIDlet implements CommandListener
{
    // A variable that holds the unique display
 private Display display = null;
 
 // The canvas
 private M3GCanvas canvas = null;
 
 // The MIDlet itself
 private static MIDlet self = null;
 /** Called when the application starts, and when it is resumed.
  * We ignore the resume here and allocate data for our canvas
  * in the startApp method. This is generally very bad practice.
  */
 protected void startApp() throws MIDletStateChangeException
 {
     // Allocate
  display = Display.getDisplay(this);
  canvas = new M3GCanvas(30);
 
  // Add a quit command to the canvas
  // This command won't be seen, as we
  // are running in fullScreen mode
  // but it's always nice to have a quit command
  canvas.addCommand(new Command("Quit", Command.EXIT, 1));
 
  // Set the listener to be the MIDlet
  canvas.setCommandListener(this);
 
  // Start canvas
  canvas.start();
  display.setCurrent(canvas);
 
  // Set the self
  self = this;
 }
 /** Called when the game should pause, such as during a call */
 protected void pauseApp()
 {
 
 }
 /** Called when the application should shut down */
 protected void destroyApp(boolean unconditional) throws MIDletStateChangeException
 {
     // Method that shuts down the entire MIDlet
  notifyDestroyed();
 }
 /** Listens to commands and processes */
    public void commandAction(Command c, Displayable d) {
        // If we get an EXIT command we destroy the application
        if(c.getCommandType() == Command.EXIT)
            notifyDestroyed();
    }
   
    /** Static method that quits our application
     * by using the static field 'self' */
    public static void die()
    {
        self.notifyDestroyed();
    }
}
 
M3GCanvas
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.m3g.Background;
import javax.microedition.m3g.Camera;
import javax.microedition.m3g.Graphics3D;
import javax.microedition.m3g.Light;
import javax.microedition.m3g.Transform;
public class M3GCanvas
extends GameCanvas
implements Runnable {
    // Thread-control
    boolean running = false;
    boolean done = true;
   
    // If the game should end
    public static boolean gameOver = false;
   
    // Rendering hints
    public static final int STRONG_RENDERING_HINTS = Graphics3D.ANTIALIAS | Graphics3D.TRUE_COLOR | Graphics3D.DITHER;
    public static final int WEAK_RENDERING_HINTS = 0;
    public static int RENDERING_HINTS = STRONG_RENDERING_HINTS;
   
    // Key array
    boolean[] key = new boolean[5];
   
    // Key constants
    public static final int FIRE = 0;
    public static final int UP = FIRE + 1;
    public static final int DOWN = UP + 1;
    public static final int LEFT = DOWN + 1;
    public static final int RIGHT = LEFT + 1;
   
    // Global identity matrix
    Transform identity = new Transform();
   
    // Global Graphics3D object
    Graphics3D g3d = null;
   
    // The background
    Background back = null;
   
    // The global camera object
    Camera cam = null;
   
    // The particle system
    ParticleSystem ps = null;
    FountainEffect fx = null;
   
    /** Constructs the canvas
     */
    public M3GCanvas(int fps)
    {
        // We don't want to capture keys normally
        super(true);
       
        // We want a fullscreen canvas
        setFullScreenMode(true);
       
        // Load our camera
        loadCamera();
       
        // Load our background
        loadBackground();
       
        // Set up graphics 3d
        setUp();
    }
   
    /** Prepares the Graphics3D engine for immediate mode rendering by adding a light */
    private void setUp()
    {
        // Get the instance
        g3d = Graphics3D.getInstance();
       
        // Add a light to our scene, so we can see something
        g3d.addLight(createAmbientLight(), identity);
    }
   
   
    /** Creates a simple ambient light */
    private Light createAmbientLight()
    {
        Light l = new Light();
        l.setMode(Light.AMBIENT);
        l.setIntensity(1.0f);
        return l;
    }
    /** When fullscreen mode is set, some devices will call
     * this method to notify us of the new width/height.
     * However, we don't really care about the width/height
     * in this tutorial so we just let it be
     */
    public void sizeChanged(int newWidth, int newHeight)
    {
       
    }
   
    /** Loads our camera */
    private void loadCamera()
    {
        // Create a new camera
        cam = new Camera();
    }
   
    /** Loads the background */
    private void loadBackground()
    {
        // Create a new background, set bg color to black
        back = new Background();
        back.setColor(0);
    }
    /** Draws to screen
     */   
    private void draw(Graphics g)
    {
        // Envelop all in a try/catch block just in case
        try
        {           
            // Get the Graphics3D context
            g3d = Graphics3D.getInstance();
           
         // First bind the graphics object. We use our pre-defined rendering hints.
         g3d.bindTarget(g, true, RENDERING_HINTS);
        
         // Clear background
         g3d.clear(back);
        
         // Bind camera at fixed position in origo
         g3d.setCamera(cam, identity);
        
         // Init particles
         if(ps == null)
         {
             fx = new FountainEffect(90);
             ps = new ParticleSystem(fx, 20);
         }
        
         // Emit the particles
         ps.emit(g3d);
        
         // Check controls for fountain rotation
         if(key[LEFT])
             fx.setAngle(fx.getAngle() + 5);
         if(key[RIGHT])
             fx.setAngle(fx.getAngle() - 5);
        
         // Quit if user presses fire
         if(key[FIRE])
             TutorialMidlet.die();
        }
        catch(Exception e)
        {
            reportException(e);
        }
        finally
        {
            // Always remember to release!
            g3d.releaseTarget();
        }
    }
    /** Starts the canvas by firing up a thread
     */
    public void start() {
        Thread myThread = new Thread(this);
       
        // Make sure we know we are running
        running = true;
        done = false;
       
        // Start
        myThread.start();
    }
   
    /** Run, runs the whole thread. Also keeps track of FPS
     */
    public void run() {
        while(running) {
            try {               
                // Call the process method (computes keys)
                process();
               
                // Draw everything
                draw(getGraphics());
                flushGraphics();
               
                // Sleep to prevent starvation
                try{ Thread.sleep(30); } catch(Exception e) {}
            }
            catch(Exception e) {
                reportException(e);
            }
        }
       
        // Notify completion
        done = true;
    }
   
    /**
     * @param e
     */
    private void reportException(Exception e) {
        System.out.println(e.getMessage());
        System.out.println(e);
        e.printStackTrace();
    }
    /** Pauses the game
     */
    public void pause() {}
   
    /** Stops the game
     */
    public void stop() { running = false; }
   
    /** Processes keys
     */
    protected void process()
    {
        int keys = getKeyStates();
       
        if((keys & GameCanvas.FIRE_PRESSED) != 0)
            key[FIRE] = true;
        else
            key[FIRE] = false;
       
        if((keys & GameCanvas.UP_PRESSED) != 0)
            key[UP] = true;
        else
            key[UP] = false;
       
        if((keys & GameCanvas.DOWN_PRESSED) != 0)
            key[DOWN] = true;
        else
            key[DOWN] = false;
       
        if((keys & GameCanvas.LEFT_PRESSED) != 0)
            key[LEFT] = true;
        else
            key[LEFT] = false;
       
        if((keys & GameCanvas.RIGHT_PRESSED) != 0)
            key[RIGHT] = true;
        else
            key[RIGHT] = false;
    }
   
    /** Checks if thread is running
     */
    public boolean isRunning() { return running; }
   
    /** checks if thread has finished its execution completely
     */
    public boolean isDone() { return done; }
}
MeshFactory
import javax.microedition.lcdui.Image;
import javax.microedition.m3g.Appearance;
import javax.microedition.m3g.Image2D;
import javax.microedition.m3g.IndexBuffer;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.PolygonMode;
import javax.microedition.m3g.Texture2D;
import javax.microedition.m3g.TriangleStripArray;
import javax.microedition.m3g.VertexArray;
import javax.microedition.m3g.VertexBuffer;
/**
 * Static class that handles creation of code-generated Meshes
 */
public class MeshFactory
{
    /** Creates a texture plane that is alpha-blended
     *
     * @param texFilename The name of the texture image file
     * @param cullFlags The flags for culling. See PolygonMode.
     * @param alpha The alpha value of blending. Is a full color in 0xAARRGGBB format
     * @return The finished textured mesh
     */
    public static Mesh createAlphaPlane(String texFilename, int cullFlags, int alpha)
    {
        // Create a normal mesh
        Mesh mesh = createPlane(texFilename, cullFlags);
       
        // Make it blended
        MeshOperator.convertToBlended(mesh, alpha, Texture2D.FUNC_BLEND);
        return mesh;
    }
   
    /**
     * Creates a textured plane.
     * @param texFilename The name of the texture image file
     * @param cullFlags The flags for culling. See PolygonMode.
     * @return The finished textured mesh
     */
    public static Mesh createPlane(String texFilename, int cullFlags)
    {
        // The vertrices of the plane
        short vertrices[] = new short[] {-1, -1, 0,
                                       1, -1, 0,
                                       1, 1, 0,
                                       -1, 1, 0};
        // Texture coords of the plane
        short texCoords[] = new short[] {0, 255,
                                         255, 255,
                                         255, 0,
                                         0, 0};
       
        // The classes
        VertexArray vertexArray, texArray;
        IndexBuffer triangles;
        // Create the model's vertrices
        vertexArray = new VertexArray(vertrices.length/3, 3, 2);
        vertexArray.set(0, vertrices.length/3, vertrices);
       
        // Create the model's texture coords
        texArray = new VertexArray(texCoords.length / 2, 2, 2);
        texArray.set(0, texCoords.length / 2, texCoords);
       
        // Compose a VertexBuffer out of the previous vertrices and texture coordinates
        VertexBuffer vertexBuffer = new VertexBuffer();
        vertexBuffer.setPositions(vertexArray, 1.0f, null);
        vertexBuffer.setTexCoords(0, texArray, 1.0f/255.0f, null);
       
        // Create indices and face lengths
        int indices[] = new int[] {0, 1, 3, 2};
        int[] stripLengths = new int[] {4};
       
        // Create the model's triangles
        triangles = new TriangleStripArray(indices, stripLengths);
        // Create the appearance
        Appearance appearance = new Appearance();
        PolygonMode pm = new PolygonMode();
        pm.setCulling(cullFlags);
        appearance.setPolygonMode(pm);
        // Create and set the texture
        try
        {
            // Open image
            Image texImage = Image.createImage(texFilename);
            Texture2D theTexture = new Texture2D(new Image2D(Image2D.RGBA, texImage));
           
            // Replace the mesh's original colors (no blending)
            theTexture.setBlending(Texture2D.FUNC_REPLACE);
           
            // Set wrapping and filtering
            theTexture.setWrapping(Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP);
            theTexture.setFiltering(Texture2D.FILTER_BASE_LEVEL, Texture2D.FILTER_NEAREST);
            // Add texture to the appearance
            appearance.setTexture(0, theTexture);
        }
        catch(Exception e)
        {
            // Something went wrong
            System.out.println("Failed to create texture");
            System.out.println(e);
        }
       
        // Finally create the Mesh
        Mesh mesh = new Mesh(vertexBuffer, triangles, appearance);
        // All done
        return mesh;
    }
}
MeshOperator
import javax.microedition.m3g.CompositingMode;
import javax.microedition.m3g.Mesh;
/**
 * Performs some basic operations on Mesh objects
 */
public class MeshOperator
{
    /** Sets the alpha blending of a mesh. Only meaningful if the mesh already is alpha blended */
    public static void setMeshAlpha(Mesh m, int alpha)
    {
        m.getVertexBuffer().setDefaultColor(alpha);
    }
   
    /**
     *
     * @param m The mesh to convert to a blended one
     * @param alpha The alpha color to blend with
     * @param textureBlending The texture blending parameter.
     */
    public static void convertToBlended(Mesh m, int alpha, int textureBlending)
    {
        // Set the alpha
        setMeshAlpha(m, alpha);
       
        // Fix the compositing mode
        CompositingMode cm = new CompositingMode();
        cm.setBlending(CompositingMode.ALPHA);
        m.getAppearance(0).setCompositingMode(cm);
        m.getAppearance(0).getTexture(0).setBlending(textureBlending);
    }
}
Particle
/**
 * Holds all the information of a particle.
 * A particle's alpha is controlled directly by its life. Its alpha is always
 * life * 255.
 */
public class Particle
{
    // The life of the particle. Goes from 1.0f to 0.0f
    private float life = 1.0f;
   
    // The degradation of the particle
    private float degradation = 0.1f;
   
    // The velocities of the particle
    private float[] vel = {0.0f, 0.0f, 0.0f};
   
    // The position of the particle
    private float[] pos = {0.0f, 0.0f, 0.0f};
   
    // The color of the particle (RGB format 0xRRGGBB)
    private int color = 0xffffff;
   
    /** Empty initialization */
    public Particle()
    {
       
    }
   
    /**
     * Initializes the particle
     * @param velocity Sets the velocity
     * @param position Sets the position
     * @param color Sets the color (no alpha)
     */
    public Particle(float[] velocity, float[] position, int color)
    {
        setVel(velocity);
        setPos(position);
        this.setColor(color);
    }
    /**
     * @param life The life to set.
     */
    void setLife(float life) {
        this.life = life;
    }
    /**
     * @return Returns the life.
     */
    float getLife() {
        return life;
    }
    /**
     * @param vel The vel to set.
     */
    void setVel(float[] tvel) {
        System.arraycopy(tvel, 0, vel, 0, vel.length);
    }
    /**
     * @return Returns the vel.
     */
    float[] getVel() {
        return vel;
    }
    /**
     * @param pos The pos to set.
     */
    void setPos(float[] tpos) {
        System.arraycopy(tpos, 0, pos, 0, pos.length);
    }
    /**
     * @return Returns the pos.
     */
    float[] getPos() {
        return pos;
    }
    /**
     * @param color The color to set.
     */
    void setColor(int color) {
        this.color = color;
    }
    /**
     * @return Returns the color.
     */
    int getColor() {
        return color;
    }
    /**
     * @param degradation The degradation to set.
     */
    public void setDegradation(float degradation) {
        this.degradation = degradation;
    }
    /**
     * @return Returns the degradation.
     */
    public float getDegradation() {
        return degradation;
    }
}
ParticleEffect
import javax.microedition.m3g.Graphics3D;
/**
 * The interface that determines which effect the particle engine will display.
 * The ParticleEffect class also holds information about the bitmap used
 * for displaying particles (if any)
 */
public interface ParticleEffect
{
    // Initializes a particle
    public void init(Particle p);
   
    // Updates a particle
    public void update(Particle p);
   
    // Renders a particle
    public void render(Particle p, Graphics3D g3d);
}
BitmapParticleEffect
import javax.microedition.m3g.Graphics3D;
import javax.microedition.m3g.Mesh;
import javax.microedition.m3g.PolygonMode;
import javax.microedition.m3g.Transform;
/**
 * Represents a particle effect that uses a bitmap.
 */
public abstract class BitmapParticleEffect implements ParticleEffect
{
    // The mesh
    Mesh mesh = null;
   
    // The transformation matrix
    Transform trans = new Transform();
   
    // The scale
    float scale = 1.0f;
   
    /** Initializes the bitmap used to render particles */
    public BitmapParticleEffect(String filename, float scale)
    {
        // Load the plane with the wanted texture
        mesh = MeshFactory.createAlphaPlane(filename, PolygonMode.CULL_BACK, 0xffffffff);
       
        // Make sure we set the scale
        this.scale = scale;
    }
   
    /**
     * @see ParticleEffect#render(Particle, Graphics3D)
     */
    public void render(Particle p, Graphics3D g3d)
    {
        // Calculate the alpha
        int alpha = (int)(255 * p.getLife());
       
        // Create the color
        int color = p.getColor() | (alpha << 24);
       
        // Set alpha
        MeshOperator.setMeshAlpha(mesh, color);
       
        // Transform
        trans.setIdentity();
        trans.postScale(scale, scale, scale);
        float[] pos = p.getPos();
        trans.postTranslate(pos[0], pos[1], pos[2]);
       
        // Render
        g3d.render(mesh, trans);
    }
}
FountainEffect
import java.util.Random;
/*
 * Created on 2005-aug-31
 */
/**
 * Creates a nice fountain effect for the particles, that shoots particles
 * in a certain direction, determined by its angle. The angle can be changed in real-time.
 */
public class FountainEffect extends BitmapParticleEffect
{
    // The angle of particle emission
    private int angle = 90;
   
    // The sine and cosine of the current angle
    private float[] trig = {1.0f, 0.0f};
   
    // The emitting origin
    private float[] pos = {0.0f, 0.0f, 0.0f};
   
    // The randomizer
    Random rand = null;
   
    /**
     * @param angle The angle of particle emission
     */
    public FountainEffect(int angle)
    {
        // Init the bitmap
        super("/res/particle.png", 0.05f);
       
        // Set the angle
        setAngle(angle);
       
        // Get randomizer
        rand = new Random();
    }
    /**
     * @see ParticleEffect#init(Particle)
     */
    public void init(Particle p)
    {
        // Set the particle's life
        p.setLife(1.0f);
       
        // Set the particle's position
        p.setPos(pos);
       
        // Create the particle's velocties
        float[] vel = new float[3];
       
        // We want velocities from 0.2f to 1.0f
        float xyvel = rand.nextFloat() * 0.8f + 0.2f;
       
        // We want the particle to die slowly
        p.setDegradation(xyvel / 18);
       
        // Set velocities according to trigonometry with a small deviation
        vel[0] = xyvel * trig[1] + rand.nextFloat() * 0.125f - 0.0625f;
        vel[1] = xyvel * trig[0] + rand.nextFloat() * 0.125f - 0.0625f;
       
        // No movement in depth
        vel[2] = 0.0f;
       
        // Set the velocity
        p.setVel(vel);
       
        // Set the random color
        int r = (int)(120 * rand.nextFloat()) + 135;
        int g = (int)(120 * rand.nextFloat()) + 135;
        int b = (int)(120 * rand.nextFloat()) + 135;
        int col = (r << 16) | (g << 8) | b;
        p.setColor(col);
    }
    /**
     * @see ParticleEffect#update(Particle)
     */
    public void update(Particle p)
    {
        // Simply update position
        float[] ppos = p.getPos();
        float[] vel = p.getVel();
        ppos[0] += vel[0];
        ppos[1] += vel[1];
        ppos[2] += vel[2];
       
        // Update life
        p.setLife(p.getLife() - p.getDegradation());
       
        // Check life. If it is dead, we just reinit it
        if(p.getLife() < -0.001f)
        {
            init(p);
        }
    }
    /**
     * @param angle The angle to set.
     */
    public void setAngle(int angle) {
        this.angle = angle;
        trig[0] = (float)Math.sin(Math.toRadians(angle));
        trig[1] = (float)Math.cos(Math.toRadians(angle));
    }
    /**
     * @return Returns the angle.
     */
    public int getAngle() {
        return angle;
    }
    /**
     * @param pos The pos to set.
     */
    void setEmittingOrigin(float[] pos) {
        this.pos = pos;
    }
    /**
     * @return Returns the pos.
     */
    float[] getEmittingOrigin() {
        return pos;
    }
}
ParticleSystem
import javax.microedition.m3g.Graphics3D;
/**
 * Manages emission of particles in our 3D world
 */
public class ParticleSystem
{
    // The effect
    private ParticleEffect effect = null;
   
    // The particles
    Particle[] parts = null;
   
    /**
     * Creates a particle system that emits particles according to a defined effect.
     * @param effect The effect that controls the behaviour of the particles
     * @param numParticles The number of particles to emit
     */
    public ParticleSystem(ParticleEffect effect, int numParticles)
    {
        // Copy the effect
        setEffect(effect);
       
        // Init the particles
        parts = new Particle[numParticles];
        for(int i = 0; i < numParticles; i++)
        {
            parts[i] = new Particle();
            effect.init(parts[i]);
        }
    }
   
    /** The method that does it all. Needs to be called every tick of a game loop */
    public void emit(Graphics3D g3d)
    {
        for(int i = 0; i < parts.length; i++)
        {
            getEffect().update(parts[i]);
            getEffect().render(parts[i], g3d);
        }
    }
    /**
     * @param effect The effect to set.
     */
    public void setEffect(ParticleEffect effect) {
        this.effect = effect;
    }
/**
     * @return Returns the effect.
     */
    public ParticleEffect getEffect() {
        return effect;
    }
}
已有 0 位网友发表了看法